Rate limits
Every GraphQL request is assigned a numeric cost before it is executed: the complexity score of its query. Each merchant has a budget that refills at a steady rate up to its maximum bucket size. As long as a request fits into the currently available budget, it is executed and its cost is deducted; otherwise it is rejected with a THROTTLED error.
Rate limits are tracked per merchant, not per API token. Creating additional tokens does not raise the budget.
Budgets by plan
The API is available on the Professional and Enterprise plans. Each of those plans has a bucket size (the maximum budget available at any moment) and a restore rate (how quickly the budget refills, in points per second). Short bursts are allowed up to the bucket size; sustained throughput is capped by the restore rate.
| Plan | Restore rate | Bucket size |
|---|---|---|
| Professional | 500 points/s | 5,000 points |
| Enterprise | 2,000 points/s | 20,000 points |
A merchant on the Professional plan, for example, can burst up to 5,000 points of request cost from a full bucket and sustain about 500 points/s of cost on average once the bucket has been drawn down.
Limits per request
Independent of the bucket, every request has to stay within two limits:
- Complexity: the complexity score of the query may be at most 5,000.
- Depth: fields may be nested at most 15 levels deep.
A query above one of these limits is rejected during validation with a BAD_REQUEST error, before any cost is charged. A request whose cost is larger than the merchant's bucket size can never fit into the bucket and is rejected with the code MAX_COST_EXCEEDED.
In both cases, retrying with the same shape fails again. Split the operation into smaller pages or query a smaller set of fields.
Inspecting the current budget
Every response to a request that passed validation carries the state of the bucket in three headers:
| Header | Content |
|---|---|
X-RateLimit-Limit | The bucket size of the merchant. |
X-RateLimit-Remaining | The points left in the bucket after this request. |
X-RateLimit-Reset | The seconds until the bucket is full again. |
When the response contains data, it also reports the request's cost and the bucket state in the GraphQL extensions object:
{
"extensions": {
"cost": {
"requestedQueryCost": 350,
"throttleStatus": {
"maximumAvailable": 5000,
"currentlyAvailable": 4650,
"restoreRate": 500
}
}
}
}
requestedQueryCost— the cost calculated for the request before execution and charged against the bucket.throttleStatus.maximumAvailable— the bucket size for this merchant.throttleStatus.currentlyAvailable— points remaining in the bucket right after this response.throttleStatus.restoreRate— points per second added to the bucket until it is full.
Clients that batch synchronization work can use currentlyAvailable and restoreRate, or the X-RateLimit-* headers, to pace requests instead of relying on retries.
Handling throttling
When a request does not fit into the current budget, the API returns a GraphQL error with the THROTTLED code and a retryAfter field in the error's extensions. The response also carries the request's cost and the bucket state in extensions.cost:
{
"errors": [
{
"message": "Throttled. Retry in 1.7s — your requested cost (1200) exceeds currently available points (350).",
"extensions": {
"code": "THROTTLED",
"retryAfter": 1.7
}
}
],
"extensions": {
"cost": {
"requestedQueryCost": 1200,
"throttleStatus": {
"maximumAvailable": 5000,
"currentlyAvailable": 350,
"restoreRate": 500
}
}
}
}
retryAfter is the number of seconds the client should wait before sending the next request, at least one second. The Retry-After header carries the same wait rounded up to whole seconds. Back off until that time has elapsed before retrying.
Reducing request cost
Cost grows with the number of items returned by list fields and with the depth of nested connections, so the most effective ways to lower cost are:
- Request only the fields the integration needs; drop nested connections that are not used.
- Use smaller page sizes on list fields, and paginate explicitly with
first+after. - Cache stable objects such as warehouses, bin locations, and suppliers locally where possible.
- Prefer incremental synchronization by filtering on fields such as
updatedAtwhen the relevant type exposes them.