Skip to content
All blog
Shopify API Integration Development

Shopify API rate limits and query cost

Masni 7 min read

Rate limiting is where Shopify integrations meet reality. A sync that works perfectly against a development store with fifty orders behaves entirely differently against a store with a real catalogue and three years of history.

The reason is that Shopify's GraphQL limit is not a request count. It is a budget, and every query spends from it according to how much work it asks for.

How the budget works

Think of a bucket that holds a certain number of points and refills continuously at a fixed rate.

Each query is assessed a cost before it runs, based on how many objects it could return and how deeply it nests. That cost is deducted. If the bucket has enough, the query runs; if not, it is rejected and you wait for the bucket to refill.

Two things follow immediately.

Small, precise queries are cheap. Asking for six fields on one order costs very little and you can do it often.

Broad, nested queries are expensive even when they return little. The cost is based on what the query could return, so requesting a hundred orders with all their line items and each line's variant is expensive whether or not those orders exist.

The allowance scales with the store's Shopify plan, so the same integration has more headroom on a higher plan than on an entry-level one. That is worth knowing before promising a sync frequency to a merchant whose plan you have not asked about.

The two limits that catch people out

The refill rate governs sustained throughput. Over an hour, how much data can you move? This is the one that decides whether a full historical sync takes twenty minutes or two days.

The per-query ceiling governs any single request. A query above the maximum cost is rejected outright, no matter how empty the bucket is or is not. This one is a design constraint rather than a throughput constraint, and it is the one that surprises developers, because the fix is not waiting — it is restructuring the query.

A query that walks orders, then line items, then variants, then inventory across locations can exceed the ceiling on its own. It has to be split into several shallower queries, which is more code and more round trips and unavoidable.

The response tells you where you stand

Every GraphQL response carries the current state of your budget alongside the data: the actual cost of the query that just ran, the requested cost, and how much of the allowance remains.

This is the single most useful thing in the whole area and it is routinely ignored. An integration that reads it can slow itself down before being rejected. One that ignores it discovers the limit by being refused, retries, and is refused again.

Requested cost and actual cost differ, usually because you asked for a page of a hundred and there were only twelve. Shopify assesses on the request and refunds the difference, which means conservative page sizes are less punishing than they look — but sizing pages to what you expect rather than to the maximum is still the cheaper habit.

Designing a sync that does not fight the limit

Read the budget from every response and adapt. Slow down as the remaining allowance falls, rather than running flat out until refused. This is a few lines of code and it is the difference between a sync that completes and one that spends its time retrying.

Back off properly when rejected. Wait, with increasing intervals, rather than immediately retrying. An immediate retry is guaranteed to fail again and it consumes the refill you are waiting for.

Request only the fields you use. Every field costs. A query built by copying an example and deleting nothing is paying for data that goes straight into the bin.

Page conservatively. Smaller pages, more of them, is usually faster in practice than large pages that are throttled or rejected.

Never poll for change. Repeatedly asking whether anything has happened is the most expensive possible way to find out. Use notifications for freshness and a periodic reconciling pull for completeness — see Shopify webhooks and why they are not enough.

Use bulk operations for volume. Anything large — a full historical import, a whole catalogue — belongs in a bulk operation, which runs asynchronously and outside the normal budget. Trying to move a store's entire history through ordinary queries is the classic mistake, and it is slow rather than impossible — see Shopify bulk operations and when to use them.

What this means for a merchant, not a developer

Three practical consequences worth understanding even if you never see the code.

Real-time is a claim to interrogate. No integration reads your store continuously. Something changes, a notification fires, the integration fetches the change. Between those points there is a delay, and near-real-time is the honest description.

Initial sync takes longer than you expect. Bringing in years of history is bounded by the refill rate, and it is a one-off cost measured in hours or days rather than minutes. Plan the go-live around it rather than being surprised by it.

Your Shopify plan affects your integration's headroom. Not dramatically for a typical store, and noticeably if you are running a large catalogue with frequent updates and several channels attached.

None of this is a defect. It is a shared platform protecting itself, and a well-built integration lives comfortably inside it — see what it takes to build a Shopify connector.

Common questions

How does Shopify limit API usage?

Not by counting requests. Each GraphQL query is assessed a cost based on how many objects it could return and how deeply it nests, and that cost is deducted from an allowance that refills continuously. Small precise queries are cheap and can be made often, while broad nested queries are expensive even when they return very little data.

Why is a query rejected even when I am not making many requests?

Because there is a separate ceiling on the cost of any single query. A request above that maximum is refused outright regardless of how much allowance remains, so the fix is restructuring the query into several shallower ones rather than waiting or slowing down.

How should an integration handle Shopify throttling?

By reading the budget state that every response carries — the query's actual cost, its requested cost and the remaining allowance — and slowing down as headroom falls rather than running flat out until refused. When a request is rejected, it should back off with increasing intervals, since an immediate retry consumes the refill it is waiting for.

Why does the initial Shopify sync take so long?

Because moving years of history is bounded by how fast the allowance refills, not by how fast your code runs. It is a one-off cost usually measured in hours rather than minutes, and it is substantially faster through bulk operations, which run asynchronously outside the normal request budget.


Related: Shopify bulk operations and when to use them · why Shopify moved to GraphQL · Shopify webhooks and why they are not enough


See what you could build

Start a free trial and describe what your business needs in plain language — SmartB Studio builds the module for you.

Start free trial
Get started

No credit card · Cancel anytime · Your data stays yours