Every error response carries the same JSON envelope, the HTTP status code matches the semantics, and the error string is stable enough to switch on. The list below is exhaustive — if you hit something not on it, please report it to support@tracepass.eu.
Envelope
{
"error": "Validation error",
"details": {
"fieldErrors": {
"model": ["Required"]
}
}
}error is always present and is a stable string — switch on it in your client. details is optional; when present its shape depends on the specific error (a flattened Zod result for validation, plan/usage numbers for an overage, role / permission name for a 403).
Status codes
| Status | When | Retry? |
|---|---|---|
| 400 | Validation error — body or query failed schema check; or unknown field key on a passport PATCH. | No (fix the request) |
| 401 | Missing or revoked API key, or session expired. | No (mint a new key / re-auth) |
| 402 | DPP quota exhausted on a passport-create call. Retry with `confirmOverage: true` if the plan supports overage. Also fired when an active subscription is required but past_due/canceled. | Yes, with `confirmOverage: true` |
| 403 | Plan-gate (e.g. v1 API on a Free plan) or workspace permission denied. | No |
| 404 | Resource doesn't exist or is in a different workspace. | No |
| 409 | Conflict — duplicate model on a product, GTIN registered to a different tenant, serial collision within a GTIN. | Sometimes (resolve the conflict, then retry) |
| 410 | Resource was permanently removed (rare; mostly applies to public passport URLs after archive). | No |
| 413 | Payload too large — applies to multipart image uploads (5 MB cap). | No |
| 415 | Unsupported media type — image upload that isn't PNG / JPG / WebP. | No |
| 422 | Idempotency-Key reused with a different request body. | No (generate a fresh key) |
| 423 | Resource is suspended (public passport viewer only). | No |
| 429 | Rate limit exceeded — daily writes or daily passport reads. | Yes (after the next UTC midnight) |
| 500 | Unhandled server error. We're paged. | Yes (with backoff) |
| 503 | Maintenance window or overload. Returns Retry-After. | Yes (after Retry-After) |
Retry guidance
For the “Yes” rows above: exponential backoff with jitter, capped at six attempts. We don't accept retries more aggressive than once-per-second on a single endpoint — anything faster won't hit the database (in-memory rate limiter rejects), but you'll burn your daily-write budget. TheRetry-After header is set on every 503; the rate-limit reset for 429 is always the next 00:00 UTC.
Common error strings
| Error string | Status | Meaning |
|---|---|---|
Validation error | 400 | Schema check failed. details.fieldErrors carries the per-field reasons (Zod flatten()). |
Invalid field key: <key> | 400 | PATCH .../fields/<key> referenced a key that's not on the passport's template. |
No template found for category: <slug> | 400 | The category you sent isn't seeded — check the spelling against the public Buyer's Guide. |
A product with this model already exists for your company | 409 | Model strings are unique within a workspace. Pick a different model or update the existing product. |
This GTIN is already registered by another company. Contact support if this is an error. | 409 | GTINs are globally unique across tenants — two companies can't both own the same GTIN. |
Serial number already exists for this GTIN | 409 | Pick a different serial. Within a single GTIN, serials must be unique. |
Idempotency-Key has already been used with a different request body. Use a new key for the new request, or reuse the original body. | 422 | You sent the same key twice with different bodies. Generate a fresh key. |
overage_required | 402 | DPP quota exhausted. Body carries planLimit + currentUsage + extraPriceCents. Retry with `confirmOverage: true` to accept the per-passport charge. |
API rate limit: N writes/day via /api/v1. Currently X today; requested Y. Retry tomorrow (UTC) or upgrade your plan. | 429 | Daily v1 write budget exhausted. Wait or upgrade. |
API rate limit: N passports/day read via /api/v1. Currently X today; requested Y. Retry tomorrow (UTC) or contact support for a bulk export. | 429 | Daily v1 passport-read budget exhausted. Wait, upgrade, or use the dashboard JSON-LD bulk export. |
Passport not found | 404 | Wrong ID or wrong workspace. Verify the API key matches the workspace that owns the passport. |