Skip to content

Public API v2: Errors & retries

Branch on errors.code; message is written for people and may change.

HTTP Code Cause Retry?
400 INVALID_BODY The body is not valid JSON. No
400 DOMAIN_MISSING No X-Shop-Domain. No
401 ACCESS_KEY_MISSING No X-API-Key. No
401 ACCESS_KEY_INVALID Unknown key, a key of another store, a v1 key, or an unknown domain. No
401 ACCESS_KEY_REVOKED The key was revoked, or the store moved to a plan without Public API. No
403 AUTHORIZATION_DENIED The key lacks a scope (details names it), the request comes from an IP outside the key’s allowed IPs (details names the IP), or the plan does not include the module. No
404 RESOURCE_NOT_FOUND No such path, or the id does not exist, was deleted or belongs to another store. No
409 RESOURCE_CONFLICT Another bulk request or upload of the store is running; a request with the same Idempotency-Key is still running; a price list is being updated by the app. Yes, after a few seconds
413 PAYLOAD_TOO_LARGE Body over 10 MB. No — split it
422 VALIDATION_ERROR A field is wrong (details lists each one), the request costs more than 600 points, or the store would pass 10,000 rules of the module. No — fix the request
429 RATE_LIMIT_EXCEEDED Out of points, or more than 10 requests of the store at once. Yes, after Retry-After
500 INTERNAL_ERROR Unexpected error on our side. Yes, with backoff
502 UPSTREAM_UNAVAILABLE Shopify did not answer, or did not save during an upload. Yes, with backoff

A 422 lists every wrong field in details: field is the path in the request (rules.0.discount_value, form.7.options), message says what is wrong, and code tells the kind of problem:

details[].code Meaning
invalid_type Wrong type, or a required field is missing.
invalid_value Not one of the allowed values of an enum.
invalid_format Wrong format: a date without Z, an id that is not numeric, a decimal with 3 places.
too_small / too_big Under or over a limit: length, number, number of items.
unrecognized_keys A field the endpoint does not take; field is root or the object that holds it.
custom A business rule, explained in message — e.g. a percentage over 100, overlapping ranges, a duplicate code.
A cursor that is not valid 422
{
"success": false,
"status": "error",
"errors": {
"code": "VALIDATION_ERROR",
"message": "Validation failed",
"details": [
{
"field": "cursor",
"message": "Invalid cursor; pass next_cursor or prev_cursor as received"
}
]
},
"meta": {
"timestamp": "2026-10-21T09:34:53.200Z",
"version": "v2",
"request_id": "4b9ec3f9-eb99-4044-80c1-8ae53d918b15",
"rate_limit": {
"cost": 10,
"limit": 600,
"remaining": 590,
"restore_rate": 10
}
}
}
  • Retry only the codes marked yes. Fix the request for the others.
  • On 429, wait the seconds in Retry-After, then send the same request again.
  • On 500 and 502, retry with exponential backoff (1 s, 2 s, 4 s, 8 s …) and give up after about 5 tries.
  • Add an Idempotency-Key to POST requests you may retry, so a retry never creates a rule twice. See Bulk & idempotency.
  • PUT and DELETE are safe to retry as they are.
  • In a bulk response, retry only the elements whose result is error with a retryable code.