Your integration passes every test in a development store. Then it goes live against a client’s catalogue of 40,000 SKUs, or runs a nightly sync against a Shopify Plus store with genuine order volume, and suddenly you’re seeing THROTTLED errors or HTTP 429 responses. Nothing in your code changed, the traffic pattern did.
This is the single most common production surprise for developers building custom Shopify integrations, and it’s entirely avoidable if you design for Shopify’s rate limiting model from the start rather than bolting on retry logic after something breaks.
This post walks through how Shopify actually throttles API access, the GraphQL Admin API’s cost-based system and, for context, the older REST Admin API’s simpler call-limit approach, and the practical patterns experienced Shopify developers use to build integrations that stay well inside the limits, even at scale.
A note on numbers before we start: Shopify’s exact bucket sizes, cost points, and leak rates vary by API version, plan tier (standard Shopify vs Shopify Plus), and occasionally change as Shopify updates the platform. Rather than quoting specific figures that could be outdated by the time you read this, this post explains the mechanism precisely so you understand how to calculate and monitor your own limits, and flags where you should confirm the current numeric values against Shopify’s official developer documentation before you build.
The Short Answer: How Shopify Throttles API Requests
Shopify uses a leaky bucket algorithm for both of its Admin APIs, but the two implementations work differently:
- GraphQL Admin API, throttling is cost-based. Every query and mutation has a calculated “cost” in points based on the fields and connections it requests, not simply one request equals one unit. A shop has a bucket with a maximum size and a restore (leak) rate measured in points per second. Expensive queries drain the bucket faster than cheap ones.
- REST Admin API, throttling is a simpler call-limit bucket. Each request costs the same regardless of complexity, and the bucket has a maximum number of calls with a fixed leak rate per second.
Both approaches exist to protect Shopify’s shared infrastructure from any single app or integration overwhelming it, and both return a clear signal when you’ve hit the ceiling, so a well-built integration should never actually fail because of throttling, only slow down briefly.
GraphQL Admin API: Cost-Based Throttling Explained
Shopify has been steering developers toward the GraphQL Admin API for new integrations for some time, largely because it lets you request exactly the fields you need in a single round trip instead of chaining multiple REST calls. That efficiency is also why its throttling model is more nuanced.
The leaky bucket model
Think of each shop as having a bucket that holds a maximum number of cost points. Every GraphQL request you send drains points from that bucket equal to the query’s calculated cost. Points continuously “leak” back into the bucket at a steady restore rate, measured in points per second. If a request’s cost would take the bucket below zero, Shopify rejects it with a THROTTLED error instead of processing it.
Shopify Plus stores generally get a larger bucket and faster restore rate than standard Shopify stores, reflecting the higher API demand of enterprise integrations, but confirm the current multiplier in Shopify’s documentation for the API version you’re targeting, as this has been revised over time.
How query cost is calculated
Cost is calculated from the structure of your query, not the size of the response:
- Simple scalar fields (a product’s title, an order’s total) typically carry a small, often negligible cost.
- Connections, fields that return lists, like
products(first: 50)ororder.lineItems, cost more, and the cost scales with how many items you ask for (thefirstorlastargument). Requesting 250 items in a connection costs meaningfully more than requesting 10. - Nested connections compound. A query that fetches 50 orders, and for each order fetches 50 line items, and for each line item fetches variant data, can rack up cost quickly because the nesting multiplies out.
- Mutations generally cost more than equivalent read queries, since they trigger write operations and side effects.
Shopify calculates a requested query cost before running your query (used to decide whether to throttle it) and returns an actual query cost after execution, which can differ slightly depending on how many items were actually returned. Both values, along with your current bucket status, are returned in every response under the extensions.cost object, this is the single most useful diagnostic tool you have.
A typical extensions block looks like this:
"extensions": {
"cost": {
"requestedQueryCost": 52,
"actualQueryCost": 47,
"throttleStatus": {
"maximumAvailable": 2000.0,
"currentlyAvailable": 1953.0,
"restoreRate": 100.0
}
}
}
If you’re not already logging this block on every request, that’s the first change to make, it tells you exactly how close to the ceiling you’re running before it becomes a problem.
Handling THROTTLED errors
When a query would exceed the available bucket, Shopify returns a top-level errors array containing an entry with "extensions": { "code": "THROTTLED" }, rather than a normal GraphQL response. The correct handling pattern is:
- Check for the
THROTTLEDerror code specifically, rather than treating any error as fatal. - Read
throttleStatus.currentlyAvailableandrestoreRatefrom the response (if present) to calculate how long until enough points restore. - Wait an appropriate interval, then retry the same query, don’t silently drop the request or surface it as a user-facing failure.
- If throttling happens repeatedly, that’s a signal to reduce query cost or shift the workload to the Bulk Operations API (more on this below), not just to retry harder.
REST Admin API: The Older, Simpler Call-Limit Bucket
Plenty of existing integrations, and some newer ones for endpoints not yet available in GraphQL, still use the REST Admin API. Its throttling is more predictable because every call has an equal, fixed cost regardless of what it fetches.
- The bucket has a maximum call capacity and refills at a steady rate of calls per second.
- Every response includes an
X-Shopify-Shop-Api-Call-Limitheader showing your current usage against the bucket (for example,32/40), so you can see how close you are without waiting to be throttled. - When you exceed the limit, Shopify returns an HTTP
429 Too Many Requestsresponse with aRetry-Afterheader telling you how many seconds to wait before trying again.
The practical contrast for developers: REST throttling is easy to reason about (count your calls, watch the header) but doesn’t reward efficient querying, a call that fetches one field costs the same as one that fetches fifty. GraphQL rewards precise, well-designed queries but requires you to actually understand cost calculation to avoid surprises. For any new integration work, GraphQL is generally the better foundation specifically because it lets you control cost directly.
Design Patterns That Keep You Under the Limit
None of the above matters much if your integration architecture doesn’t account for it. These are the patterns that consistently keep custom Shopify integrations stable under real production load.
1. Request batching and query efficiency
Combine what would be several REST calls into a single, well-structured GraphQL query using aliases and nested connections, rather than firing off many small requests in sequence. One moderately complex query is almost always cheaper, in both cost points and latency, than ten simple ones. The trade-off is knowing when a query has become expensive enough that it’s better split or moved to a different pattern entirely, which is where reading the extensions.cost block on real traffic pays off.
2. Bulk Operations API for large data jobs
For anything touching a large slice of a store’s data, a full product catalogue export, a historical order backfill, a one-off data migration, don’t loop paginated queries against the standard rate limit at all. Shopify’s Bulk Operations API runs a single query or mutation asynchronously against the entire dataset, processes it outside the normal cost bucket, and delivers the result as a downloadable JSONL file when complete. It’s slower to get results (you poll for completion rather than getting an instant response) but effectively removes rate limiting as a concern for large jobs. This is the correct tool any time you catch yourself writing a loop that pages through thousands of records against the regular API.
3. Webhook-driven sync instead of polling
Polling the API on a schedule to check “has anything changed?” is one of the most common causes of avoidable rate-limit pressure, and it doesn’t scale, as a store’s catalogue or order volume grows, so does your polling cost, for no added value most of the time. Subscribing to Shopify’s webhooks (order creation, product updates, inventory level changes, fulfilment events) and reacting to pushed events instead is both more efficient and closer to real-time, since you’re only doing work when something has actually changed.
4. Exponential backoff and retry logic
Even a well-designed integration will occasionally get throttled, particularly during bursts (a bulk price update, a flash sale). Retry logic should back off exponentially, wait, then wait longer, then longer again, rather than retrying immediately in a tight loop, which just re-triggers the same throttle. Respect the Retry-After header on REST 429 responses and the throttleStatus.restoreRate on GraphQL where available, rather than using a fixed guess.
5. Monitoring API usage in production
Log the cost and throttle status of every request, and alert when usage against the bucket regularly runs high, don’t wait for customer-facing errors to find out an integration is close to its ceiling. This is especially important for integrations that will run against stores of varying sizes; a pattern that’s fine on a 500-SKU store can behave very differently on a 50,000-SKU one.
A Practical Checklist for Designing Rate-Limit-Aware Integrations
Use this at the design stage, before you write the integration, not after it starts throwing errors in production:
- [ ] Confirm which API you’re using (GraphQL vs REST) and read the current cost/limit documentation for that specific API version.
- [ ] Log
extensions.cost(GraphQL) or theX-Shopify-Shop-Api-Call-Limitheader (REST) on every request from day one. - [ ] Identify any workflow that touches a large or unbounded number of records, and route it through the Bulk Operations API rather than paginated calls.
- [ ] Replace scheduled polling with webhook subscriptions wherever Shopify offers a relevant webhook topic.
- [ ] Build exponential backoff with jitter into your retry logic, and handle
THROTTLEDand HTTP 429 explicitly rather than as generic errors. - [ ] Load-test against a store with realistic data volume, not just a near-empty development store.
- [ ] Set up alerting on sustained high bucket usage, not just on outright failures.
Getting all of this right the first time, especially the cost calculations and the decision about when to use Bulk Operations versus standard queries, is exactly the kind of technical groundwork that determines whether an integration stays reliable as a store scales. If you’re scoping a custom sync, a middleware layer between Shopify and an ERP, or any integration handling non-trivial data volume, it’s worth having that architecture reviewed before you build against it. This is the kind of work our Shopify integrations team does regularly, designing the request patterns up front so rate limits are a non-issue rather than a production incident.
Frequently Asked Questions
Does Shopify Plus get higher API rate limits than standard Shopify?
Yes, Shopify Plus stores generally receive a larger bucket size and faster restore rate on both APIs, reflecting the higher API demand typical of enterprise integrations. The exact multiplier has changed over Shopify’s API versions, so confirm the current figures for your target API version in Shopify’s developer documentation rather than assuming a fixed ratio.
Should new integrations use REST or GraphQL?
For new development, GraphQL is generally the better choice. It lets you request exactly the fields you need and gives you direct visibility into query cost via the extensions.cost object, which makes it far easier to design around the rate limit deliberately. REST remains relevant for a handful of endpoints not yet available in GraphQL and for maintaining existing integrations.
What’s the difference between requestedQueryCost and actualQueryCost?
requestedQueryCost is Shopify’s estimate of a query’s cost calculated before execution, used to decide whether to throttle the request. actualQueryCost is the real cost after the query ran, which can be lower if fewer items were returned than the maximum requested. Both are useful for tuning queries over time.
Can I avoid rate limits entirely by using the Bulk Operations API for everything?
No, Bulk Operations is designed for large, asynchronous jobs like full data exports or backfills, not for real-time or interactive requests. It trades immediate response time for the ability to process very large datasets outside the standard cost bucket. Everyday reads and writes still belong on the standard GraphQL or REST endpoints.
Why is my integration getting throttled even though my traffic looks low?
It’s usually query cost, not request count. A single GraphQL query with deeply nested connections (orders, each with line items, each with variant data) can carry a high cost even if you’re only sending one request per minute. Check the extensions.cost block on the actual query in question rather than assuming low request volume means low cost.
Ready to Get Your Integration Architecture Right?
Rate-limit issues are almost always design problems, not bugs, and they’re far cheaper to solve on paper than in a production incident. If you’re planning a custom Shopify integration and want the request patterns, batching strategy, and bulk data handling reviewed by people who build these regularly, book a call with our team and we’ll talk through your specific data volumes and sync requirements.