vatverify home
All guides

The VIES API guide: how EU VAT validation actually works

How VIES (VAT Information Exchange System) works under the hood. SOAP/XML, rate limits, downtime patterns, and why wrapping it with vatverify is 10× simpler than hitting it directly.

TL;DR

  • VIES is the authoritative EU VAT registry, operated by the European Commission.
  • It speaks SOAP/XML only. There is no REST endpoint.
  • 27 EU member states plus Northern Ireland (XI prefix) = 28 distinct registries behind one aggregator.
  • vatverify wraps all of it: REST in, structured JSON out, retries handled, downtime cached.
SDK status

@vatverify/node (used in the Node example below) ships on npm at the API launch. Reading this before launch? The REST endpoint is live today. Swap any vat.validate({ vat_number }) call for fetch('https://api.vatverify.dev/v1/validate?vat_number=...') with an Authorization: Bearer header. The Python and cURL snippets on this page work today without any package install.

What is VIES?

VIES has been the Commission's public-facing gateway to member-state VAT registrations since the early 1990s. It was built to support intra-Community trade, specifically to let a seller confirm that a buyer's VAT number is valid before applying zero-rate (reverse-charge) treatment on a B2B cross-border invoice. Getting that confirmation wrong is an audit risk: if you zero-rate an invoice for a number that wasn't registered at the time, the VAT liability can land back on you.

How VIES is structured

VIES is not a database. It is an aggregator. When you send a checkVat request, the VIES gateway proxies the query to the national registry for that country code and relays the response. There is no unified schema: each country controls the format of its own response. Germany returns the company name and address as separate fields; Ireland returns a single combined address string; some countries return no name at all on certain number types. That inconsistency is one of the main reasons wrapping VIES is non-trivial.

The SOAP/XML protocol

VIES exposes a single SOAP 1.1 endpoint:

https://ec.europa.eu/taxation_customs/vies/services/checkVatService

A minimal checkVat request envelope looks like this:

<?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>

You POST this with Content-Type: text/xml; charset=utf-8 and a SOAPAction: "" header. The response is also XML. You then parse the SOAP envelope, navigate to the checkVatResponse element, and extract valid, name, address, and requestDate. That is four steps before you have a usable value in your application.

The response

A successful response for the IE example above looks like:

<soap:Envelope xmlns:soap="http://schemas.xmlsoap.org/soap/envelope/">
  <soap:Body>
    <checkVatResponse xmlns="urn:ec.europa.eu:taxud:vies:services:checkVat:types">
      <countryCode>IE</countryCode>
      <vatNumber>6388047V</vatNumber>
      <requestDate>2026-04-27+01:00</requestDate>
      <valid>true</valid>
      <name>GOOGLE IRELAND LIMITED</name>
      <address>3RD FLOOR, GORDON HOUSE, BARROW STREET, DUBLIN 4</address>
    </checkVatResponse>
  </soap:Body>
</soap:Envelope>

Note that Ireland returns name and address as two distinct fields, but the address is a single concatenated string. Germany, by contrast, returns structured fields (street, postcode, city) in the address element. You need country-aware parsing logic to handle this correctly.

Country coverage

VIES covers all 27 EU member states:

AT, BE, BG, CY, CZ, DE, DK, EE, EL (Greece), ES, FI, FR, HR, HU, IE, IT, LT, LU, LV, MT, NL, PL, PT, RO, SE, SI, SK

Plus Northern Ireland (XI) under the post-Brexit Windsor Framework protocol. XI numbers are validated through VIES even though the rest of the UK (GB prefix) routes through the HMRC REST API.

Rate limits

VIES has no published rate limit in its documentation. In practice, the gateway starts returning HTTP 503 errors after roughly 60 concurrent requests from a single IP. The recommended sustained ceiling is 10 requests/second per IP if you are hitting VIES directly.

Every response should be cached. VIES data changes rarely. A company's registration status does not flip day to day. A 30-day cache TTL is conservative enough to stay accurate and aggressive enough to dramatically reduce your request volume.

Downtime patterns

VIES has two classes of downtime:

Scheduled maintenance: Mondays between 03:00–05:00 UTC. The gateway returns a SERVICE_UNAVAILABLE fault during this window. Plan your batch jobs around it.

Country-specific outages: Individual member-state registries go down independently of the VIES gateway itself. Spain (ES), Germany (DE), and France (FR) have historically had the worst per-country uptime. When a country registry is unavailable, VIES returns a MS_UNAVAILABLE fault code rather than a proper checkVatResponse.

