Error codes
Every error response uses the envelope { "error": { "code", "message", "details"? } }. code is a stable, machine-checked identifier — branch on it, not on message (which is a human sentence and may change). Every response also carries an X-Request-ID header; include it when reporting an issue. The most common cross-cutting codes:
| Code | HTTP | When it occurs |
|---|---|---|
VALIDATION_ERROR | 422 | Request body or query params failed Zod validation; details lists the offending paths. |
TARGET_NOT_OWNED | 422 | A link or scope names a product, entity or asset that belongs to another organisation (for example one a supplier shares through a grant). Only your own records can be linked. |
COMPLIANCE_JUSTIFICATION_REQUIRED | 422 | Publishing a food product with mandatory EU FIC particulars missing, without a justification. details lists each missing field and locale; complete them, or publish again with a justification of at least 10 characters (granted buyers see it). |
REVISION_CHANGED | 409 | A save sent If-Match with an editToken that is no longer current: someone else changed the product or its draft since you opened it. Reload, then save again. |
REVISION_OPEN | 409 | Publishing an archived product that has an unpublished draft through POST /products/{id}/publish or PATCH status=published. Publish the draft (POST /products/{id}/revision/publish) instead — its gates then run on what goes live. |
REVISION_BASE_CHANGED | 409 | Publishing a draft whose fields were also changed on the published product since the draft started. details names each field; discard the draft or redo those fields. |
REVISION_EMPTY | 422 | Publishing a draft that no longer differs from the published product. |
GTIN_CLAIMED | 409 | Publishing a Digital Product Passport for a GTIN another organisation (or another of your products) already holds on the public GS1 resolver. Contact support to have a GTIN transferred. |
EMAIL_TAKEN | 409 | Changing a sign-in email to an address another account already uses. |
EMAIL_SUPPRESSED | 422 | The new sign-in address is on the delivery-suppression list after earlier bounces; a platform admin can release it. |
EMAIL_SSO_MANAGED | 422 | The account, or the old or new address’s domain, signs in through single sign-on, so the identity provider owns the email. |
EMAIL_CHANGE_LOCKED | 409 | An email change on this account was undone in the last 24 hours, so a new one cannot be started yet. |
SESSION_REVOKED | 401 | The token was issued before the account was signed out everywhere (its sign-in email changed or was reverted). Sign in again. |
UNAUTHORIZED | 401 | No valid credential (missing/expired JWT or API key). |
INVALID_CREDENTIALS | 401 | Login failed — email/password did not match. |
ROLE_CHANGED | 401 | The session token was issued before the user’s role or organisation changed. Sign in again to get a token that matches. |
INVALID_TOKEN | 401 | A one-time or scoped token (invite, verify, onboarding, refresh) is malformed, expired, or already used. |
FORBIDDEN | 403 | Authenticated, but the principal’s role lacks permission for this action (RBAC). |
NOT_FOUND | 404 | The resource does not exist, or is not visible to the caller under row-level security. |
CONFLICT | 409 | The request conflicts with current state (e.g. a duplicate unique value). |
INVALID_STATE | 422 | The target is not in a state that permits this transition. |
RATE_LIMITED | 429 | The per-plan sliding-window rate limit was exceeded; see the Retry-After header. |
ENTITLEMENT_EXCEEDED | 422 | A plan ceiling (products, seats, storage) is already at its limit; details names the entitlement. Only returned when entitlement enforcement is enabled. |
PLAN_UPGRADE_REQUIRED | 402 | The requested capability is not included in the organisation's plan; details names the entitlement. Only returned when entitlement enforcement is enabled. |
GONE | 410 | The resource once existed but has been withdrawn or offboarded. |
ORG_SUSPENDED | 403 | The organisation is suspended — reads and DSAR export stay open, mutations are frozen. |
INTERNAL_ERROR | 500 | An unexpected server error; internals are never leaked to the client. |
SANDBOX_READONLY | 403 | A sandbox API key attempted a mutating request; sandbox keys are read-only. |
INSUFFICIENT_SCOPE | 403 | The API key lacks the *:read/*:write scope this verb requires. |
SELECTION_TOO_LARGE | 422 | A bulk selection (e.g. enrichment ids) exceeds the per-job cap. |
CONDITION_NOT_MET | 422 | A workflow transition’s data condition evaluated false (fail-closed). |
QUALITY_EVALUATION_TOO_EXPENSIVE | 422 | A blocking quality profile’s regex() rules exceeded the time budget for one evaluation, so a mandatory rule could not be decided (publish gate, onboarding approve). Simplify the regex rules. |
TRANSFORM_FAILED | 422 | An onboarding mapping transform failed; approve is all-or-nothing with row-addressed details. |
GLN_INVALID | 422 | A supplied GLN failed check-digit or ownership validation. |
UNKNOWN_FRAMEWORK | 400 | A compliance framework code is not registered. |
GDSN_NO_CONNECTION | 400 | A GDSN publish was attempted with no active data-pool connection. |
SSO_FAILED | 401 | The SAML/OIDC assertion could not be validated or mapped to an org connection. |