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. Report unexpected status codes 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 (per-field messages under fieldErrors 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 | Plan allowance exceeded where the plan has an overage rate: `epcis_events_overage_required` on EPCIS capture, `measurements_overage_required` on measurements. On Free, EPCIS capture and measurements return `epcis_events_hard_block` / `measurements_hard_block` instead. Passport create never returns 402 for the plan limit: a draft takes no plan slot, and the limit applies when you publish in the dashboard. Also returned by v1 writes that add or change content when billing is not in good standing (past due, unpaid or cancelled). Reads keep working, and so do scope checks, wrong-answer reports, catalogue runs and compliance-profile changes. | Overage: yes, with `confirmOverage: true`. Hard block: no (upgrade). Billing: no (resolve billing first) |
| 403 | Workspace permission denied (e.g. a supplier-portal-only credential attempting a v1 write). | No |
| 404 | Resource doesn't exist or is in a different workspace. | No |
| 409 | Conflict. On passport writes: passport is archived (`passport_archived`); an identity field (GTIN, serial, etc.) is locked after first publish (`identifier_locked`, with locked field names in `keys`); or a concurrent write race (`concurrent_update` — reload and retry). On any v1 write: a request with the same Idempotency-Key is still running (`idempotency_key_in_use` — retry with the same key once it finishes). On product and passport create: duplicate model on a product, GTIN registered to a different tenant, or serial collision within a GTIN. | For concurrent_update and idempotency_key_in_use: yes (retry). Others: resolve the conflict first |
| 410 | Passport has expired: the subscription ended and the 30-day grace window passed. It resolves again if the subscription is restored. Archived passports return 404, not 410. | No |
| 413 | Payload too large — applies to multipart image uploads (5 MB cap). | No |
| 415 | Unsupported media type — an image upload that isn't PNG / JPG / WebP, or a file whose content doesn't match its declared type. | No |
| 422 | Idempotency-Key reused with a different request body; or, on an upload, the virus scanner found malware. | No (generate a fresh key; for an upload, the file is refused) |
| 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); or, on an upload, the virus scanner is briefly unavailable — nothing is stored unscanned. | Yes (after Retry-After) |
Retry guidance
For the “Yes” rows above: exponential backoff with jitter, capped at six attempts. Aggressive retries burn your daily-write budget without benefit. The Retry-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 reasons, keyed by field. |
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. |
passport_archived | 409 | This passport was archived. Archived passports are permanently read-only. A passport marked for deletion is read-only too and returns the same code. |
identifier_locked | 409 | The request would change, clear or un-approve an identity field (e.g. gtin, serialNumber) that already holds an approved value on a published passport. A value still awaiting review can be corrected, and an empty one filled. The keys array lists the locked fields. |
concurrent_update | 409 | Another write changed the passport while this one was being saved. Reload the passport and retry. |
idempotency_key_in_use | 409 | A request with the same Idempotency-Key is still being processed. Wait for it to finish, then retry with the same key: you get its result back. |
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. |
Subscription is past due — resolve billing to continue creating new content. Published passports remain accessible. | 402 | Billing is not in good standing. Writes that add or change content are refused; every read keeps working and published passports stay live. Resolve billing in the dashboard. |
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. |