Field notes from running catalog syncs into Shopify's rate limiter until we learned to respect it.
A catalog sync dies quietly. No stack trace, no alert. The dashboard just stops moving, and you sit there watching a progress bar that has decided it's done for the day.
We blamed our own code. Of course we did; it's always your code. We checked the retry logic, the queue, the connection pool, and found nothing, because there was nothing to find.
Then we opened the response log and hit a wall of HTTP 429s. Too Many Requests. Shopify was doing exactly what its documentation said it would, in a section we had read less carefully than we should have (skimming rate-limit docs is a hobby you only get to enjoy once).
It's always your code, until it isn't
Our pipelines push ~5,000 products a day through client catalogs. Fetch, enrich, write metafields, update. Every step is an API call, and every call lands in the same place: a bucket with a hole in the bottom.
Requests pour in the top and drain out the hole at a fixed rate. Pour faster than the drain for long enough and the bucket overflows. The overflow is your 429.

For the REST Admin API, that bucket holds 40 requests. Per store, per app. It drains at 2 requests per second.
It gets better. Every REST call costs exactly one unit, whether you're fetching a single product title or rewriting an entire order with all its line items. The pricing has all the nuance of a coin-operated laundromat.
So: 50 products, 3 calls each (get the product, write metafields, touch inventory). That's 150 calls. At 2 per second, 75 seconds. Running concurrent requests, the bucket itself is dry in 20 seconds.
Fifty products. On a catalog job, fifty products is a rounding error.
Then we did the math on a real store
Take a Shopify Plus store with 50,000 products. Plus buys ten times the room: a bucket of 400, draining at 20 per second. Sounds spacious.
Fetch, enrich, update collections: 3 calls per product means 150,000 calls. At 20 per second, that's 7,500 seconds, or 125 minutes of sustained API time for one pass over the catalog.
And the part that actually hurts: orders, customer lookups, everything else on the store drinks from the same bucket the whole time. Your storefront and your sync are fighting over one drain.
The part we had skimmed
The turn came when we stopped treating GraphQL as nicer syntax and started treating it as a different meter. GraphQL bills you for what you ask, in points. A product title costs 1 point; your budget is 100 points per second on standard plans, 1000 on Plus, with a hard ceiling of 1,000 points on any single query so no one hogs the lane.
That precision is not cosmetic. Fisher Technology, a Shopify Plus partner, cut a customer sync from 200,000 API calls to 40,000 by asking for the three fields they needed instead of the whole record. Same data, one-fifth the cost.
On our 50,000-product job, batched mutations at roughly 50 points per product come to 2,500,000 points. At 1000 per second that's about 42 minutes, against REST's 125. The 1,000-point ceiling packs about 20 products per query, so roughly 2,500 queries where REST needed 150,000 calls.
There's also a third door. Bulk Operations: hand Shopify the whole job, let it grind in the background, collect the results as a JSONL file that sticks around for seven days. None of it touches the bucket your live traffic is drinking from. On API version 2026-01 and newer you can run up to 5 bulk queries and 5 bulk mutations at once; older versions allow one of each.
The trade-off is that bulk results are eventually consistent rather than live. For enrichment work we genuinely don't care; the 3am sync isn't in a hurry.

Same job, three meters
None of this ever lands on an invoice, because Shopify doesn't charge per API call. The cost arrives as time: how long a pass over the catalog takes, and whether a traffic spike stalls the pipeline behind it.
Most sync problems that get blamed on Shopify are architecture problems wearing a costume.
What we keep taped to the wall
- Know your SKU count and your daily calls per SKU, and map them against the bucket before you build, not after.
- Past 1,000 catalog updates a day, GraphQL or Bulk Ops stops being optional.
- Past 50,000 SKUs with enrichment on top, Bulk Ops is table stakes.
We built our processing capacity around these numbers because they aren't negotiable, and oddly enough the constraint made the system better. These days, when a dashboard stops moving, we open the response log before we open our own code.
The bucket leaks on purpose. Respect the drain.
If you'd rather find your ceiling before your catalog does, OKART's Infrastructure Stress-Test maps your catalog and API traffic against these limits, read-only, in a few days.
Sources
- REST Admin API rate limits: leaky bucket algorithm, request limits, and plan tiers for REST API.
- Shopify API limits: GraphQL Admin API rate limits, point-based costs, and single-query maximum.
- Bulk operations with the GraphQL Admin API: asynchronous bulk query and mutation operations, concurrency limits, and JSONL results.
- An Introduction to Rate Limits: leaky bucket mechanics, rate-limit approaches (REST, GraphQL, Storefront), and practical impact.
- GraphQL vs REST: How One Shopify Partner Increased Performance: real case study, Fisher Technology's 200,000 to 40,000 API call reduction and performance gains.





Share:
The sweater our pipeline promoted to merino wool
The sweater our pipeline promoted to merino wool