Error codes
All Traceable API error responses include a code field containing a machine-readable string identifier. Use code, not the HTTP status, as the primary basis for error handling logic — the same HTTP status can represent different error conditions.
Full error reference
| HTTP Status | Code | Meaning | Common cause | Recommended action |
|---|---|---|---|---|
400 | VALIDATION_ERROR | Request body or query parameter failed validation | Missing required field, wrong data type, invalid enum value | Inspect details.fields in the response for per-field error messages. Fix the request before retrying. |
400 | INVALID_SLUG | The slug format is invalid | Slug contains uppercase letters, spaces, or special characters | URL-encode the slug and ensure it matches [a-z0-9-]+ |
401 | UNAUTHORIZED | No authentication credentials provided | Missing Authorization header on an endpoint that requires it | Add the Authorization: Bearer {key} header to the request |
401 | INVALID_API_KEY | The provided API key is not recognised or has been revoked | Key was deleted in the portal, or a typo in the key value | Check the key value in your environment config. Generate a new key if necessary. |
401 | EXPIRED_API_KEY | The API key has passed its expiry date | Key was created with an explicit expiry (if your account has key expiry policy enabled) | Generate a new key in Settings → API Keys |
403 | FORBIDDEN | Authentication succeeded but the authenticated entity lacks permission for this action | Attempting to verify a PoLI request that belongs to a different API key, or accessing a feature not enabled for your account | Verify you are using the correct API key for this action. Contact support if you believe the restriction is incorrect. |
403 | DPP_NOT_PUBLIC | The DPP exists but is not accessible to the requester | Attempting to access a DPP that has been unpublished or is in draft state | Check the product's status in the Traceable portal. Only published DPPs are accessible via the public API. |
404 | PRODUCT_NOT_FOUND | No published product found for the given identifier | Slug does not exist, the DPP has been deleted, or it has not yet been published. Also returned when a productSlug in a PoLI request refers to an unknown product. | Verify the identifier is correct. Check the product exists and is published in the Traceable portal. |
404 | REQUEST_NOT_FOUND | The PoLI access request ID does not exist | Incorrect requestId in a verify call, or the request was deleted | Check the requestId value. Ensure you are using the same API key that submitted the request. |
409 | DUPLICATE_REQUEST | A conflicting resource already exists | Submitting a PoLI access request when an active request already exists for the same product from the same entity | Use GET /api/poli/verify to check the status of the existing request instead of creating a new one |
422 | UNPROCESSABLE_ENTITY | The request was well-formed but semantically invalid | Jurisdiction code is valid ISO 3166-1 but not an EU member state for a PoLI request; productSlug refers to a product with no published DPP | Review the specific constraint described in details. This is a logic error, not a format error — fix the request values. |
429 | RATE_LIMITED | Too many requests in the current window | Burst of requests from a single IP or API key exceeding the rate limit | Wait for retryAfter seconds and retry with exponential backoff. See Rate Limiting. |
500 | INTERNAL_ERROR | An unexpected error occurred on the server | A server-side bug or unhandled exception | Log the X-Request-Id header value and contact support if the error persists. Do not retry immediately — wait at least 30 seconds. |
503 | SERVICE_UNAVAILABLE | The platform or a critical dependency is temporarily unavailable | Database maintenance, Redis connectivity issue, or platform deployment | Retry with exponential backoff. Check status.traceable.digital for incident information. |
Error response structure
Every error response follows this structure:
{
"error": "Human-readable description",
"code": "MACHINE_READABLE_CODE",
"details": {}
}
For VALIDATION_ERROR responses, details contains field-level messages:
{
"error": "Request body validation failed",
"code": "VALIDATION_ERROR",
"details": {
"fields": {
"jurisdiction": "Must be a valid ISO 3166-1 alpha-2 country code",
"contactEmail": "Must be a valid email address"
}
}
}
For DUPLICATE_REQUEST, details contains the existing request ID:
{
"error": "An active access request already exists for this product",
"code": "DUPLICATE_REQUEST",
"details": {
"existingRequestId": "poli_req_01HABC..."
}
}
Error handling best practices
Use code, not HTTP status
// Correct: handle by code string
const data = await response.json();
if (data.code === 'DUPLICATE_REQUEST') {
return { requestId: data.details.existingRequestId, alreadyExists: true };
}
// Fragile: multiple error conditions share the same HTTP status
if (response.status === 409) { ... } // might miss VALIDATION_ERROR on status 400 with same meaning
Only retry 429 and 503
Retry with backoff exclusively for transient errors:
const RETRYABLE_CODES = new Set(['RATE_LIMITED', 'SERVICE_UNAVAILABLE']);
async function callWithRetry(fn: () => Promise<Response>): Promise<Response> {
const response = await fn();
const data = await response.json();
if (!response.ok && RETRYABLE_CODES.has(data.code)) {
// retry with backoff
}
return response; // return immediately for all other errors — retrying won't help
}
Do not retry 4xx errors without fixing the cause
A VALIDATION_ERROR, INVALID_SLUG, PRODUCT_NOT_FOUND, or DUPLICATE_REQUEST will not resolve itself on retry. Fix the request before retrying.
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,
code: data.code,
url: response.url,
});
// Include requestId when contacting support
}
Surface meaningful errors to users
Map error codes to user-facing messages in your integration:
function getErrorMessage(code: string): string {
const messages: Record<string, string> = {
PRODUCT_NOT_FOUND: 'This product passport is not available. The product may not exist or may not be published yet.',
RATE_LIMITED: 'Too many requests. Please wait a moment and try again.',
SERVICE_UNAVAILABLE: 'The Traceable platform is temporarily unavailable. Please try again shortly.',
INTERNAL_ERROR: 'An unexpected error occurred. If this persists, please contact support.',
};
return messages[code] ?? 'An error occurred. Please try again.';
}