Skip to main content

Error codes

This page covers the public DPP API (GET /api/dpp/* and the GS1 Digital Link resolver at /01/...). Error responses include a code field; use code, not the HTTP status, as the primary basis for error handling. The one exception is the per-IP edge limiter, described below.

Other surfaces

The Integration API (/api/v1) has its own, larger error catalogue with a different envelope: see Integration API: Errors and conventions. The BMS endpoint has its own error responses: see BMS Endpoints. PoLI access is a browser flow with tokenised links and two same-origin browser endpoints, none intended for programmatic use: see PoLI access.

Public DPP API error reference​

HTTP StatusCodeMeaningRecommended action
300MULTIPLE_PRODUCTSMore than one published product carries the GTIN on /01/...Add /21/{serial} or /10/{batch}, or follow a link in choices
400INVALID_VERSIONversion in /api/dpp/{slug}/v/{version} is not a positive integerSend an integer of 1 or more
404PRODUCT_NOT_FOUNDNo publicly served product found for the given slugVerify the slug. Drafts, submitted products, regulator-revoked products, test products and products that were never published return 404. Published passports stay online even after withdrawal, end of production or a manufacturer suspension; see Archived passports.
404VERSION_NOT_FOUNDThe product is served but has no snapshot at that versionDo not retry.
404CONFIG_NOT_FOUNDThe product has no usable category configurationContact support.
429RATE_LIMITEDToo many requests in the current windowWait and retry with exponential backoff. Honour the Retry-After header. See Rate Limiting.
500INTERNAL_ERRORAn unexpected server errorWait at least 30 seconds before retrying, and contact support if it persists, quoting the time of the request and the slug.
500MAPPING_FAILEDThe JSON-LD document could not be built (/api/dpp/{slug}/jsonld)Retry later and contact support if it persists.
503SNAPSHOT_INTEGRITY_FAILUREThe stored snapshot could not be verified, so nothing is servedRetry after the Retry-After interval (300 seconds). The platform team is alerted automatically.

Error response structure​

Error responses carry a machine-readable code and a human-readable message. Branch on code, never on the message text.

{
"error": {
"code": "RATE_LIMITED",
"message": "Too many requests. Try again later."
}
}

The per-IP edge limiter answers 429 with {"error":"Too many requests"} and no code. Branch on the status for that case.

A withdrawn, deprecated or suspended-manufacturer passport is not an error. It answers 200 with an availability object and the header X-DPP-Availability: archived:

{
"availability": {
"status": "archived",
"reasons": ["withdrawn"],
"withdrawal": {
"reason": "Recall",
"withdrawnAt": "2026-05-10T09:00:00.000Z",
"successorUrl": "/dpp/replacement-product-slug"
}
}
}

(Abbreviated; the passport fields sit beside it, and the full object is in Archived passports.)


Error handling best practices​

Use code, not HTTP status

const res = await fetch(`https://dpp.traceable.digital/api/dpp/${slug}`);
const data = await res.json().catch(() => ({}));

if (!res.ok && data.error?.code === 'RATE_LIMITED') {
// wait for Retry-After seconds, then retry
}

if (res.headers.get('X-DPP-Availability') === 'archived') {
// show data.availability.notice and any successor link
}

Only retry transient errors

const RETRYABLE = new Set(['RATE_LIMITED', 'SNAPSHOT_INTEGRITY_FAILURE', 'INTERNAL_ERROR']);

// 404 PRODUCT_NOT_FOUND is deterministic, so do not retry.

Check for archived passports

From platform release v0.99.20 a withdrawn DPP returns 200 with an availability block, not 410. Earlier releases returned 410 PRODUCT_WITHDRAWN, and a client written for that must change: branch on the X-DPP-Availability: archived header or the availability object and surface the reason. See DPP endpoints.

Log what identifies the request

The public DPP routes return no request identifier. Log the time of the request, the slug and the status when you contact support. For /api/v1 requests, log the X-Traceable-Request-Id response header, which is also returned as requestId in the error body.

if (response.status >= 500) {
logger.error('Traceable API server error', {
slug,
status: response.status,
at: new Date().toISOString(),
requestId: response.headers.get('X-Traceable-Request-Id'), // /api/v1 only
});
}
Was this page helpful?