Error codes
The stable code values, what causes them, and what to do.
Failures come back as:
{ "error": { "code": "VALIDATION_FAILED", "message": "…" } }code is stable and is what your software should branch on. message is written for people and its wording may change.
The document itself
| Code | HTTP | Cause |
|---|---|---|
PEPPOL_VALIDATION_FAILED | 400 | The document does not satisfy the Peppol rules for its type |
VALIDATION_FAILED | 400 | The request was malformed: a bad field, an unknown JSON key, a cursor that does not parse |
VALIDATION_UNAVAILABLE | 400 / 503 | The document could not be checked. Retry later; it is not a verdict |
PEPPOL_DOCUMENT_NOT_SUPPORTED | 400 | No capability covers this document type in this direction |
PEPPOL_RECIPIENT_NOT_FOUND | 400 | The receiving address is not known to us |
PEPPOL_DISCOVERY_FAILED | 400 / 503 | The recipient could not be looked up on the network |
PAYLOAD_TOO_LARGE | 413 | Over the size limit for the body or the document |
A validation failure is yours to fix — resending the same bytes fails again. A discovery failure is often the recipient's address being wrong, or their registration not covering the document type.
Credentials and access
| Code | HTTP | Cause |
|---|---|---|
UNAUTHORIZED | 401 | Missing, malformed or revoked credential |
FORBIDDEN | 403 | The credential is valid but not allowed here |
PERMISSION_DENIED | 403 | The API key lacks the permission this route needs |
PASSWORD_CHANGE_REQUIRED | 403 | The user must set a new password first |
NOT_FOUND | 404 | Missing, out of your scope, or not a valid id |
NOT_FOUND is deliberately returned for things that exist but are outside your scope. A credential confined to one company cannot tell the difference between another company's document and one that was never created — that is the point.
State and conflicts
| Code | HTTP | Cause |
|---|---|---|
CONFLICT | 409 | It already exists |
INVALID_STATE | 409 | The action does not apply in the current lifecycle state |
IDEMPOTENCY_CONFLICT | 409 | The same Idempotency-Key with a different body |
IDENTIFIER_TAKEN | 409 | That company or participant identifier is in use |
EMAIL_CONFLICT | 409 | That email already belongs to a user |
COMPANY_REQUIRED | 400 | Your credential reaches several companies and the request did not name one |
ACCOUNT_CLOSED / COMPANY_CLOSED | 409 | Closed, and not accepting changes |
LAST_OWNER / LAST_API_KEY | 409 | Removing it would leave no way in |
Rate limiting and availability
| Code | HTTP | Cause |
|---|---|---|
429 (no code needed) | 429 | Too many requests. Wait for Retry-After |
PEPPOL_UNAVAILABLE | 503 | A dependency is down. Retry with backoff |
PEPPOL_INTERNAL_ERROR | 500 | Our fault. Retrying an identical request rarely helps |
What to retry
Retry 429 after Retry-After, and 503 with exponential backoff. Both are temporary by definition.
Do not retry a 4xx unchanged — the answer will not differ. The exception is VALIDATION_UNAVAILABLE, which says the check could not be run rather than that it failed.
Always carry an Idempotency-Key on sends, so that a retry after a timeout — where you never learned whether the first attempt landed — cannot produce a second document.