Rate limits

The admin API is rate limited per organization, not per key. Minting a second secret key does not buy a second allowance. Every key an organization holds spends from the same bucket.

Requests you make from the dashboard while signed in are not counted. The limit applies only to requests authenticated with a secret key.

The ceiling

Burst600 tokens
Refill10 tokens per second

It is a token bucket. Every request spends tokens, and the bucket refills continuously at 10 a second up to its 600 ceiling. An idle integration therefore starts with the full 600 and can spend it as fast as it likes, then settles into 10 requests a second for as long as it keeps going.

Those numbers are sized for the heaviest thing a well-behaved integration does: a one-time migration that reads everything you have. At the maximum page size of 250 records, a 100,000 record catalog is 400 requests, which fits inside a single burst with room to spare. Nothing that walks your data page by page should ever see a 429.

The ceiling is your plan's apiRequestsPerMinute allowance (600 on every plan except Unlimited, which has 3,000), so it is a number on your organization rather than a constant. GET /organization returns the value your requests are measured against.

What each endpoint costs

Most endpoints spend one token. The ones that do noticeably more work per request cost more, so a loop over them is bounded by the work it causes rather than by the number of requests it makes.

CostEndpointsWhy
10POST /customers/import, POST /orders/import, POST /products/import, POST /reviews/import, POST /wishlists/import, POST /loyalty-memberships/import, POST /loyalty-historyOne call carries an unbounded batch of records and writes all of them.
10POST /import-jobs, POST /export-jobsQueues a background job that reads or writes a whole file of rows.
5POST /analytics/queryAggregates a whole resource, which is the most expensive read in the API.
5GET /saved-charts/{id}/data, and a saved chart's data linkThe same aggregate. An answer from the last minute is reused and costs 1.
5PATCH /customers/bulk-update, PATCH /orders/bulk-update, PATCH /products/bulk-update, PATCH /reviews/bulk-update, POST /loyalty-activity/bulk-expireUpdates every record matching a filter in one request.
1Everything elseOrdinary reads and single-record writes.

Each endpoint's cost is on its page in the API reference, and in the OpenAPI document your store serves at /api/openapi as x-rate-limit-cost on the operation, so you can plan against it without reading this table.

Reads are charged as well as writes. Paginating a list endpoint without advancing your cursor is the most common way to use up the limit, and counting only writes would miss it.

When you go over

A request that cannot afford its cost gets a 429 and does nothing else. It is not queued and it is not partially applied.

{
  "error": {
    "code": "TOO_MANY_REQUESTS",
    "message": "Too many API requests for this organization. Slow down and try again in a moment."
  }
}

The response carries a Retry-After header with the whole number of seconds until you can afford the same request again. Wait that long rather than retrying immediately, because a tighter retry loop only makes the wait longer for everything else your organization is doing.

HTTP/1.1 429 Too Many Requests
Retry-After: 3

The endpoints with their own limits

Two endpoints carry a separate bucket on top of this one, because they are called from your own backend at a different rate:

  • POST /loyalty-events: a burst of 120, refilling at two a second. This follows your plan's loyaltyEventsPerMinute allowance, which is 600 on Unlimited.
  • POST /loyalty-history: a burst of 30, refilling at one a second. It is deliberately tighter than the ceiling would be on its own, so a runaway loop is told which endpoint it exhausted.

These are per organization and per endpoint, so they do not compete with each other. A request to one of them spends from both its own bucket and the organization-wide one.

The widget API

The widget API is limited differently, because it is called from every shopper's browser rather than from one backend of yours.

A request carrying a publishable key is limited per address: each combination of publishable key and caller address gets a burst of 120, refilling at two a second. That is 120 requests a minute for one shopper, which is a product page firing every widget it has and a browsing session on top. It is also the limit Shopify documents for its own Storefront API per buyer address (a 60 second bucket at half a second a request, leaking one a second), so a headless storefront already stays within it. Going over gets the same 429 with a Retry-After header:

{
  "error": {
    "code": "TOO_MANY_REQUESTS",
    "message": "Too many requests from this address. Try again in a moment."
  }
}

A few storefront actions have a tighter limit of their own on top, again per caller address: asking for an instant answer (20 a minute), voting on whether a review was helpful (10 an hour), and submitting a review or uploading its photos. These apply whichever key the request carries, so a server sending them for many shoppers shares one address's allowance.

Send apiPublishableKey once. A request that names it more than once is refused with a 400.

A request carrying a secret key as a bearer token draws on the organization ceiling above instead, at one token per request, and takes the place of the apiPublishableKey parameter.

A server rendering pages for many shoppers should use a secret key, not the publishable key. Every call would otherwise come from one address, so your whole storefront would share a single shopper's allowance. See Calling the widget API from a server.

Raising the limit

The ceiling is a field on your organization, so it can be raised for your account without a release. If a legitimate integration cannot fit inside it, get in touch before you start working around it. An import endpoint or a background export job is usually a better answer than a faster loop.