Shopify API Rate Limit Error: The Real Fix
A Shopify API rate limit error means you've hit the leaky bucket limit on REST or GraphQL. Here's why it happens and the fix that holds up at scale.

429 Too Many Requests. Or worse — nothing errors at all, your app just gets slower and slower until a sync job that normally finishes in four minutes is still running at forty, and nobody can tell you why.
That's a Shopify API rate limit error, and it behaves differently depending on whether you're hitting the REST Admin API or the GraphQL Admin API, which is exactly why half the fixes people copy from a forum post don't actually hold up on their setup. I hit a version of this on a client's inventory sync app a few months back — tens of thousands of SKUs, a nightly cron job, a REST client that worked fine in staging and fell over completely once the store crossed about 40,000 variants. Here's what's actually happening, and the fix that holds up once your data stops being toy-sized.
Why This Happens: Shopify's Leaky Bucket
Shopify throttles both Admin APIs with a leaky bucket, not a flat "X requests per minute" counter. On REST, a standard store gets a bucket capacity of 40 requests that refills at 2 requests per second — Shopify Plus stores get a bigger bucket, usually 80. Every call drains the bucket by 1; it refills continuously in the background. Burst past the capacity and you get a 429. Stay under the refill rate and you never will, even running flat out.
GraphQL works on the same leaky-bucket idea but measures cost, not request count. A standard store's bucket holds 1000 points and refills at 50 points per second. A simple query might cost 1 point; a deeply nested query pulling variants, metafields, and inventory levels in one shot can cost 50+ points on its own. This is the part people miss — a GraphQL query that "looks like one request" can drain the bucket faster than ten REST calls.
REST and GraphQL Aren't the Same Throttle
Switching from REST to GraphQL doesn't automatically fix a rate-limit problem — it just changes what you're budgeting. REST is predictable: count your calls, stay under 2/sec sustained, you're fine. GraphQL forces you to actually think about query cost, which Shopify returns on every response under extensions.cost. Ignore that field and you're flying blind, because the same query shape can cost wildly different amounts depending on how many nested objects actually come back.
The Fix: Read the Headers, Don't Guess a Delay
Every REST response includes X-Shopify-Shop-Api-Call-Limit, formatted as used/capacity (e.g. 32/40). Every GraphQL response includes the cost breakdown in extensions.cost.throttleStatus. Use those instead of a hardcoded sleep:
This does two things a flat delay can't: it backs off instantly and correctly on an actual 429 using Shopify's own Retry-After value, and it throttles proactively before you hit the wall rather than after.
Where Most Teams Get This Wrong
Most of the fixes I see are a flat sleep(500) between every single call, no matter what. That's the wrong instinct. On a quiet sync with plenty of bucket headroom, you're wasting time you didn't need to spend. On a heavy one — multiple workers, webhooks firing, another app hitting the same store's API — you'll still get throttled, because a fixed delay has no idea what else is draining the bucket. I'd skip the fixed-delay approach entirely unless you're doing something trivially small, and read the actual headroom instead. It's not more code, just the right fifteen lines instead of the lazy one.
Bulk Operations: The Real Fix for Big Syncs
For anything resembling a full catalog import or export — which is what actually broke on that client project — stop fighting the per-request bucket and use Shopify's Bulk Operations API instead. bulkOperationRunQuery and bulkOperationRunMutation run asynchronously on Shopify's side and hand you back a JSONL file when done, completely outside the normal per-call throttle. Swapping that sync job from paginated REST calls to a single bulk query dropped it from the 40-minute failure mode back under five minutes, with zero rate-limit retries.
Frequently Asked Questions
Does switching from REST to GraphQL fix rate limiting on its own?
No. It changes the unit you're budgeting — cost instead of request count — but a badly-shaped GraphQL query can still burn through the bucket faster than REST would have. Check extensions.cost on every response before assuming GraphQL solved anything.
Why am I getting throttled when I'm staying under 2 calls per second myself? Because the bucket is shared per store, not per app or per client. Webhooks, Shopify Flow, other installed apps, and even Shopify's own background jobs can all draw from the same allocation. If you're rate-limited while your own code looks compliant, something else hitting that store's API is the likely cause.
The Bottom Line
Read the rate-limit headers Shopify already gives you on every response, back off proactively before you hit the ceiling instead of reactively after a 429, and move any real bulk sync onto the Bulk Operations API instead of fighting the per-request bucket with pagination. That's the actual fix — everything else is just slower ways of hitting the same wall.


