Documentation / Getting started
Errors
Every error response has the same envelope:
{ "error": { "code": "RATE_LIMITED", "message": "too many requests", "retry_after": 1 } }
Additional fields (retry_after, reset_at, checks) live alongside code and message inside the error object, never at the top level.
| HTTP status | error.code | When |
|---|---|---|
| 400 | BAD_REQUEST | missing/invalid vat_number, unresolvable country, malformed JSON, unknown field, or body over 64 KiB |
| 401 | UNAUTHORIZED | missing, malformed, unknown, or revoked API key |
| 404 | NOT_FOUND | unknown route, or a check that does not exist or is not owned by the caller |
| 405 | METHOD_NOT_ALLOWED | valid route, wrong HTTP method (the Allow header lists the accepted methods) |
| 429 | RATE_LIMITED | more than 10 requests/second on this key; carries retry_after (seconds) |
| 429 | QUOTA_EXCEEDED | monthly quota exhausted; carries reset_at (RFC 3339) |
| 403 | ACCOUNT_DISABLED | the account is disabled — on a direct POST /v1/checks, or while replaying a request accepted earlier with a 202; see also reason on GET /v1/requests/{id} |
| 429 | TOO_MANY_PENDING | when 200 background requests are already pending on this account; carries retry_after and pending |
| 400 | EMPTY_FILE, TOO_MANY_ROWS, INVALID_CSV, MISSING_CANDIDATE | unusable batch file — see Batches |
| — | INVALID_REFERENCE | a batch line whose reference fails validation — see Batches |
| 409 | BATCH_RUNNING | DELETE /v1/batches/{id} on a batch that is still queued or running |
| 413 | FILE_TOO_LARGE | batch upload over 2 MiB |
| 429 | TOO_MANY_BATCHES | 5 batches are already queued or running on this account; carries active |
| 400 | EXPORT_TOO_LARGE | GET /v1/exports/dossier.zip over 10 000 checks — see Audit file export |
| 409 | EXPORT_IN_PROGRESS | GET /v1/exports/dossier.zip while one is already running for this account — see Audit file export |
| 400 | INVALID_FORMAT | POST /v1/watches with a VAT number that cannot be normalised |
| 403 | WATCH_LIMIT | the plan's watch limit is reached; carries limit |
| 403 | FREQUENCY_NOT_ALLOWED | weekly or daily monitoring asked on a plan that does not sell it |
| 409 | WATCH_EXISTS | POST /v1/watches on a number this account already watches |
| 400 | NO_WEBHOOK | POST /v1/webhook/test on an account with no webhook configured |
| 400 | INVALID_REQUESTER | requester_vat_number is not a valid VAT number |
| 422 | IDEMPOTENCY_MISMATCH | same Idempotency-Key, different body — see Idempotency |
| 409 | IDEMPOTENCY_IN_PROGRESS | the first request with this Idempotency-Key is still running; carries retry_after |
| 403 | TEST_KEY_NOT_ALLOWED | a test key on batches, watches, the audit export, anchors or timestamps |
| 503 | UPSTREAM_UNAVAILABLE | reserved, emitted by no code path today. A VIES outage no longer produces it: the request is accepted with 202 instead, and an unreachable database is a 500 INTERNAL |
| 500 | INTERNAL | unexpected server-side failure |
A syntactically malformed VAT number, or one VIES reports as invalid, is not an HTTP error: it is a normal 201 Created response with valid: false.
Latency when a member state is saturated #
VIES answers MS_MAX_CONCURRENT_REQ (or GLOBAL_MAX_CONCURRENT_REQ) when the member state's concurrency slots are full. That is not a verdict: the answer exists, it is only waiting for a slot, so the API retries on a bounded schedule instead of failing fast. A check on a saturated member state can therefore take up to about 20 seconds waiting for VIES before answering. The enrichment sources that run after the verdict, and the confirmation step that also goes through VIES, push the worst case for the whole check to about 48 seconds. Requests are also queued per member state, two at a time, and a caller that waits more than 5 seconds for a slot gets its checks entry marked backpressure rather than holding the line. Size client timeouts at 60 seconds or more.
When that window runs out, the call is no longer refused. The request is accepted with 202 Accepted and completed in the background — see Pending validation above. The outcome reaches the client either through the webhook, which is the recommended route because it needs no polling, or by reading GET /v1/requests/{id} at the poll_url the 202 body carries. Nothing is ever invented in the meantime: a saturated upstream never produces valid: false, the checks entry carries MS_MAX_CONCURRENT_REQ rather than a bare timeout, no check is written and no quota is consumed until VIES has actually answered.