Shopify's REST Admin API uses a leaky bucket: you get a bucket of 40 requests that drains at 2 per second on standard plans. You can burst to 40, but sustained throughput is 2 per second, and going over returns HTTP 429 with a Retry-After header. Almost every integration hits this the first time it syncs a real catalogue, because it was built and tested against a store with fifty products. The fix is not faster retries, it is pacing: honour Retry-After, keep a token budget client-side, and move bulk work to the GraphQL Admin API where cost-based limits let you fetch far more per call.
Shopify’s REST Admin API meters requests with a leaky bucket:
Retry-After header.The distinction that catches people out is between burst and sustained capacity. You can fire 40 requests instantly. You cannot fire 40 requests per second. Sustained throughput is 2 per second, and the bucket of 40 only buys you twenty seconds of that rate before you are pinned to the drain rate.
Integrations get built against development stores. A development store has fifty products and a dozen orders, so the bucket never fills and the limit never appears.
Then the same code points at a store with 20,000 products and a year of order history, and the arithmetic becomes visible: at 2 requests per second, 20,000 sequential requests take nearly three hours, assuming nothing else is competing for the same budget.
Nothing broke. The code was always this shape. The volume simply made the shape matter.
Do not retry immediately. A retry is a request, and a request fills the bucket you are waiting on. Tight retry loops keep you permanently at the limit and turn a brief slowdown into a stall.
Do not use a fixed sleep. A hard-coded one-second pause is either too slow when there is headroom or too fast when there is not, and it ignores the answer the API already gave you.
Do not treat 429 as an error. It is flow control. An integration that logs it as a failure will fill its error tracking with noise and hide the failures that matter.
| Approach | Effect |
|---|---|
Honour Retry-After | The API tells you exactly how long to wait. Use that number. |
| Read the bucket state from response headers | Slow down before you hit the wall rather than after |
| Pace client-side with a token budget | Requests queue at a sustainable rate instead of racing |
| Exponential backoff with jitter | Prevents several workers from retrying in lockstep |
| Move bulk reads to GraphQL | Cost-based limits, far more data per call |
| Idempotency keys on writes | A retried write cannot create a duplicate |
That last row is the one people skip, and it is the one that causes real damage. Rate limiting turns a single logical write into several attempts. Without an idempotency key, a retry after a timeout can create a second order, a second invoice, a second refund. The rate limit is an inconvenience; a duplicate financial document is an accounting problem.
Rate limits are not a bug to be fixed once. They are a permanent condition of the environment that shifts underneath you: Shopify changes limits, moves functionality between REST and GraphQL, and deprecates API versions on a schedule. A sync that is paced correctly today can be wrong in six months because the endpoint it depends on was reshaped.
Seeing this in production and want it to stop being your problem? We design, build, and permanently operate the pipeline, including the validation and retry logic that stops this class of failure reaching you. Fixed-price scoping quote within 48 hours.