Back to blog
TutorialJuly 2, 20268 min read

What to Do When an API Call Fails: Retries, Concurrency, and Throughput

Learn how to handle failed Just One API calls: retry transient failures with bounded backoff, tune concurrency for your workload, and track business codes to improve successful requests per minute.

The short answer

API calls are not 100% successful, and that is normal for real-time data collection APIs. A request can fail because of the target platform state, network fluctuation, upstream page changes, a single collection path, or a client-side timeout. When a call fails, the right response is not to stop the whole job. Classify the failure and use a bounded retry with short backoff when the error is retryable.

The Just One API docs state that there are no general rate limits for API usage. In practice, you do not need to hold failed requests for a long time just because you are worried about a platform-wide concurrency limit. You can increase or decrease concurrency based on your workload: raise concurrency to finish a batch faster, or lower it to reduce pressure on your local machine, network, and downstream storage.

A few high-traffic endpoints may document specific restrictions, so always follow the endpoint docs when they mention one. Retries and concurrency are tools for improving successful throughput, but they still need maximum attempts, timeouts, logging, and clear stop conditions.

Check code, not only HTTP status

Just One API business results should be determined by the code field in the response body. According to the API Usage Guide, code: 0 means success. Non-success business codes should be handled by type.

  • 0 means success, so the result can enter your business workflow.
  • 301 means collection failed and should be retried.
  • 500 means internal server error and can usually be retried with a maximum attempt count.
  • 302 means rate limit exceeded. If this appears for an endpoint, reduce concurrency or add delay for that endpoint.
  • 303 means the daily quota has been exceeded. Stop retrying until the quota window resets; this condition may be returned with HTTP 429.
  • 100, 400, 600, 601, and 602 usually require fixing token, parameters, permissions, balance, or token budget settings instead of blind retries.
  • HTTP timeout, connection reset, DNS errors, and other network failures can usually enter the retry queue.
  • For HTTP failures, retry network errors, 408, 429, and temporary 5xx responses. Other 4xx responses normally require a request or authentication fix.

The business code table in the docs also marks code: 0 as billed and non-success business codes as not billed. Even so, do not retry forever. Infinite retries waste task time, make logs harder to read, and can hide parameter problems.

Why prompt, bounded retries help

Many failures are temporary. A failed attempt does not mean the same parameters will fail again. Real-time collection may hit a short-lived target platform issue, a network route problem, a page state change, or a client timeout that is too short. Retrying promptly with exponential backoff and jitter can recover part of those failed calls without sending a synchronized burst to an already stressed service.

For batch jobs, the goal is not to make every single attempt succeed. The goal is to get more successful results in the same amount of time. If you only run requests slowly and serially, total successful output can be low. If you use higher concurrency, then even a lower success rate can still produce meaningful successful results per minute.

This is a throughput mindset, not just a single-attempt success-rate mindset. Track both success rate and successful requests per minute. A low success rate with high concurrency may still satisfy the business requirement. A high success rate with very low concurrency may still make the overall job too slow.

A practical retry policy

Make failure handling an explicit strategy instead of scattering temporary if checks through business code.

  • Retry 301, network errors, timeouts, and temporary 500 responses after a short exponential backoff with jitter.
  • Set a maximum attempt count per request, such as 3 to 5 attempts.
  • Log attempt number, endpoint path, version, request key, business code, latency, and error message.
  • Stop immediately for 100, 303, 400, 600, 601, and 602 because these are quota, configuration, or account-state problems.
  • Treat 302 separately by reducing endpoint concurrency or adding delay, and honor Retry-After when the server provides it.
  • For batch jobs, put retryable failed items back into a delayed queue so workers can continue instead of stopping the whole job.

The Just One API docs recommend a 120-second request timeout. If that is too long for your workflow, use at least 60 seconds. A timeout that is too short can turn requests that might have succeeded into false failures, creating unnecessary retries.

Concurrency is a business knob

Just One API does not apply a general rate limit, so concurrency can be tuned according to your business goal. Higher is not always better, and lower is not always safer. Concurrency should serve the job you are running.

  • Increase concurrency when a batch must finish quickly.
  • Reduce concurrency when your machine, network egress, or downstream database is under pressure.
  • If one endpoint has a lower short-term success rate, either raise concurrency to preserve successful throughput or reduce concurrency to save resources, depending on business priority.
  • If the job is not time-sensitive, run it more slowly. If it has a time window, derive concurrency from the target number of successful results.

For a simple estimate, if the current single-attempt success rate is about 20% and you send 1000 attempts per minute, you may get around 200 successes per minute. If you only send 50 attempts per minute, you may get around 10 successes per minute. This is throughput arithmetic, not a platform success-rate promise, but it explains why lower success-rate endpoints can still produce valuable output under higher concurrency.

A workable configuration shape

Put concurrency, timeout, and retry count in configuration so they can be tuned per endpoint and workload.

json
{
  "default": {
    "concurrency": 100,
    "timeoutMs": 120000,
    "maxAttempts": 4
  },
  "slowBatchJob": {
    "concurrency": 30,
    "timeoutMs": 120000,
    "maxAttempts": 5
  },
  "urgentBatchJob": {
    "concurrency": 300,
    "timeoutMs": 90000,
    "maxAttempts": 3
  }
}

These numbers are examples of the configuration shape, not fixed recommendations. Tune them according to your machine, network, endpoint behavior, job deadline, and downstream write capacity.

Pseudocode example

