Skip to main content

Error codes

This page covers the public DPP API (GET /api/dpp/*, GET /api/health). Error responses include a code field; use code, not the HTTP status, as the primary basis for error handling.

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 web flow with no JSON API, so it has no error codes — see PoLI access.

Public DPP API error reference

HTTP StatusCodeMeaningRecommended action
404PRODUCT_NOT_FOUNDNo published product found for the given slugVerify the slug. Only published DPPs from active companies are served; drafts, deprecated, and unpublished products return 404.
410PRODUCT_WITHDRAWNThe product's DPP has been withdrawn (EU Battery Regulation Art. 76)Surface the withdrawal to your user. The response body carries the reason and any successor link. Do not treat as a transient error.
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 errorLog the X-Request-Id header and contact support if it persists. Wait at least 30 seconds before retrying.
503SERVICE_UNAVAILABLEThe platform or a dependency is temporarily unavailableRetry with exponential backoff. Check status.traceable.digital.

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."
}
}

A withdrawn product (410) includes the withdrawal context:

{
"error": {
"code": "PRODUCT_WITHDRAWN",
"message": "This battery passport has been withdrawn by the manufacturer",
"reason": "Recall",
"withdrawnAt": "2026-05-10T09:00:00.000Z",
"successorUrl": "/dpp/replacement-product-slug"
}
}

Error handling best practices

Use code, not HTTP status

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

if (data?.error?.code === 'PRODUCT_WITHDRAWN') {
// show the withdrawal notice and any successor link
}

Only retry transient errors

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

// 404 PRODUCT_NOT_FOUND and 410 PRODUCT_WITHDRAWN are deterministic — do not retry.

Handle 410 explicitly

A withdrawn DPP returns 410, not 404. An integration that treats every non-200 as "not found" will hide the recall. Branch on 410 and surface the reason. See DPP endpoints.

Log the X-Request-Id for 5xx errors

const requestId = response.headers.get('X-Request-Id');
if (response.status >= 500) {
logger.error('Traceable API server error', { requestId, status: response.status });
// Include requestId when contacting support
}