vatverify handles both: if the upstream registry is degraded, the API returns meta.source_status: "degraded" alongside a cached result, so your application still gets a usable response instead of an exception.

Ireland gotchas

Ireland's VIES responses have a couple of quirks worth knowing:

  • The address field is a single string (street, city, and postcode concatenated), not structured sub-fields.
  • Legal names containing special characters (é, ß, Ø, ü) come through in UTF-8 and must be handled as such. If your XML parser defaults to ISO-8859-1, you will get mojibake.

vatverify normalizes address structure and character encoding before returning results.

VAT number format per country

Each country uses a different prefix and format. Here is a sample of the most common ones:

CountryPrefixFormatRegex
AustriaATU + 8 alphanumeric^ATU[0-9]{8}$
BelgiumBE10 digits^BE[01][0-9]{9}$
GermanyDE9 digits^DE[0-9]{9}$
FranceFR2 alphanum + 9 digits^FR[A-Z0-9]{2}[0-9]{9}$
SpainES9 alphanumeric^ES[A-Z0-9][0-9]{7}[A-Z0-9]$
ItalyIT11 digits^IT[0-9]{11}$
NetherlandsNL12 alphanumeric^NL[0-9]{9}B[0-9]{2}$
PolandPL10 digits^PL[0-9]{10}$

Full format reference including all 28 VIES countries is at /docs/api.

Wrapping VIES with vatverify

Handling VIES directly means: writing an XML serializer, a SOAP client, response parser, retry logic, exponential backoff, a cache layer, and monitoring. The vatverify equivalent is:

validate.ts
import { Vatverify } from '@vatverify/node';

const vat = new Vatverify(process.env.VATVERIFY_API_KEY!);
const result = await vat.validate({ vat_number: 'IE6388047V' });
console.log(result.data.valid, result.data.company?.name);

That is it. The SOAP envelope, parsing, retries, and caching happen inside the SDK.

validate.py
import os
import httpx

response = httpx.get(
    "https://api.vatverify.dev/v1/validate",
    params={"vat_number": "IE6388047V"},
    headers={"Authorization": f"Bearer {os.environ['VATVERIFY_API_KEY']}"},
)
result = response.json()
print(result["data"]["valid"], (result["data"].get("company") or {}).get("name"))

Or with cURL if you just need a quick check:

curl "https://api.vatverify.dev/v1/validate?vat_number=IE6388047V" \
  -H "Authorization: Bearer $VATVERIFY_API_KEY"

Direct VIES vs vatverify

FeatureDirect VIESvatverify
ProtocolSOAP/XMLREST/JSON
CachingNone (you build it)30-day automatic
RetriesNone (you build it)Automatic with backoff
Error codesSOAP faults (string)Structured JSON errors
Sustained throughput~60 req/sec before 503Unbounded (cached)
Downtime handlingYour problemmeta.source_status: "degraded" fallback
MonitoringNoneVercel observability dashboard
Response schemaCountry-inconsistent XMLNormalized JSON every time

FAQ

Can I call VIES directly without vatverify?

Yes. The endpoint is public. But you take on the SOAP client, XML parsing, retry logic, rate-limit handling, cache layer, monitoring, and country-specific response normalization yourself. For a prototype that is fine; for production that is a non-trivial ops surface.

Does VIES validate check-digits?

No. VIES only verifies that a number is in the national registry. It does not run checksum algorithms. A structurally valid number that fails its country's checksum algorithm will still return valid: true from VIES if it happens to exist in the registry. Use @vatverify/vat-rates for offline format and checksum validation before you send requests upstream.

Is VIES free?

Yes. The European Commission provides VIES at no cost. You pay in engineering time to handle the protocol, reliability, and normalization, not in money.

How often is VIES data updated?

It depends on the member state. Some countries push real-time changes; others batch-update overnight (T+1). Most countries are within 24 hours of the source of truth. France and Germany tend to be close to real-time; some smaller registries update once a day.

What happens on MS_UNAVAILABLE?

The country registry is down. VIES returns a SOAP fault with code MS_UNAVAILABLE. vatverify intercepts this, serves the last cached result with meta.source_status: "degraded" if one exists, and otherwise returns a clean HTTP 502 with a registry_unavailable error code rather than an unhandled exception.

Validate VAT in three lines.

Free up to 500 requests per month. No credit card.

Start free