Errors
A stable machine code to branch on, and prose for the person reading the log.
Errors carry the human-readable reason at meta.message and the code at errors.code. Branch on the code, never on the message — the message is written for people and may be reworded at any time.
{ "meta": { "message": "This API key does not have the 'offers:read' scope." }, "errors": { "code": "insufficient_scope", "required_scope": "offers:read" }}Status codes#
| Status | Means |
|---|---|
400 | Malformed request — usually a body that is not JSON. |
401 | The key is missing, unknown, revoked or expired. |
402 | The plan does not include the API, or the account's lead quota is spent. |
403 | The key is valid but lacks the scope named in the body. |
404 | No such record in this account. Never a hint that it exists elsewhere. |
409 | A conflict you can act on — a reused idempotency key, or a promotion in use. |
413 | The payload is over a size limit. Nothing was written. |
422 | The request is understood and refused. Every problem is listed. |
429 | Rate limited. Read Retry-After. |
503 | The API is temporarily switched off. |
Rejected writes list everything#
A write is validated as a whole and applied as a whole. One bad row rejects the request with 422 and every problem it found, so one round trip tells you all of it — and nothing in the request was applied. A half-applied import would leave you quoting a mix of two catalogues, which is harder to spot and harder to undo than an import that plainly did not happen.
{ "meta": { "message": "2 problems with this request. Nothing was written." }, "errors": { "code": "invalid_request", "problems": [ "matrices[0].prices[1] must hold 4 price(s), 3 given.", "matrices[2].code may contain letters, digits, underscore and hyphen only." ] }}Codes worth branching on#
| Code | Status | What to do |
|---|---|---|
insufficient_scope | 403 | Add the scope named in required_scope to the key. It cannot be widened in flight. |
plan_upgrade_required | 402 | The account is on Starter. Nothing to retry — tell the person, not the machine. |
quota_reached | 402 | The plan's lead allowance for this period is spent, or billing is locked. Stop; do not retry. |
idempotency_key_reused | 409 | That key already carried a different body. Pick a new key. |
idempotency_in_progress | 409 | Your own earlier request is still running. Retry it unchanged in a moment. |
promotion_in_use | 409 | The delete would cost you the record of which promotion won those offers. Repeat with ?force=1 if you mean it. |
nothing_to_apply | 422 | Applying a promotion whose rate is 0 — which is how we record one taken off by hand. |
invalid_request | 422 | Read errors.problems. Every one of them has to be fixed. |
Retrying#
Retry 429 after Retry-After, and 503 with a backoff. Do not retry 4xx — a scope does not appear because you asked twice. When you do retry a write, send the same Idempotency-Key you sent the first time.