How to Validate EU VAT Numbers Programmatically

If you sell B2B in the EU, you need to validate your customer's VAT number before applying the reverse charge mechanism. Getting this wrong means you either charge VAT when you shouldn't (annoying your customer) or skip it when you should (creating a tax liability). This guide covers how to validate VAT numbers programmatically using a REST API, with examples for EU, UK, and other supported regions.

Why VAT validation matters

Under EU VAT rules, B2B cross-border transactions within the EU can be zero-rated if the buyer provides a valid VAT identification number. This is the "reverse charge" mechanism: the tax obligation shifts from the seller to the buyer.

To apply the reverse charge, you must verify that the buyer's VAT number is valid and active at the time of the transaction. "Valid format" isn't enough. The number must be registered and active with the relevant tax authority. This is also important for fraud prevention: fake VAT numbers are a common vector for VAT fraud.

How VIES works

The European Commission operates VIES (VAT Information Exchange System), a service that checks VAT numbers against national tax authority databases across all 27 EU member states. When you query VIES with a VAT number, it routes the request to the relevant country's tax authority and returns whether the number is valid.

VIES exposes a SOAP/XML endpoint. A typical request looks like this:

POST https://ec.europa.eu/taxation_customs/vies/services/checkVatService
Content-Type: text/xml

<soapenv:Envelope xmlns:soapenv="http://schemas.xmlsoap.org/soap/envelope/"
  xmlns:urn="urn:ec.europa.eu:taxud:vies:services:checkVat:types">
  <soapenv:Body>
    <urn:checkVat>
      <urn:countryCode>DE</urn:countryCode>
      <urn:vatNumber>123456789</urn:vatNumber>
    </urn:checkVat>
  </soapenv:Body>
</soapenv:Envelope>

The response is XML containing a valid boolean, the company name, and address (when available).

Pain points of using VIES directly

VIES works, but building a production integration against it is painful:

  • SOAP/XML: You need to construct XML payloads and parse XML responses. Most modern stacks don't have great SOAP support.
  • Inconsistent availability: VIES depends on each member state's national service. Some countries (Italy, Spain) have frequent downtime windows. There's no SLA.
  • No UK support: Since Brexit, UK VAT numbers aren't in VIES. You need a separate integration with HMRC's API.
  • No caching: If VIES is down for a country, your validation fails. You need to build your own caching layer.
  • No test mode: You're always hitting the live service, making integration testing unreliable.

The modern approach: using a REST API

Instead of integrating with VIES directly, you can use a REST API that wraps VIES (and HMRC) and handles caching, error handling, and normalization for you.

Here's a validation request using curl and Vatly:

curl https://api.vatly.dev/v1/validate?vat_number=DE123456789 \
  -H "Authorization: Bearer vtly_live_your_api_key"

The response is structured JSON:

{
  "data": {
    "valid": true,
    "vat_number": "DE123456789",
    "country_code": "DE",
    "company": {
      "name": "ACME GmbH",
      "address": "Musterstraße 1, 10115 Berlin"
    },
    "consultation_number": null,
    "requested_at": "2026-03-17T10:30:00Z"
  },
  "meta": {
    "request_id": "550e8400-e29b-41d4-a716-446655440000",
    "request_duration_ms": 1240,
    "cached": false
  }
}

Code examples

TypeScript / Node.js

const response = await fetch(
  "https://api.vatly.dev/v1/validate?vat_number=DE123456789",
  {
    headers: {
      Authorization: "Bearer vtly_live_your_api_key",
    },
  }
);

const { data, meta } = await response.json();

if (data.valid) {
  console.log(`Valid VAT: ${data.company.name}`);
  if (meta.cached) {
    console.log("Result served from cache");
  }
} else {
  console.log("Invalid VAT number");
}

Python

import requests

response = requests.get(
    "https://api.vatly.dev/v1/validate",
    params={"vat_number": "DE123456789"},
    headers={"Authorization": "Bearer vtly_live_your_api_key"},
)

result = response.json()

if result["data"]["valid"]:
    print(f"Valid VAT: {result['data']['company']['name']}")
else:
    print("Invalid VAT number")

UK, Swiss, and Norwegian VAT numbers

The same endpoint handles UK, Swiss (CHE prefix), Liechtenstein (LI prefix), and Norwegian (NO prefix) VAT numbers. Vatly detects the country prefix and routes the request to the appropriate national tax authority:

curl https://api.vatly.dev/v1/validate?vat_number=GB123456789 \
  -H "Authorization: Bearer vtly_live_your_api_key"

The response format is identical. You don't need conditional logic based on the country.

Integration testing with test mode

Use a test-mode API key (prefixed vtly_test_) with magic VAT numbers to simulate different scenarios without hitting VIES or HMRC:

# Always returns valid
curl https://api.vatly.dev/v1/validate?vat_number=DE111111111 \
  -H "Authorization: Bearer vtly_test_your_test_key"

# Always returns invalid
curl https://api.vatly.dev/v1/validate?vat_number=DE000000000 \
  -H "Authorization: Bearer vtly_test_your_test_key"

This makes your CI/CD pipeline fast and deterministic. See the Vatly documentation for the full list of magic numbers and error simulations.

Get started

Vatly offers a free tier with 500 validations per month, no credit card required. The API, response format, and caching work the same across all plans. The same endpoint also validates Swiss (CHE), Liechtenstein, Norwegian (MVA), and Australian (ABN) numbers.

Start validating for free →

Frequently asked questions

What is a VAT identification number?

A VAT identification number is a unique identifier assigned to businesses registered for Value Added Tax in the EU or UK. It consists of a two-letter country prefix followed by digits (and sometimes letters). You need to validate these numbers to determine the correct tax treatment for B2B transactions.

Can I validate UK VAT numbers with VIES?

No. Since Brexit, UK VAT numbers (GB prefix) are no longer in VIES. You need to use HMRC's separate API for UK validation. Vatly handles all supported countries through a single endpoint, routing to VIES, HMRC, or the relevant national registry automatically based on the country prefix.

How long does a VAT validation take?

A live VIES lookup typically takes 500ms to 3 seconds depending on the member state. HMRC lookups average around 1 second. Vatly's 25-day cache means repeat lookups for the same number return in under 50ms.

Do I need to validate VAT numbers for B2C sales?

No. VAT validation is only relevant for B2B transactions where you need to determine whether the reverse charge mechanism applies. For B2C sales, you charge VAT at the applicable rate regardless of the buyer's location.