Two limits apply to every request on /api/v1, over a rolling one minute window. They are counted differently, and the difference matters to how you scale. There are 68 resource families, each with its own family bucket. Products, orders, customers, inventory, pricing, discounts, webhooks and so on each count separately.

What “per API key” means for you

Your family budget follows the credential, not the machine. Running the same key from ten workers does not give you ten times the budget: all ten share one counter. This is deliberate, and it cuts both ways in your favour:
  • You get the budget you were told, on every host, and it does not change when you add or move servers.
  • Another company behind the same NAT, cloud egress range or corporate proxy cannot consume your budget, and you cannot consume theirs.
If you need more throughput, mint a second API key: each key carries its own family budget. Both still sit under the global ceiling below.
The global ceiling is still counted per IP address, because it applies before we know which key you are using. It exists to bound unauthenticated traffic, and it is set well above the family budget so a normal authenticated caller never meets it first.

Why both limits exist

The per-family budget stops one workload starving another. A catalogue sync hammering products cannot throttle an unrelated integration that only reads reviews. The global ceiling bounds the total you can extract, which per-family limits alone cannot do. Without it a caller simply multiplies their budget by the number of families, and 68 families at 600 each is 40,800 requests a minute.
The global ceiling is deliberately higher than the per-family figure. If they were equal the outer limit would always bind first and the per-family buckets would never do anything.

Budget headers

Every response, not only a refusal, tells you where you stand:
RateLimit-Reset is delta seconds, not a unix timestamp. Sleep for that many seconds. You do not need your clock to agree with ours. When the two limiters stack, the headers describe whichever one is binding: the smaller remaining wins. So RateLimit-Remaining: 3 means three more requests, whether that came from your family budget or the global ceiling. All three fields move together, so a response never describes half of one limiter and half of another.

Being refused

Over budget returns 429 with the standard envelope, plus Retry-After in whole seconds:
Retry-After is rounded up and never below 1. A Retry-After: 0 would tell a client to retry immediately into a refusal it is guaranteed to hit again, turning a rate limit into a hot loop against the thing it was meant to protect.

Backing off correctly

1

Honour Retry-After

Sleep for exactly the seconds given. Do not retry sooner, and do not use a fixed one second sleep, which will simply be refused again.
2

Add jitter

If you run several workers, add a random fraction of a second so they do not all wake at the same instant and re-trigger the limit together.
3

Watch RateLimit-Remaining and slow down before you are refused

Throttling yourself at a low remaining is cheaper than being refused and retrying. A sync loop that pauses at RateLimit-Remaining: 5 never sees a 429 at all.
4

Page with a larger limit

?limit=100 fetches five times as many records per request as the default 20, for the same one request against your budget. Most rate limit problems on a bulk read are really a paging size problem.

Failed authentication is limited separately

Repeated bad credentials are counted in their own bucket, independent of your request budget. A key that has been revoked or mistyped will start being refused for rate limiting rather than authentication if you retry it in a loop. Fix the credential rather than retrying it.

A 429 does not consume your idempotency key

If a write is refused for rate limiting, the Idempotency-Key you sent is released rather than spent. Retry the same operation with the same key once your window resets. See Idempotency.