akwitoEU VAT proof

Errors

Every error response has the same envelope:

json
{ "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 statuserror.codeWhen
400BAD_REQUESTmissing/invalid vat_number, unresolvable country, malformed JSON, unknown field, or body over 64 KiB
401UNAUTHORIZEDmissing, malformed, unknown, or revoked API key
404NOT_FOUNDunknown route, or a check that does not exist or is not owned by the caller
405METHOD_NOT_ALLOWEDvalid route, wrong HTTP method (the Allow header lists the accepted methods)
429RATE_LIMITEDmore than 10 requests/second on this key; carries retry_after (seconds)
429QUOTA_EXCEEDEDmonthly quota exhausted; carries reset_at (RFC 3339)
403ACCOUNT_DISABLEDthe 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}
429TOO_MANY_PENDINGwhen 200 background requests are already pending on this account; carries retry_after and pending
400EMPTY_FILE, TOO_MANY_ROWS, INVALID_CSV, MISSING_CANDIDATEunusable batch file — see Batches
—INVALID_REFERENCEa batch line whose reference fails validation — see Batches
409BATCH_RUNNINGDELETE /v1/batches/{id} on a batch that is still queued or running
413FILE_TOO_LARGEbatch upload over 2 MiB
429TOO_MANY_BATCHES5 batches are already queued or running on this account; carries active
400EXPORT_TOO_LARGEGET /v1/exports/dossier.zip over 10 000 checks — see Audit file export
409EXPORT_IN_PROGRESSGET /v1/exports/dossier.zip while one is already running for this account — see Audit file export
400INVALID_FORMATPOST /v1/watches with a VAT number that cannot be normalised
403WATCH_LIMITthe plan's watch limit is reached; carries limit
403FREQUENCY_NOT_ALLOWEDweekly or daily monitoring asked on a plan that does not sell it
409WATCH_EXISTSPOST /v1/watches on a number this account already watches
400NO_WEBHOOKPOST /v1/webhook/test on an account with no webhook configured
400INVALID_REQUESTERrequester_vat_number is not a valid VAT number
422IDEMPOTENCY_MISMATCHsame Idempotency-Key, different body — see Idempotency
409IDEMPOTENCY_IN_PROGRESSthe first request with this Idempotency-Key is still running; carries retry_after
403TEST_KEY_NOT_ALLOWEDa test key on batches, watches, the audit export, anchors or timestamps
503UPSTREAM_UNAVAILABLEreserved, 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
500INTERNALunexpected 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.