Este artículo aún no está disponible en español; se muestra a continuación en inglés.
On this pageError shape
Error shape
Every error is a JSON object with an error property: a stable machine-readable code such as insufficient_scope, or a short message. Most errors add a human-readable message; validation errors add details, keyed by field; and every /v1 error carries request_id, the same value as the X-Request-Id header. Branch on the HTTP status and error, show message to people, and log request_id.
{"error": "<code or message>","message": "Optional explanation for people.","details": { "<field>": ["<message>", "..."] },"request_id": "3f1c2a9e-8d4b-4c6f-9e2a-7b5d1c0e8f34"}Status codes
Success is 200 (read or update), 201 (created) or 207 (a bulk request where some rows failed). Everything else:
| Status | Meaning | error | What to do |
|---|---|---|---|
| 400 | The body or query failed validation, the Idempotency-Key is malformed, or JSON was announced and none sent. | Validation failedinvalid_idempotency_key | details names each invalid field with its messages. Fix the request; retrying it unchanged fails again. |
| 401 | The key is missing, malformed, unknown or revoked. | unauthorized | Check the header is Authorization: Bearer dpp_live_… (or dpp_test_…). A revoked key never comes back; create a new one. |
| 402 | Your plan does not include this. | plan_upgrade_requiredpaused_on_maintenance | feature and requiredPlan say what is needed. API access itself (erp_api) is on every plan; publishing needs a paid plan. |
| 403 | The key is valid but may not do this. | insufficient_scopemethod_not_allowed_for_api_keys | A write with a read-only key needs a key with the write scope. DELETE is never available to API keys. |
| 404 | Not found, not in your organisation, or not an API route. | not_foundNot foundroute_not_found | Records of other organisations are indistinguishable from records that do not exist. Branch on the status, not the exact spelling of error. |
| 409 | The request conflicts with the state of a record, the organisation or another request. | organisation_frozenidentifier_takenidempotency_in_progress | A frozen organisation is read-only (reads and exports keep working). A recycled passport cannot be published. idempotency_in_progress: the first request with this key is still running — retry shortly. |
| 413 / 415 | Document too large, or of an unsupported type. | — | Documents must be PDF, JPEG or PNG and at most 25 MB. |
| 422 | Well-formed, but refused by a business rule. | publish_blockedinvalid_identifierdate_requiredinvalid_webhook_urlidempotency_key_reused | message explains the rule; publish_blocked lists blockers. An idempotency key cannot be reused for a different request. |
| 429 | Rate limit exceeded. | — | Wait for the number of seconds in retry-after, then retry. |
| 5xx | Something failed on our side. | Internal server error | Retry with backoff and the same Idempotency-Key (a 5xx is never stored, so the retry runs again). Quote the request_id if you contact us. |
A record that belongs to another organisation is answered with 404, exactly as if it did not exist: the API never confirms that someone else’s identifier is in use.
Validation errors
Request bodies are validated before anything is written. A 400 names every invalid field at once, so one round trip shows everything to fix.
{"error": "Validation failed","details": {"capacityKwh": ["Number must be greater than 0"],"chemistry": ["Required"]},"request_id": "3f1c2a9e-8d4b-4c6f-9e2a-7b5d1c0e8f34"}Bulk requests and 207
Bulk endpoints take up to 1,000 rows and answer per row rather than all or nothing: a batch of two hundred where three rows cannot be processed applies the other 197 and names the three. The status is 200 when every row succeeded and 207 Multi-Status when at least one failed. Always read results, even on 200.
{"requested": 4,"published": 1,"failed": 2,"results": [{ "id": "cm1q8y0bq0006l70fx9y8z7w6", "ok": true },{ "id": "cm1q8y0bq0008l70fa1b2c3d4", "ok": true, "alreadyPublished": true },{ "id": "cm1q8y0bq000al70fe5f6g7h8", "ok": false, "error": "missing_place_of_manufacture" },{"id": "cm1q8y0bq000cl70fi9j0k1l2","ok": false,"error": "model_incomplete","message": "The battery model still owes 2 required item(s) for its category: Weight, Nominal voltage."}]}Row-level codes by endpoint:
| Endpoint | Row error codes |
|---|---|
| POST /passports/publish | not_foundceasedmissing_place_of_manufacturemodel_incomplete |
| POST /passports/bulk-lifecycle | not_foundalready_recycled |
| POST /passports/import-updates | not_foundalready_recyclednothing_to_change |
In the example above, published counts only passports published by this request; an already-published row is ok but not counted again.
JSON bodies
Send Content-Type: application/json only when the request has a body. Several write endpoints take no body — POST /passports/{id}/publish, for example — and a request that announces JSON but sends nothing is rejected with 400. HTTP clients such as Python’s requests with json= handle this for you; with fetch, set the header only alongside a body.
Retrying safely
Retry 429 after the retry-after delay and 5xx with exponential backoff. Do not retry 400, 401, 403, 404 or 422 unchanged — they will fail the same way.
Publishing is safe to repeat: an already-published passport is reported as alreadyPublished and nothing changes. Creating batteries is not: re-sending POST /items/batch without a key after a timeout creates new batteries with new identifiers. Send an Idempotency-Key with every write and the retry returns the first answer instead — see idempotency and retries.