Home
Guide

Errors & Responses

v2 status codes, response envelope, and validation behavior.

Success envelopes

Successful reads and writes return the action's data payload directly, with no wrapper. The HTTP status code is the contract:

CodeWhen
200 OKRead completed
202 AcceptedWrite accepted — this is the success code for all writes. Synchronous writes return the updated resource snapshot; bulk/async actions return {job_id, status_url} — see Bulk & Async Jobs
204 No ContentAction succeeded with no payload (rare)
207 Multi-StatusReserved for bulk actions reporting per-item failures
Note

v2 writes return 202 Accepted even when the local database write committed synchronously — Shopify-side sync (metafields, selling-plan groups, order mutations) generally completes in the background. The response body reflects the committed local state.

Error envelope

All non-2xx responses share one shape:

{ "errors": ["one or more human-readable strings"] }

errors is always an array of strings. Even single-error responses come back as a one-element array.

Status codes

CodeMeaningTypical cause
400 Bad RequestMalformed JSONMissing/invalid JSON body
401 UnauthorizedMissing or invalid X-Auth-TokenWrong key or no header
404 Not FoundUnknown action path, or resource not foundTypo in path, deleted offer, expired job ID
409 ConflictState-machine refused the transitione.g., release on a cancelled order, set_collection on a non-collection offer
422 Unprocessable EntitySchema validation failureMissing required field, bad enum value, over a bulk cap
429 Too Many RequestsRate limit exhaustedSee Rate Limits
500 Internal Server ErrorServer bug — please reportUnexpected exception

404 — unknown path vs missing resource

An unregistered verb+path combination:

{ "errors": ["No v2 action registered for POST /preorders/offers/nope"] }

A registered path addressing a resource that doesn't exist in this shop:

{ "errors": ["Preorder offer not found: 5b2e..."] }

If you hit the first kind, ask the API itself: GET /help?prefix=... or GET <scope>/help returns the registered actions under any scope — see Dispatch & Discovery.

409 — state-machine conflict

State-mutating actions check whether the transition is legal before running. If not, they return 409 with a one-line reason:

{ "errors": ["Cannot release: order is already cancelled."] }

Some 409s can be overridden with force: true on the request body (e.g., releasing an order before the balance is paid). The action's notes in the /help manifest will say so.

422 — validation

Schema breaches surface as one error per field:

{ "errors": ["mode must be one of: auto, manual"] }

Caps are validation errors too:

{ "errors": ["Too many order_ids: 1200 (max 1000 per request)"] }

Idempotency

v2 does not support request-level idempotency keys (yet). Practical rules:

  • Reads are safe to retry.
  • Lifecycle actions are idempotent in their effect (enabling an already-enabled offer is a no-op success).
  • Bulk submissions create a new job each time — don't auto-retry without checking the previous job_id first.
  • Creates are not idempotent — duplicate submissions create duplicate resources.

Stable error messages

We treat the human-readable strings in errors as part of the API contract for cross-cutting messages — "X is required", "Y must be one of: ...". Resource-specific 409 reasons may evolve over time; don't pattern-match on those.