Errors and rate limits
The error shape every API v2 action returns, what each status code means, and the 40-points-per-minute rate limit with the 429 response.
Last updated
Errors
Section titled “Errors”Every failed request returns a JSON body with an errors array of plain sentences, and a status code that says what kind of failure it was:
{ "errors": ["access.download_limit_count must be an integer of at least 0"], "field_errors": { "access.download_limit_count": ["must be an integer of at least 0"] }}Some errors add machine-readable keys so your code does not have to read the sentence.
| Status | Meaning | What to do |
|---|---|---|
401 |
The API key is missing, invalid, expired or revoked (code says which). |
Check the X-Auth-Token header; an expired or revoked key needs a new one. See Authentication. |
404 |
The record does not exist for this store, or the path is not an action. | Check the id and the path. |
409 |
The request conflicts with the current state, or it needs a confirmation. | Read the message; do not retry blindly. A code of confirmation_required means a preview. |
422 |
Validation failed. | Read field_errors (dotted field path to messages), then fix the request. |
429 |
Rate limited. Nothing was done. | Wait retry_after seconds and send the same request again. |
502 |
Shopify or the email service did not finish the request (code: "shopify_error"). Nothing was changed. |
Try again shortly. |
Unknown parameters
Section titled “Unknown parameters”Each action accepts exactly the fields in its schema. If you send one it does not take, the request is rejected before anything runs, and the response tells you what it does accept:
{ "errors": ["Unknown parameter for GET /orders: state. It accepts: page, per_page, search, financial_status."], "code": "unknown_parameters", "unknown": ["state"], "accepted": ["page", "per_page", "search", "financial_status"]}Fix the call; retrying it unchanged will fail the same way.
Rate limits
Section titled “Rate limits”Requests are limited per store, shared by all of its API keys, with a points budget:
| Budget | 40 points per 60 seconds |
A read (GET) |
1 point |
A write (POST, PUT, PATCH, DELETE) |
2 points |
The budget belongs to the key, so everything using it shares it: your scripts, your integrations and any AI assistant connected with the key. It is also shared with API v1.
Every response carries three headers so you can see where you are:
| Header | Meaning |
|---|---|
X-RateLimit-Limit |
Points per window (40). |
X-RateLimit-Remaining |
Points left in the current window. |
X-RateLimit-Reset |
Unix time, in seconds, when the window ends. |
When you hit the limit
Section titled “When you hit the limit”A 429 means the request was not processed. The response has a Retry-After header and the same information in the body:
{ "errors": [ "You have exceeded the rate limit of 40 points per 60 seconds. Read requests cost 1 point(s) and write requests cost 2 point(s). Please retry after 12 second(s)." ], "code": "rate_limited", "retry_after": 12, "error": "Rate limit exceeded", "message": "You have exceeded the rate limit of 40 points per 60 seconds. Read requests cost 1 point(s) and write requests cost 2 point(s). Please retry after 12 second(s)."}Wait retry_after seconds, then send the same request again. It is safe, because nothing happened the first time.
async function call(url, options, attempts = 5) { const response = await fetch(url, options); if (response.status !== 429 || attempts === 0) return response; const wait = Number(response.headers.get('Retry-After') || 5); await new Promise((resolve) => setTimeout(resolve, wait * 1000)); return call(url, options, attempts - 1);}Staying under the limit
Section titled “Staying under the limit”- Fetch lists, not records. One
GET /orders?per_page=200costs 1 point. Two hundredGET /orders/:idcalls cost 200. - Use the bulk actions.
POST /licences/bulk_deleteandPOST /assets/bulk_deletedo in one write what a loop of deletes does in many. - Read
X-RateLimit-Remainingand slow down before you reach zero, rather than waiting for a429.
Thanks for letting us know.
Still stuck? Ask Quinn or chat with support