GET /company/vat
Validates an intra-EU VAT number against VIES (VAT Information
Exchange System, European Commission) - the federation of the national VAT
registers of all EU member states (EU-27, plus XI for Northern Ireland).
The check is live: every answer reflects a query to the member state’s
register at the moment of the call. Given a French SIREN instead, the
endpoint derives the French VAT number by pure computation and checks the
derived number the same way.
Use it as the pre-action check before an agent invoices a European
counterparty: an intra-EU VAT number that is confirmed by its member state is
what reverse-charge invoicing (intra-Community exemption) and invoice
mentions rely on. It pairs naturally with
GET /company/resolve (who the company is),
GET /company/events (what has legally happened
to it) and POST /invoice/validate, whose
plausibility checks are syntax-only by design - this endpoint is where
the register is actually consulted. See the live
/catalog for the authoritative endpoint listing and
price.
x402 golden rule: the agent pays for a verification that ran, not for
a “positive” result. All three outcomes - valid, invalid and
unverifiable - are 200, billed (see the three
outcomes below). Requests the service
cannot answer - a malformed number, a non-EU country prefix, a failed
checksum - leave the 200 range and are not billed.
Parameters
Provide exactly one of vat or siren - zero or both is a
400 INVALID_INPUT.
| Parameter | Type | Required | Description |
|---|---|---|---|
vat | string | one of | Full VAT number with its country prefix, any EU member state (EU-27 + XI) |
siren | string | one of | 9-digit French SIREN; the French VAT number is derived and then checked |
GET /company/vat?vat=FR40303265045
GET /company/vat?siren=303265045
Input is normalized before anything else: usual separators (spaces, dots,
dashes) are stripped, case is folded to upper, and the ISO alias GR is
accepted for the Greek VAT prefix EL. The number’s syntax is validated
per member state before any network call - a syntactically invalid number
is a 400, never a wasted VIES query.
French derivation from a SIREN
With siren=, the French VAT number is derived by pure computation:
key = (12 + 3 × (SIREN mod 97)) mod 97 → FR<key><SIREN>
For example SIREN 303265045 derives to FR40303265045. The SIREN must
first pass strict form (9 digits) and its Luhn check digit (no SIREN is
exempt - the La Poste SIREN 356000000 passes the standard check); a failed
checksum is a
400 INVALID_CHECKSUM - almost certainly a typo the agent should fix. The
derivation is traced in the answer (derived_from_siren) and in the
provenance. The same key consistency is enforced on vat=FR… input when the
key is numeric; alphabetic keys from the older French scheme are left for
VIES to judge.
200 response - UnifiedResponse
{
"data": { ... },
"provenance": {
"source": "vies",
"fetched_at": "2026-07-12T15:30:31Z",
"freshness": { "kind": "live" }
}
}
freshness.kind:live- every answer is a VIES check at call time, there is no cache or snapshot behind this endpoint.provenance.sourcestates what was actually consulted:vies(the member state answered),vies + pure-computation(same, with the French number derived from a SIREN), orpure-computationalone (degraded: VIES did not answer, only the local syntax check and derivation ran - the provenance never claims a consultation that did not happen).
Fields of data
| Field | Type | Description |
|---|---|---|
vat_number | string | The checked VAT number, normalized (derived when siren= was used) |
country | string | Member-state prefix of the number (FR, DE, EL, XI, …) |
syntax_valid | bool | The number respects its member state’s national syntax |
status | string | valid, invalid or unverifiable - see below |
name | string | Registered name, relayed only when the member state publishes it; absent otherwise |
address | string | Registered address, same publication rule; absent otherwise |
derived_from_siren | string | The input SIREN, present only when the number was derived from siren= |
reason | string | Only on unverifiable: why the register could not be consulted (timeout, network_unreachable, MS_UNAVAILABLE, …) |
coverage | string | Only on unverifiable: syntax_only - exactly what was verified |
checked_at | string | Timestamp of the check |
The three outcomes, all paid answers
The question the agent pays for is “was this VAT number checked?” - and all three outcomes answer it:
valid- the member state’s register confirmed the number.nameandaddressare relayed as published when the state discloses them (some member states do not).invalid- the member state’s register firmly rejected the number. A “no” is a useful answer: the agent should not apply the intra-Community exemption to this counterparty.unverifiable- VIES or the member state was unavailable. The degraded answer is honest about its own boundaries: the syntax check still ran (syntax_valid: true), the failure is motivated (reason) and the scope is bounded (coverage: "syntax_only") - the answer states exactly what was verified (the syntax) and what was not (the national register). “Unverifiable right now” is not “invalid” - and a VIES outage is never a 5xx on this endpoint.
Example - valid (real VIES answer, 2026-07-12)
GET /company/vat?vat=FR40303265045 (SODIMAS, the example documented by
VIES itself):
{
"data": {
"vat_number": "FR40303265045",
"country": "FR",
"syntax_valid": true,
"status": "valid",
"name": "SA SODIMAS",
"address": "11 RUE AMPERE\n26600 PONT DE L ISERE",
"checked_at": "2026-07-12T15:30:31.096065814Z"
},
"provenance": {
"source": "vies",
"fetched_at": "2026-07-12T15:30:31.096065814Z",
"freshness": { "kind": "live" }
}
}
Example - invalid, derived from a SIREN (real VIES answer, 2026-07-12)
GET /company/vat?siren=111111118 - a well-formed SIREN (Luhn passes) that
does not exist; the derived FR44111111118 is firmly rejected by the member
state:
{
"data": {
"vat_number": "FR44111111118",
"country": "FR",
"syntax_valid": true,
"status": "invalid",
"derived_from_siren": "111111118",
"checked_at": "2026-07-12T15:30:53.104349431Z"
},
"provenance": {
"source": "vies + pure-computation",
"fetched_at": "2026-07-12T15:30:53.104349431Z",
"freshness": { "kind": "live" }
}
}
This is a 200, billed: the question “is this VAT number valid?” received
its answer - a firm “no” from the member state. The derivation is traced
both in derived_from_siren and in the pure-computation part of the
source.
Example - unverifiable (degraded, VIES unreachable, 2026-07-12)
GET /company/vat?vat=FR40303265045 while VIES cannot be reached:
{
"data": {
"vat_number": "FR40303265045",
"country": "FR",
"syntax_valid": true,
"status": "unverifiable",
"checked_at": "2026-07-12T19:05:19Z",
"reason": "network_unreachable",
"coverage": "syntax_only"
},
"provenance": {
"source": "pure-computation",
"fetched_at": "2026-07-12T19:05:19Z",
"freshness": { "kind": "live" }
}
}
200, never a 5xx for a VIES outage: the syntax was verified
(syntax_valid: true), the degradation is motivated (reason), bounded
(coverage: "syntax_only"), and the provenance only claims the local
computation (pure-computation). The agent knows exactly what it paid for -
and can retry later for the register-level answer.
Freshness
This is a live endpoint: each answer is a VIES consultation at call time
(short timeout, a single brief retry), and freshness.kind is always
live. There is nothing to cache honestly here - a VAT registration can be
revoked at any time, so the value of the answer is precisely that it is
current.
Limits
Stated plainly, so the agent knows what the answer does and does not claim:
- Availability is per member state: VIES federates national registers,
so one state under maintenance yields
unverifiablefor its numbers only - checks against other states are unaffected. nameandaddressare relayed only when the member state publishes them; several states do not disclose them through VIES.- No consultation proof (VIES
requestIdentifier) in v1. siren=derivation applies to French companies only - and a valid SIREN does not imply VAT registration: the derived number can come backinvalidif the company is not registered for intra-Community VAT.
Errors
Only requests the service cannot answer leave the 200 range - none of
them are billed. Note the deliberate absence of a business 404: a
well-formed number always gets an answer (valid, invalid or
unverifiable).
| Status | code | Case |
|---|---|---|
| 400 | INVALID_INPUT | Zero or two of vat/siren; country prefix not an EU member state; number violates its state’s national syntax; malformed SIREN |
| 400 | INVALID_CHECKSUM | French VAT key inconsistent with the embedded SIREN, or siren= fails its Luhn check - a probable typo the agent should fix |
| 404 | NOT_FOUND | Unknown routes only - never a well-formed VAT number |
| 500 | INTERNAL | Internal error (detail logged, not exposed) - a VIES outage is not one of these |
{ "error": "provide exactly one of 'vat' or 'siren'", "code": "INVALID_INPUT" }
Attribution
Validation is performed against VIES (VAT Information Exchange System), operated by the European Commission - a public EU service, consulted without any key or account.
See also
GET /company/resolve- the official identity card behind a SIREN/SIRET: legal name, status, legal form, NAF activity, head office.GET /company/events- BODACC legal announcements for the same counterparty: insolvency proceedings, deregistrations, business sales.POST /invoice/validate- EN 16931 invoice validation; its VAT-number plausibility check is syntax-only, this endpoint is the register-level complement.- For agents - discovery surfaces, the live
/catalogand how settlement works.