Skip to content

Rate limits

What the API returns when you exceed a limit and how to retry.

When you exceed a limit, the API returns 429 Too Many Requests with a Retry-After header: the number of seconds to wait before retrying, at least 1.

Response
HTTP/1.1 429 Too Many Requests
Retry-After: 12
Content-Type: application/json

{ "status": 429, "title": "Too many requests" }

What is limited

  • All requests, per credential or per IP address.
  • Storefront purchase writes: checkout, payments, payment receipts, coupons, reviews, cancellations, and returns have a lower limit than reads.
  • Authentication: POST /connect/token and sending sign-in codes.
  • Admin bulk operations: imports, exports, and batch updates run one at a time per store. If you send another before the previous one responds, you get 429.

Retry

Wait for the time in Retry-After and send the request again. Cap the number of attempts.

retry-after-429.ts
async function withRetryAfter(request: () => Promise<Response>, maxAttempts = 4) {
  for (let attempt = 1; ; attempt += 1) {
    const response = await request();
    if (response.status !== 429 || attempt === maxAttempts) return response;

    const seconds = Number(response.headers.get("retry-after") ?? "1");
    await new Promise((resolve) => setTimeout(resolve, seconds * 1000));
  }
}

Retrying a 429 is safe: the rejected request did not run. If several processes share credentials, use a shared queue so they don't all retry at once.

An accepted import returns 202 with the id of an operation that runs in the background. Track it with GET /v1/operations/{id}; uploading the file again creates another import.

Use fewer requests

  • Request large pages: limit=200 on cursor lists.
  • Cache data that rarely changes, such as categories, brands, or zones.
  • Use webhooks instead of polling.

Last updated:

On this page