Errors
How the API reports errors as RFC 9457 Problem Details with stable codes and a request id.
Every error response has a 4xx or 5xx status and a body of type application/problem+json (RFC 9457
Problem Details). Your code decides by the HTTP status and the stable code; the texts are for people.
Members of a problem
typestringrequiredProblem type. about:blank unless a specific type is documented.
titlestringrequiredShort summary; for about:blank the HTTP status phrase, e.g. Bad Request.
statusintegerrequiredThe HTTP status code, repeated in the body.
codestringrequiredStable, machine-readable code in snake_case, e.g. validation_error. See Codes.
detailstringExplanation of this occurrence, in English. Show it in logs, do not parse it.
instancestringURI reference of the request that caused the problem, when present.
request_idstringThe id of the request, equal to the X-Request-Id response header. Quote it when you contact support.
errorsobject[]Field-level details for validation_error, and for conflict when a value must be unique.
fieldstringrequiredThe body field (dotted, with list positions, e.g. values[0].measured_at), the query parameter or
header name (e.g. limit), or body when the body as a whole is invalid (malformed JSON, an unknown
property, a wrong content type).
codestringrequiredStable reason code, see Field error codes.
messagestringrequiredExplanation in English; localise by code.
Field error codes
| Code | Meaning |
|---|---|
required | A required field, parameter or header is missing. |
too_long | A text is longer than allowed. |
too_short | A text is shorter than allowed. |
out_of_range | A number, date or time lies outside the allowed range, e.g. a measured_at in the future. |
invalid_format | The value has the wrong format (date, UUID, pattern), or the body is not valid JSON or not application/json. |
invalid_check_digit | The check digit of a GTIN or GLN is wrong. |
invalid_value | The value is not allowed here: an unknown enum value, an invalid parameter or cursor, a failing cross-field check. |
unknown_field | The field is not known here, e.g. a field that is not condition data of the unit, or an unknown body property. |
already_exists | A value that must be unique is already taken (with conflict). |
Examples
A condition update with a measurement time in the future
(updateCondition) is refused as a whole:
{
"type": "about:blank",
"title": "Bad Request",
"status": 400,
"code": "validation_error",
"detail": "The request failed validation.",
"request_id": "4f9c2a7d1e0b5c8a3f6e2d19",
"errors": [
{
"field": "values[0].measured_at",
"code": "out_of_range",
"message": "measured_at lies in the future"
}
]
}Too many requests: the Retry-After header says how many seconds to wait.
HTTP/1.1 429 Too Many Requests
Content-Type: application/problem+json
Retry-After: 17
X-Request-Id: 8b1e6f03c2d94a7e5f0a1b2c
{
"type": "about:blank",
"title": "Too Many Requests",
"status": 429,
"code": "rate_limited",
"detail": "Too many attempts. Try again later.",
"request_id": "8b1e6f03c2d94a7e5f0a1b2c"
}Codes
The ProblemCode values of the published contract. Codes marked as portal only do not occur with an API
key; they are listed because the contract contains them.
| Code | HTTP status | Meaning |
|---|---|---|
validation_error | 400 | The request is invalid. errors[] names each affected field, query parameter or header with a stable error code. |
invalid_token | 400 | Portal only: an email or invitation link is unknown, used or expired. |
unauthenticated | 401 | No valid API key: the header is missing, the key is unknown or revoked, or the request also carries a session cookie. |
invalid_credentials | 401 | Portal only: the email address or password of a sign-in is wrong. |
forbidden | 403 | The key lacks the scope the operation needs, or a viewer's rights are not enough. |
email_not_verified | 403 | Portal only: the email address is not verified yet. |
not_found | 404 | The resource does not exist, or it belongs to another organisation. |
method_not_allowed | 405 | The path exists, but not with this HTTP method. |
conflict | 409 | The request contradicts the current state, for example the unit's battery model is archived: its data can be read but no longer changed. |
identifiers_locked | 409 | Portal only: the identifiers of a published unit can no longer change. |
payload_too_large | 413 | The request body is larger than allowed (1 MiB by default). |
publish_blocked | 422 | Portal only: publishing fails on open required data; blockers[] lists it. |
rate_limited | 429 | Too many requests. Wait the seconds given in the Retry-After header, then try again. |
request_closed | 410 | Supplier and service-partner links only: the link expired, was cancelled or has been used. |
internal_error | 500 | An error on our side. Retry later; quote the request_id when you contact support. |
address_lookup_unavailable | 503 | Portal only: the address search is not reachable right now; the address can be entered by hand. |
Request id
Every response, successful or not, carries an X-Request-Id header. In a problem, request_id has the same
value. The server creates the id itself and ignores an id you send. Log it with each failed request and quote
it when you contact support.
Handling errors
- Decide by
statusandcode, never bytitle,detailormessage: those texts can change. - Expect new codes over time. Treat an unknown
codeby its HTTP status class. - Fix
400,401,403,404and409instead of repeating the request unchanged. - Retry
429afterRetry-After, and5xxor network errors with exponential backoff. Before you repeat a write, read Retries and idempotency. - Responses never contain SQL, stack traces or upstream errors; an unexpected failure is
500 internal_errorwith a request id.
The guide Handle errors shows complete error handling in TypeScript, Python and Go.