Shopify's 429 carries a Retry-After header and a 40-request bucket; a WooCommerce 429 comes from the host, not from WooCommerce. How to read both and sync well.
Key takeaways
- Shopify publishes its limits: a 40-request bucket with a 2-per-second leak rate on standard plans, and ten times that on Shopify Plus.
- Every REST response carries `X-Shopify-Shop-Api-Call-Limit` in the form `32/40`, so you have a fuel gauge on every call.
- GraphQL uses a completely different model: a points-per-second budget, and a single query may not exceed a cost of 1,000 points.
- WooCommerce itself does not rate limit the REST API. A 429 from it belongs to the web server, the CDN or a security plugin.
- A sync built on a loop with a fixed `sleep` breaks the moment anything else touches the same store.
Shopify's 429 is a documented mechanism: the REST Admin API uses a leaky bucket of 40 requests per app per store on standard plans, and a 429 response carries a Retry-After header telling you how many seconds to wait. WooCommerce is the opposite case - core does not rate limit the REST API, so a 429 there almost always comes from the host or a WAF.
Two different models on one platform
Shopify operates two separate limiting systems, and confusing them is the most common reason a developer "fixes" a 429 that keeps coming back.
| REST Admin API | GraphQL Admin API | |
|---|---|---|
| Unit measured | The request | Calculated cost in points |
| Standard-plan budget | 40-request bucket | 100 points per second |
| Recovery rate | 2 requests per second | Continuous refill, per restoreRate |
| Where you see the state | X-Shopify-Shop-Api-Call-Limit header | extensions.cost.throttleStatus in the body |
| On exceeding it | 429 with Retry-After | The query is throttled and the budget is reported back |
| Additional ceiling | None | A single query capped at 1,000 points |
The practical difference: on REST the request is the unit, so pulling 500 products one at a time drains the bucket even though each call is tiny. On GraphQL the cost is derived from how many fields and connections you asked for, so one over-broad query can blow the budget by itself.
Why does WooCommerce return 429 when WooCommerce has no rate limit?
Because the limit is not coming from WooCommerce. A WooCommerce store runs on WordPress, which runs on a web server, which usually sits behind a protection layer. Any of those can return a 429, and none of them knows anything about your store logic.
- The host. Nginx or Apache limiting rules on
wp-jsonpaths and onadmin-ajax.php. - A security plugin or WAF. Blocks applied to specific paths, sometimes with no
Retry-Afterheader at all. - The Store API. This is the one layer where WooCommerce does document a rate limit of its own - a default of 25 requests in a 10-second window, on POST requests only, and disabled by default. It returns
RateLimit-Limit,RateLimit-Remaining,RateLimit-ResetandRateLimit-Retry-Afterheaders.
So diagnosis on WooCommerce starts from a different question entirely: not "how do I slow the sync down" but "who returned this 429". The answer is visible in the response body - a native WordPress error looks nothing like a WAF block page.
Reading the state instead of guessing it
- On Shopify REST, read
X-Shopify-Shop-Api-Call-Limiton every response. A value of38/40means two requests of headroom, and you can slow down before being throttled rather than after. - When a 429 arrives, honour
Retry-Afterexactly as given. That is the number of seconds the platform itself is asking for, and it beats any formula you invent. - On GraphQL, read
throttleStatuson every response:maximumAvailable,currentlyAvailableandrestoreRategive you the whole budget picture. - Keep
requestedQueryCostalongsideactualQueryCost. A wide gap means you are requesting pages larger than what comes back. - On WooCommerce, log the status code together with the raw response body and every header. Without that you cannot separate a server block from a Store API limit.
How do you build a sync that survives a 429?
- Separate collection from processing. One process fetches and writes to a queue, another consumes it. A sync that does both in one loop stops entirely on the first 429.
- Give every queue item an attempt counter and a next-attempt time.
Retry-Afteris written into that next-attempt time, not into asleepthat blocks the whole worker. - Run a single worker per store. The Shopify bucket is per app per store, so two parallel workers split the same budget and hit the wall sooner.
- Use cursor-based pagination and persist the cursor. A sync that restarts from the beginning after every failure spends its budget re-fetching what it already had.
- Make writes idempotent. After a 429 you do not know whether the request was blocked before or after the update landed, and without a unique key per operation you create duplicates.
- Record the actual rate, not the intended one. Requests genuinely sent per minute is the only number you can tune against.
What actually breaks
- A fixed
sleep. Works in isolation, fails the moment a second app or a human in the admin draws on the same bucket. - Ignoring
Retry-After. An immediate retry extends the throttle rather than shortening it. - Running in parallel "to speed it up". Doubles consumption, reaches 429 faster, and completes no more work.
- One enormous GraphQL query. Fails on the 1,000-point ceiling rather than the per-second budget, so slowing down will never fix it.
- Treating 429 as fatal. The process dies, somebody reruns it from the start, and the cycle closes.
- Syncing everything every run. A full pull instead of changes since the last timestamp is the fastest way to exhaust a budget.
Diagnosing a stalled sync in five minutes
- Pull the full status code and every response header for the failed request out of your logs. Without the headers there is no diagnosis.
- On Shopify, look at
X-Shopify-Shop-Api-Call-Limiton the requests immediately before the failure. If it was already near the ceiling, this is a pacing problem rather than a bug. - Check whether
Retry-Afteris present at all. Its presence indicates a declared platform limit; its absence suggests an infrastructure-layer block. - Count how many requests actually went out in the minute before the failure. That number, not the intent expressed in the code, is what the platform measured.
- Check who else was working against the same store at that moment - another app, a second worker, or a human exporting data from the admin.
- On GraphQL, compare
requestedQueryCostwithactualQueryCoston the last successful calls. A high requested cost is the signal that your pages are too large.
The order matters, because the first four steps separate three problems that look identical from the outside: too high a rate, too expensive a query, and a block that has nothing to do with the store at all.
Related: What API rate limiting is, Shopify vs WooCommerce, Connecting an online store to an ERP.
Sources
Frequently asked questions
How many requests am I allowed on Shopify?
On the REST Admin API, standard plans get a bucket of 40 requests per app per store, refilling at 2 requests per second. Shopify Plus values are ten times higher. GraphQL is measured in points rather than requests, so the number depends entirely on the query you send.
Does Shopify return a Retry-After header?
Yes. A 429 from the REST Admin API is returned together with a `Retry-After` header containing the number of seconds to wait. That is the value to honour, rather than a schedule you decided in advance. On GraphQL the equivalent information arrives inside `extensions.cost.throttleStatus`.
I got a 429 from WooCommerce. What do I check first?
Who returned it. WooCommerce core does not rate limit the REST API, so check the web-server logs, the WAF or CDN rules, and any security plugins. If the block is on a checkout path, also check whether the Store API rate limiting feature has been enabled.
Can the limit be raised?
On Shopify the limit follows the store's plan tier and is not adjustable per app. On WooCommerce, because the limit comes from infrastructure, the hosting provider is the place to ask. In both cases, reducing the number of requests is a better investment than trying to enlarge the budget.
Keep reading
Related service
Inventory & Purchasing
SKU-level stock, reorder rules and an approval flow that leaves a record.
About the author
Yehonatan Saadia
Freelance automation, web & MVP developer
I'm Yehonatan Saadia, a senior developer who builds business automation, custom websites, and MVPs for small and mid-sized companies across the US, Europe, and Israel. These guides come from real client work, not theory.
Work with meHave a project like this?
Tell me what you're trying to automate or build. I reply within 24 business hours with a few targeted questions, then we walk through it on a free 30-minute call, with no commitment. You come away with a scope, a timeline and a fixed price - or a straight answer that it isn't worth building.
