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.

ParameterTypeRequiredDescription
vatstringone ofFull VAT number with its country prefix, any EU member state (EU-27 + XI)
sirenstringone of9-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.source states what was actually consulted: vies (the member state answered), vies + pure-computation (same, with the French number derived from a SIREN), or pure-computation alone (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

FieldTypeDescription
vat_numberstringThe checked VAT number, normalized (derived when siren= was used)
countrystringMember-state prefix of the number (FR, DE, EL, XI, …)
syntax_validboolThe number respects its member state’s national syntax
statusstringvalid, invalid or unverifiable - see below
namestringRegistered name, relayed only when the member state publishes it; absent otherwise
addressstringRegistered address, same publication rule; absent otherwise
derived_from_sirenstringThe input SIREN, present only when the number was derived from siren=
reasonstringOnly on unverifiable: why the register could not be consulted (timeout, network_unreachable, MS_UNAVAILABLE, …)
coveragestringOnly on unverifiable: syntax_only - exactly what was verified
checked_atstringTimestamp 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. name and address are 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 unverifiable for its numbers only - checks against other states are unaffected.
  • name and address are 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 back invalid if 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).

StatuscodeCase
400INVALID_INPUTZero or two of vat/siren; country prefix not an EU member state; number violates its state’s national syntax; malformed SIREN
400INVALID_CHECKSUMFrench VAT key inconsistent with the embedded SIREN, or siren= fails its Luhn check - a probable typo the agent should fix
404NOT_FOUNDUnknown routes only - never a well-formed VAT number
500INTERNALInternal 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 /catalog and how settlement works.