# Is this EU VAT number valid right now? A live VIES check > Before an agent invoices cross-border, GET /company/vat asks the member state live via VIES. Valid, invalid and unverifiable are all paid, dated answers. Published 2026-08-12 · Updated 2026-08-15 · HTML version: https://invoket.com/blog/is-this-eu-vat-number-valid-right-now --- An agent about to issue a cross-border invoice inside the EU has to decide whether to charge VAT or apply the intra-Community exemption, and that decision rests on a number the counterparty typed into a form. Since 1 January 2020 the customer's VAT identification number is not a formality on that invoice — it is a **substantive condition** of the exemption itself. [`GET /company/vat`](/docs/api/company-vat) puts the question to the member state's own register, live, at the moment of the call, and returns one of three outcomes — `valid`, `invalid`, `unverifiable`. All three are **paid `200`s**, because the agent pays for a **check that ran**, not for a "positive" result. This article walks the endpoint and, more importantly, its boundaries, with captures taken from production on 12 August 2026. ## Why the number stopped being a formality [Council Directive (EU) 2018/1910](https://eur-lex.europa.eu/eli/dir/2018/1910/oj) of 4 December 2018 — the "Quick Fixes" — amended Article 138(1) of the VAT Directive 2006/112/EC and added two substantive conditions to the exemption of an intra-Community supply of goods, applicable from **1 January 2020**: 1. the acquirer is **identified for VAT purposes in a member state other than the one of dispatch**, and has **indicated that VAT identification number** to the supplier; 2. the supplier files a correct **recapitulative statement** (EC Sales List) covering the supply. Before that change, a missing or wrong VAT number was a formal defect that could often be cured after the fact. After it, the number is part of what makes the exemption exist. That is what turns "check the VAT number" from bookkeeping hygiene into a **pre-action check** — a thing an agent must do *before* it issues the document, not during a later reconciliation. ## VIES is not a database — it is 27 registers behind one door The European Commission is explicit that VIES is a **search engine, not a database**: it forwards each query to the register of the member state concerned and relays that state's reply. The Commission accepts no responsibility for the accuracy of data it does not hold. Three consequences follow, and every one of them shows up in the response: - **Availability is per member state.** One national register under maintenance or backup affects *its* numbers only; checks against the other states are unaffected. [Your Europe](https://europa.eu/youreurope/business/taxation/vat/check-vat-number-vies/index_en.htm) states the cause plainly — "the system is sometimes not available due to the need to back-up the national databases". - **The quota is per member state, not per caller.** `MS_MAX_CONCURRENT_REQ` is a documented VIES condition meaning the maximum number of concurrent requests *for that state* has been reached. A strictly sequential, well-behaved caller can be throttled simply because the rest of Europe is querying the same register in the same second. - **Disclosure varies.** Some states publish the registered name and address through VIES; others return only the verdict. This is not a gap in the endpoint — it is the member state's own policy, relayed as-is. An integration that treats VIES as a uniform, always-on EU service will be surprised. An integration that treats it as a federation of national registers with national behaviours will not. ## The three outcomes, all of them answers ### `valid` — the register confirmed it `GET /company/vat?vat=IT00159560366`: ```json { "data": { "vat_number": "IT00159560366", "country": "IT", "syntax_valid": true, "status": "valid", "name": "FERRARI S.P.A.", "address": "VIA EMILIA EST 1163 \n41122 MODENA MO", "checked_at": "2026-08-12T06:14:39.751919101Z" }, "provenance": { "source": "vies", "fetched_at": "2026-08-12T06:14:39.751919101Z", "freshness": { "kind": "live" } } } ``` The same call against a German number, in the same session, comes back confirmed with **no name and no address**: ```json { "data": { "vat_number": "DE811907980", "country": "DE", "syntax_valid": true, "status": "valid", "checked_at": "2026-08-12T06:19:13.790857354Z" }, "provenance": { "source": "vies", "fetched_at": "2026-08-12T06:19:13.790857354Z", "freshness": { "kind": "live" } } } ``` Both are complete answers. An agent that requires `name` to be present in order to proceed has written a rule that fails on entire member states. ### `invalid` — the register firmly rejected it A firm "no" is a useful answer, and it is worth being precise about what it means. Your Europe gives the definition: an invalid status means the number "is not registered in the relevant national database", for one of three reasons — the number **does not exist**, it **has not been activated for intra-EU transactions**, or the **registration is not yet finalised**. That second reason is the one that catches integrations. A company can be real, trading and perfectly in order domestically while its VAT number is simply not enabled for intra-Community transactions. So: > `invalid` means *this number is not usable for the intra-EU exemption*. It > does **not** mean the company does not exist. Those are two different questions, answered by two different registers. If what the agent actually needs to know is whether the legal entity exists and is still active, that is [`GET /company/resolve`](/docs/api/company-resolve) against INSEE Sirene — and whether anything legal has happened to it is [`GET /company/events`](/docs/api/company-events) against BODACC. Conflating them produces the worst kind of agent decision: a supplier refused because a tax flag was read as an existence check. ### `unverifiable` — nobody answered, and the response says so ```json { "data": { "vat_number": "FR40303265045", "country": "FR", "syntax_valid": true, "status": "unverifiable", "checked_at": "2026-08-12T06:14:27.288031693Z", "reason": "MS_MAX_CONCURRENT_REQ", "coverage": "syntax_only" }, "provenance": { "source": "pure-computation", "fetched_at": "2026-08-12T06:14:27.288031693Z", "freshness": { "kind": "live" } } } ``` Read what this response refuses to do. It does not round `unverifiable` down to `invalid`, which would silently kill a legitimate exemption. It does not round it up to `valid`, which would sign off on a supply that was never checked. It does not raise a `5xx` and lose the work. It states the failure (`reason`), bounds the scope (`coverage: "syntax_only"`), and — the detail that matters most — the provenance drops to `pure-computation`, because **claiming `vies` as a source when VIES did not answer would be a lie inside a paid response**. A VIES outage is never a `5xx` on this endpoint. It is a dated, motivated, bounded `200`. ## When the register does not answer: the last firm verdict Losing an answer you already hold to someone else's traffic is a bad trade. So when the live check fails and a **firm** verdict on that exact number was obtained within the last 24 hours, that verdict is served — dated by its real age. Fired six times concurrently at the same French number, production returned three live answers and three served from memory: ```json { "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-08-12T06:18:12Z", "served_from": "cache", "cached_age_secs": 157 }, "provenance": { "source": "vies", "fetched_at": "2026-08-12T06:18:12Z", "freshness": { "kind": "cached", "age_secs": 157 } } } ``` Three invariants make that answer buyable: - **Nothing is invented.** It is a fact really obtained from VIES, and `checked_at` **rewinds** to the moment it was obtained — the response never claims to have queried the register at the second of your call. - **A fresh answer is never degraded.** Memory is consulted only *after* the live check has failed, never instead of it. You cannot receive a dated verdict while a current one was available. - **An `unverifiable` never enters it.** It is not a verdict. So `cached` implies a firm `valid` or `invalid`, and carries neither `reason` nor `coverage`. The limit is stated so an agent can act on it: **a verdict may be up to 24 hours old**. Read `freshness.age_secs` and decide whether that is recent enough for what you are about to do. ## From a SIREN, without a network call French callers rarely hold the VAT number — they hold the SIREN. Pass `siren=` and the French VAT number is derived by pure computation, then checked like any other: ``` key = (12 + 3 × (SIREN mod 97)) mod 97 → "FR" + key + SIREN ``` ```json { "data": { "vat_number": "FR40303265045", "country": "FR", "syntax_valid": true, "status": "valid", "name": "SA SODIMAS", "address": "11 RUE AMPERE\n26600 PONT DE L ISERE", "derived_from_siren": "303265045", "checked_at": "2026-08-12T06:16:27.608979587Z" }, "provenance": { "source": "vies + pure-computation", "fetched_at": "2026-08-12T06:16:27.608979587Z", "freshness": { "kind": "live" } } } ``` The derivation is traced twice — in `derived_from_siren` and in the `pure-computation` half of the source — so the agent can always tell a number it supplied from a number we computed. And the obvious trap is worth naming: **a valid SIREN does not imply VAT registration**. `siren=111111118` is well-formed, passes its Luhn check, derives cleanly to `FR44111111118`, and comes back `invalid` from the register. ## What is a 4xx, and therefore unbilled Only requests the service cannot answer leave the 200 range, and none of them are billed. Syntax is validated **per member state before any network call**, so a malformed number never becomes a wasted VIES query: | Request | Result | |---|---| | neither or both of `vat` / `siren` | `400 INVALID_INPUT` | | `vat=US123456789` | `400 INVALID_INPUT` — not an EU prefix | | `vat=DE1234` | `400 INVALID_INPUT` — violates the DE national format | | `vat=FR99303265045` | `400 INVALID_CHECKSUM` — key inconsistent with its SIREN | | `siren=303265046` | `400 INVALID_CHECKSUM` — fails Luhn | Note the deliberate absence of a business `404`: a well-formed number **always** gets an answer. "Unknown" is not one of the outcomes, because the register — not this service — decides what it knows. ## Where it sits in an agent's invoicing loop The discovery-to-settlement mechanics are the standard x402 loop described in [the quickstart](/docs/quickstart) and [for agents](/docs/for-agents): call, receive a `402` carrying the terms, pay, replay. What matters here is the sequencing around it. 1. **Determine the treatment.** [`POST /invoice/vat-treatment`](/docs/api/invoice-vat-treatment) decides who charges the VAT, under which mechanism, and which compulsory mentions to print. It takes the customer's VAT number **as declared**, with zero network access, by design. 2. **Verify the number that treatment relied on.** That is this endpoint. The split is intentional: a determination engine should be deterministic and offline; the register consultation is a separate, live, dated act. 3. **Keep the dated response as the evidence.** A `valid` is an attestation **at an instant**, not a standing property — a registration can be revoked the day after. Store the response with its `checked_at`. If the agent needs identity, legal events, VAT and e-invoicing reachability in a single settlement rather than four, [`GET /company/report`](/docs/api/company-report) composes them by SIREN, each block carrying its own provenance. The live [`/catalog`](/docs/for-agents) is authoritative for the endpoint listing and pricing — never hardcode either. ## Limits, stated plainly - A verdict may be **served from our last firm check, up to 24 hours old**, always flagged `cached` and dated. It is never passed off as fresh — but it is not a check run for your call either. - **Availability is per member state**: one state down yields `unverifiable` for its numbers only. - `name` and `address` are relayed **only when the member state publishes them**. Several do not. - **No consultation proof** (the VIES `requestIdentifier`) in v1. - **No name/address consistency check.** VIES can confirm whether a given name matches a number; this endpoint relays what is published, it does not score a match. Payee-name matching on the banking side is a different question — see [`POST /preflight/payment`](/docs/api/preflight-payment). - `siren=` derivation is **French only**, and derivation is arithmetic, not registration. ## See also - [`GET /company/vat`](/docs/api/company-vat) — full parameters, response fields and error codes. - [`GET /company/resolve`](/docs/api/company-resolve) — does this legal entity exist and is it still active? - [`GET /company/events`](/docs/api/company-events) — BODACC insolvency, deregistration and business-sale announcements. - [`POST /invoice/vat-treatment`](/docs/api/invoice-vat-treatment) — who charges the VAT, and the exact mentions to print. - [Quickstart](/docs/quickstart) — the `402` → pay → replay loop end to end.