Error code catalogue
Every error returns a JSON body shaped like:
{
"error": "<machine_code>",
"message": "<human-readable summary>",
"details": { "field": "gtin", "value": "0073-bad-input" }
}
details is optional and varies per error. Code (error) is stable
— machine consumers should switch on it. Message is human-friendly
and may change.
Authentication / authorisation
authentication_required — 401
No Authorization: Bearer … header. Add a token from
Settings → API.
authentication_invalid — 401
Token rejected. The token may have been rotated, expired, or mistyped. Generate a fresh one from Settings → API.
forbidden — 403
Token is valid but scoped to a different tenant than the one named in the path or body. Each tenant has its own tokens — a Brand A token can never read Brand B data.
Validation
validation_failed — 400
Required field missing or malformed. details.field says which key
and details.value shows what you sent.
Common triggers:
- GTIN not 13 or 14 digits
granularitynot one ofmodel/batch/itemdpp_statusinvalid transition (e.g.draft → archivedstraight)- Country / currency / language not ISO-coded
conflict — 409
The combination of GTIN + batch + serial + CPV already exists. Either update the existing DPP via PATCH or pick different identifiers.
not_found — 404
DPP / tenant / file does not exist. May also mean the resource exists but is scoped to another tenant — we return 404 rather than 403 so attackers can't enumerate IDs.
Quotas
quota_exceeded — 429
Plan quota or rate limit hit. The response includes a Retry-After
header in seconds. Headers X-RateLimit-Limit,
X-RateLimit-Remaining, X-RateLimit-Reset are present so the
client can self-pace.
Custom-tier plans bump limits — contact support to upgrade.
plan_locked — 402
Feature not available on the current plan (e.g. custom domains on Light tier, AI Suggest on Free tier). Upgrade required.
Server-side
internal_error — 500
Bug on our side. The response body includes a request_id — pass
that to support so we can pull the trace.
upstream_unavailable — 502
A downstream dependency (PIM, Stripe, AI provider) was unreachable. Retry with exponential backoff. The error usually self-clears.
maintenance — 503
Scheduled maintenance window. The Retry-After header shows when
to retry. Posted in advance on status.dppagent.com.
Resolver-specific
withdrawn — 200 with X-DPP-Status: withdrawn
Not technically an error. The DPP exists but is marked withdrawn —
consumer still sees the passport but with a "this product is no
longer sold" notice. Header X-DPP-Status flips so integrators
can branch.
archived — 410 Gone
The DPP was archived. Consumer page renders a "this product has been retired" notice. The archived flag is permanent — once archived, always archived.
Webhook delivery
Webhooks deliver with retries. After 5 failed attempts the event moves to dead-letter. Failures show in the dashboard under Settings → API → Webhooks.