This minimal example shows the retry decision. A production job should also include a queue, logging, cancellation, and deduplication.

js
const retryableCodes = new Set([301, 500]);
const stopCodes = new Set([100, 303, 400, 600, 601, 602]);
const sleep = (milliseconds) =>
  new Promise((resolve) => setTimeout(resolve, milliseconds));

function retryDelayMs(attempt, retryAfter) {
  const retryAfterValue = retryAfter?.trim();
  if (retryAfterValue) {
    const retryAfterSeconds = Number(retryAfterValue);
    if (Number.isFinite(retryAfterSeconds) && retryAfterSeconds >= 0) {
      return retryAfterSeconds * 1000;
    }

    const retryAt = Date.parse(retryAfterValue);
    if (Number.isFinite(retryAt)) {
      return Math.max(0, retryAt - Date.now());
    }
  }

  return Math.min(500 * 2 ** (attempt - 1), 8_000) + Math.random() * 250;
}

async function callWithRetry(callApi, maxAttempts = 4) {
  for (let attempt = 1; attempt <= maxAttempts; attempt += 1) {
    let response;

    try {
      response = await callApi();
    } catch (error) {
      if (attempt === maxAttempts) {
        throw error;
      }

      await sleep(retryDelayMs(attempt));
      continue;
    }

    const result = await response.json().catch(() => null);

    if (result?.code === 303) {
      throw new Error("Daily quota exceeded; wait for the quota window to reset");
    }

    if (!response.ok) {
      const retryableHttp =
        response.status === 408 ||
        response.status === 429 ||
        response.status >= 500;

      if (!retryableHttp || attempt === maxAttempts) {
        throw new Error(`HTTP ${response.status}: request failed`);
      }

      await sleep(retryDelayMs(attempt, response.headers.get("retry-after")));
      continue;
    }

    if (!result) {
      throw new Error("API returned an invalid JSON response");
    }

    if (result.code === 0) {
      return result.data;
    }

    if (result.code === 302) {
      if (attempt === maxAttempts) {
        throw new Error("Rate limit persisted after all retry attempts");
      }

      await sleep(retryDelayMs(attempt, response.headers.get("retry-after")));
      continue;
    }

    if (stopCodes.has(result.code) || !retryableCodes.has(result.code)) {
      throw new Error(`Do not retry code ${result.code}: ${result.message}`);
    }

    if (attempt === maxAttempts) {
      throw new Error(`API failed with code ${result.code}: ${result.message}`);
    }

    await sleep(retryDelayMs(attempt));
  }
}

The key idea is simple: return immediately on success, retry only retryable failures with bounded exponential backoff, honor a valid Retry-After, and stop early for non-retryable failures. The example assumes callApi returns a Fetch-style Response and only throws for transport failures. In production, also feed repeated 302 or 429 signals back into endpoint concurrency control.

Deduplicate retry results

Retries introduce an engineering issue: the same business object may be attempted multiple times, and more than one attempt may eventually succeed. Use a stable request key to avoid duplicate writes.

  • For note details, use platform, endpoint version, and note ID.
  • For product details, use platform and product ID.
  • For search jobs, use keyword, page, sort option, and time window.
  • At write time, use a unique index or idempotent upsert so the same item is not saved twice.

The more aggressive your retry strategy is, the more important deduplication and logging become. Otherwise successful throughput may rise while downstream data quality gets worse.

When to adjust concurrency

Concurrency should not be a fixed number that never changes. Adjust it based on real signals.

  • If successful requests per minute are below target, increase concurrency or retry attempts.
  • If local CPU, memory, network, or database writes are under pressure, reduce concurrency.
  • If 302 increases, reduce concurrency or add delay for that endpoint.
  • If 301 increases but successful throughput is still acceptable, keep the strategy and monitor it.
  • If timeouts increase, first check whether the timeout is too short, then consider reducing concurrency or switching endpoint versions.

If the same endpoint has multiple versions, combine this with the API Version Number Guide. When one version is under pressure or has a lower short-term success rate, switching to a tested backup version can be more effective than retrying the same version forever.

Common questions

Should I wait before retrying a failed call

Use a short delay for retryable failures. For 301, network failures, timeouts, 408, and temporary 5xx responses, use bounded exponential backoff with jitter. For 302 or HTTP 429, honor Retry-After when available and reduce endpoint concurrency. For 303, wait for the daily quota window to reset.

Does a low success rate mean the endpoint is unusable

No. A low success rate means single attempts are less stable in the current window. If your workload allows concurrency, successful requests per minute may still be high enough for the business goal. Batch jobs should track throughput, failure cost, and deadline, not only single-attempt success rate.

Is higher concurrency always better

No. Higher concurrency creates more attempts per minute, but it also increases pressure on your local resources, network, downstream database, and logs. Raise concurrency gradually based on successful-throughput targets and monitor error codes and resource usage.

Should every error be retried

No. Invalid parameters, invalid token, permission denial, insufficient balance, and token budget limits require fixing configuration or account state first. Retrying the same bad request will not solve the problem.

Next steps

Before integrating, read the API Usage Guide for business codes, timeout guidance, and authentication. Then set concurrency, maximum attempts, timeout, and failure logs for your collection jobs. When you are ready to call the API, open JustOneAPI Dashboard to get your token and keep real tokens in secure configuration.

Continue with Just One API

Log in to the Dashboard for your token, read the full API docs, or open the MCP GitHub project for the latest configuration.