Skip to content

Errors

When a request fails, the body is an error envelope:

{
"error": {
"code": "NOT_FOUND",
"message": "Not found: VMAT-VAR-7f3d2c1a-9b4e-4c8d-a2f6-5e1b0d9c8a7f",
"retryable": false,
"requestId": "0f8c…"
}
}
  • retryable — whether retrying the same request can succeed. Retryable errors also include retryAfterMs and a Retry-After response header (in seconds).
  • action — sometimes present: a human-readable next step (e.g. on PLAN_REQUIRED).
  • requestId — also in the X-Request-Id header. Quote it in support requests.
codeHTTPMeaningRetry-After
UNAUTHORIZED401Missing, invalid, or revoked token—
PLAN_REQUIRED403The shop isn’t on the Enhanced plan (the REST API is Enhanced-only)—
FORBIDDEN_SCOPE403A PATCH with a read-only key—
INVALID_INPUT400A bad query parameter or body (the message names the problem), or a write against a Shopify-managed material (raw-materials endpoints)—
NOT_FOUND404Unknown path, or the id doesn’t exist—
CONFLICT409The request conflicts with the current state — a stale expectedUpdatedAt on a work-order write, a transition that isn’t allowed, or a build run that can’t start—
RATE_LIMITED429Per-shop rate limit reached✓
DO_OVERLOADED503The shop’s data store is busy✓
DO_UNAVAILABLE503The data store is temporarily unavailable✓
UPSTREAM_SHOPIFY502A Shopify-facing dependency failed (e.g. the live inventory read)if present
TIMEOUT504The request timed outif present
OUTPUT_TOO_LARGE413The response exceeded the size cap—
INTERNAL500An unexpected error—

An unsupported method returns 405 with code METHOD_NOT_ALLOWED and an Allow header — the only code outside the table above.

On the two /raw-materials/* write endpoints, a PATCH whose path id is not a VMAT-* id fails fast with 400 INVALID_INPUT:

{
"error": {
"code": "INVALID_INPUT",
"message": "Shopify-managed materials are read-only in API v1 — only virtual (VMAT-*) materials can be modified (got '45067340286136')",
"retryable": false,
"requestId": "…"
}
}

This rule is about materials. The three /work-orders/* writes take a work-order id and are not subject to it — see Work-order conflicts below.

The three work-order writes answer 409 CONFLICT rather than overwriting or half-applying:

  • A stale expectedUpdatedAt — someone changed the work order after you read it. Re-read it and re-send.
  • A transition that isn’t allowed from the work order’s current state; the response names the blockers.
  • A build run that can’t start — the response carries the specific reason: Shopify holds no inventory record for a line at that location, Shopify refused the push, the run would over-commit, or the run is empty. On any of them nothing was picked and nothing was pushed.

A 409 is never retryable as-is: fix the cause, or re-read and re-send with a current token.

Changing the unit of a material that is already used in BOMs or assembly bills is only allowed when a usable unit conversion exists. When it doesn’t, the app’s conflict response is passed through as 400 INVALID_INPUT — the message carries the app’s explanation of the conflict (which units were involved and why no conversion applies), so you can surface it directly to the user.

On 429 / 503, wait Retry-After seconds (or retryAfterMs) and retry the same request. Don’t fan out parallel requests against one shop — the data store is single-threaded per shop, so concurrency doesn’t go faster and can trip the rate limit.