VIES explained: how the EU VAT aggregator actually works
A deep dive into the VAT Information Exchange System, the SOAP protocol it speaks, the national registries it fronts, and how vatverify smooths over its quirks.
Key facts
- VIES fronts 27 national VAT registries plus Northern Ireland (XI) through a single SOAP gateway run by the European Commission (DG TAXUD).
- The public endpoint lives at
https://ec.europa.eu/taxation_customs/vies/services/checkVatService. There is no REST mirror. - Responses vary in shape per country: some return structured street/postcode/city fields, others return a single concatenated address string.
- Maintenance windows fall on Monday mornings (roughly 03:00 to 05:00 UTC); individual member states can go offline independently of the gateway.
- Passing
requesterCountryCodeandrequesterVatNumberupgrades the call to an authenticated consultation and returns arequestIdentifier(the consultation number) that you can cite in a tax audit.
What the registry is
VIES (the VAT Information Exchange System) has been the European Commission's public-facing gateway to national VAT registries since the early 1990s. It exists because intra-Community B2B trade works on reverse-charge: a seller in Spain invoicing a buyer in Germany zero-rates the invoice and the buyer accounts for the VAT at their own national rate. That mechanism only stands up if the seller can prove the buyer was VAT-registered at the time of supply. VIES is the proof mechanism.
The gateway is operated by DG TAXUD (Taxation and Customs Union, part of the European Commission). The legal basis sits in Council Regulation (EU) No 904/2010 on administrative cooperation in the field of value-added tax, and national registries are obliged to expose their registration data through it. VIES itself does not store VAT numbers; it is a routing layer that proxies each lookup to the right member-state backend, reads whatever shape that backend returns, and wraps it in a common SOAP envelope.
That shared envelope is the only thing standardised. Everything behind it is national, which is why consuming VIES well is less about SOAP and more about country-specific quirks.
What the API returns
The protocol is SOAP 1.1. A checkVat request is a small XML envelope:
<?xml version="1.0"?>
<soap:Envelope xmlns:soap="http://schemas.xmlsoap.org/soap/envelope/">
<soap:Body>
<checkVat xmlns="urn:ec.europa.eu:taxud:vies:services:checkVat:types">
<countryCode>IE</countryCode>
<vatNumber>6388047V</vatNumber>
</checkVat>
</soap:Body>
</soap:Envelope>POST it with Content-Type: text/xml; charset=utf-8 and an empty SOAPAction header. A successful response contains valid, name, address, requestDate, plus the echoed countryCode and vatNumber:
<checkVatResponse xmlns="urn:ec.europa.eu:taxud:vies:services:checkVat:types">
<countryCode>IE</countryCode>
<vatNumber>6388047V</vatNumber>
<requestDate>2026-05-18+02:00</requestDate>
<valid>true</valid>
<name>GOOGLE IRELAND LIMITED</name>
<address>3RD FLOOR, GORDON HOUSE, BARROW STREET, DUBLIN 4</address>
</checkVatResponse>The checkVatApprox operation is the authenticated variant. You pass your own requesterCountryCode and requesterVatNumber, the gateway logs the consultation, and the response carries a requestIdentifier (11 alphanumeric characters) that you can store on the invoice line. In the event of an audit, this is the consultation number that proves you verified the buyer's number at the time of supply.
Two details worth calling out in the response shape:
- The
addressfield is country-defined. Germany returns it as a structured element withOrt,PLZ,Strasse. Ireland returns a single comma-separated string. France sometimes omits it entirely. - Legal names come through in UTF-8. An XML parser defaulting to Latin-1 will mojibake German umlauts, Spanish ñ, and Danish å.
vatverify normalises both before handing anything back to the caller, so the output of /v1/validate follows the same schema whether the upstream was a German Ort field or an Irish string.
Rate limits and quirks
VIES has no published rate limit. In practice, the gateway trips HTTP 503s after roughly sixty concurrent requests from a single IP, and a sustained ceiling of about 10 requests per second is what most callers can hold without back-pressure. Batched calls with requesterVatNumber get throttled harder because the authenticated consultation path does extra work on the backend.
Observed failure modes:
SERVICE_UNAVAILABLE: the gateway itself is down. Typically aligns with the Monday-morning maintenance window but can happen at other times.MS_UNAVAILABLE: the gateway is up but the national registry for the requested country is unreachable. This is the most common fault; Spain, Germany, and France trend the highest on the per-country outage ledger.MS_MAX_CONCURRENT_REQ: you asked for too many concurrent lookups against a single country. Back off.TIMEOUT: the gateway gave up waiting on the national backend. Treat as transient.INVALID_INPUT: format rejection before any registry call. Always run a checksum locally first to cut this down.
Country-specific behaviours to expect: Italy returns HTTP 200 with valid: false for numbers that used to exist but have been deregistered, without a deregistration date. Greece uses the prefix EL, not the ISO GR, for historical reasons predating the ISO 3166 alignment. The Netherlands reissued the VAT numbers of sole proprietors in January 2020 so the 9-digit core would no longer be derived from the holder's BSN (citizen number); the overall format (NL + 9 digits + B + 2 digits) did not change, but legacy-to-new number mappings were issued and some third-party validators took a while to accept the new randomised numbers.
How vatverify handles it
Every /v1/validate call that lands on a VIES country runs through a short pipeline before any network work:
- Normalise: strip spaces, dots, and hyphens; uppercase the prefix. Greek numbers are expected to arrive with the VIES-native
ELprefix;GRinputs returncountry_unsupportedfrom the normaliser. - Format check: the normalised input must look like a two-letter prefix followed by an alphanumeric body. Inputs that fail the shape check return
invalid_formatwithout hitting VIES. - Cache lookup: Upstash Redis, keyed per registry and VAT number. Freshness is 30 days for valid results and 24 hours for invalids, with a seven-day fallback window past freshness used to serve degraded responses when VIES is unreachable.
Country-specific checksum helpers (the MOD-11 family, MOD-97, Luhn variants for Italy and Sweden, and others) ship in @vatverify/vat-rates for clients that want to reject obvious typos offline before calling the API.
A cache miss hits the VIES SOAP endpoint with a 10-second timeout. On MS_UNAVAILABLE, TIMEOUT, or other upstream faults, the pipeline checks for a previously-cached value for the same number. If one exists, it is served with meta.source_status: "degraded" so the caller can decide whether to retry later. If nothing is cached, the API returns a structured registry_unavailable error with a 502 status.
Passing requester_vat_number on the request triggers the authenticated VIES consultation path and persists the requestIdentifier in the response as verify_id. This is the number you save alongside your invoice record to satisfy an audit.
The batch endpoint (POST /v1/validate/batch) applies the same pipeline to each item. Batches without a requester_vat_number run every item in parallel because most resolve from cache; when a requester is set, each item hits VIES's authenticated consultation path directly, so the batch runs through a pool of five concurrent workers to avoid tripping MS_MAX_CONCURRENT_REQ. Cached items inside a batch do not count against your monthly quota; only cache misses do. When VIES is in maintenance, batches with warm-cache coverage continue to serve, while genuinely-cold items return source_status: "degraded" or registry_unavailable on a per-item basis.
Gotchas worth knowing
- Greece is
EL, notGR. VIES only recognisesELfor Greek VAT numbers, and vatverify follows VIES's alpha-2 set;GR-prefixed inputs returncountry_unsupported. If you let a user type country codes freely, mapGRtoELon your side before calling the API. - Northern Ireland is
XI, notGB. Post-Brexit, GB numbers are UK only and validate through HMRC. XI numbers stayed in the EU VAT area for goods under the Windsor Framework and still validate through VIES. valid: falseis not always permanent. Registries routinely lag behind tax-authority decisions by up to 24 hours. Rerunning a rejected lookup the next day will sometimes flip it totrueonce the backend has caught up.- The
requestDateis the member-state query date, not a source-of-truth timestamp. Do not use it as the authoritative registration date of the business.
See the API reference
- API reference:
GET /v1/validate - Glossary: VIES, consultation number, checksum, VAT number
- Error surface:
registry_unavailable,country_unsupported,invalid_format - Related guides: Handle VIES downtime, VIES API guide, HMRC VAT API, EU national databases