Developers · Core concepts

Errors

The JSON error shape, every HTTP status the Battery Passport API returns, request IDs, and the per-row results of bulk endpoints.

Version
v1.0.0
Base URL
https://dppapi-production.up.railway.app/v1
Format
JSON · OpenAPI 3.1
FeedbackLast updated

Cet article n'est pas encore disponible en français ; il est affiché ci-dessous en anglais.

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.

Shape
{"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:

StatusMeaningerrorWhat to do
400The body or query failed validation, the Idempotency-Key is malformed, or JSON was announced and none sent.Validation failedinvalid_idempotency_keydetails names each invalid field with its messages. Fix the request; retrying it unchanged fails again.
401The key is missing, malformed, unknown or revoked.unauthorizedCheck the header is Authorization: Bearer dpp_live_… (or dpp_test_…). A revoked key never comes back; create a new one.
402Your plan does not include this.plan_upgrade_requiredpaused_on_maintenancefeature and requiredPlan say what is needed. API access itself (erp_api) is on every plan; publishing needs a paid plan.
403The key is valid but may not do this.insufficient_scopemethod_not_allowed_for_api_keysA write with a read-only key needs a key with the write scope. DELETE is never available to API keys.
404Not found, not in your organisation, or not an API route.not_foundNot foundroute_not_foundRecords of other organisations are indistinguishable from records that do not exist. Branch on the status, not the exact spelling of error.
409The request conflicts with the state of a record, the organisation or another request.organisation_frozenidentifier_takenidempotency_in_progressA 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 / 415Document too large, or of an unsupported type.—Documents must be PDF, JPEG or PNG and at most 25 MB.
422Well-formed, but refused by a business rule.publish_blockedinvalid_identifierdate_requiredinvalid_webhook_urlidempotency_key_reusedmessage explains the rule; publish_blocked lists blockers. An idempotency key cannot be reused for a different request.
429Rate limit exceeded.—Wait for the number of seconds in retry-after, then retry.
5xxSomething failed on our side.Internal server errorRetry 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.

400 · POST /v1/models
{"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.

207 · POST /v1/passports/publish
{"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:

EndpointRow error codes
POST /passports/publishnot_foundceasedmissing_place_of_manufacturemodel_incomplete
POST /passports/bulk-lifecyclenot_foundalready_recycled
POST /passports/import-updatesnot_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.

Préférences en matière de cookies

Vous choisissez les cookies utilisés. Vous pouvez modifier votre choix à tout moment via « Paramètres des cookies » en bas de page. Politique de confidentialité

Nécessaires

Toujours actifs

Indispensables au fonctionnement du site : votre thème clair/sombre et ce choix de cookies sont enregistrés dans votre navigateur. Ils ne servent pas à vous suivre.

Analyse

Google Analytics 4 mesure l’utilisation des pages au moyen de statistiques, ce qui nous aide à améliorer le site. Si vous acceptez, il dépose des cookies et mesure votre visite en détail. Si vous refusez, il ne dépose aucun cookie, mais Google peut tout de même recevoir des données techniques, comme la page consultée, le type de navigateur et l’adresse IP.

_ga, _ga_* · jusqu’à 2 ans · Google