VAT Number Validation with VIES: A Programmatic Guide
KVKBase Team

VAT Number Validation with VIES: A Programmatic Guide

Learn how to validate Dutch and European VAT numbers using the VIES API. Includes code examples, error handling, and caching strategies.

vatviesvalidation

VAT Number Validation with VIES: A Programmatic Guide

If you are building a B2B platform or processing invoices within the EU, VAT number validation is unavoidable. It is not just a technical requirement — it is a legal one. For intra-community supplies, you are required to verify your customer’s VAT number.

In this article we explain how the VIES system works, how to call it programmatically, and which pitfalls to avoid.

What is VIES?

VIES stands for VAT Information Exchange System. It is a service provided by the European Commission that lets you verify whether a VAT number is valid and active. Each EU member state supplies its own data to the system.

When you check a VAT number via VIES, you get back:

  • Whether the number is valid (yes/no)
  • The name of the company (in most countries)
  • The address of the company (in most countries)

The format of a Dutch VAT number

A Dutch VAT number follows a specific format:

NL + 9 digits + B + 2 digits
Example: NL123456789B01

Before sending a request to VIES, you can validate the format locally:

function isValidDutchVatFormat(vatNumber) {
  return /^NL\d{9}B\d{2}$/.test(vatNumber.replace(/[\s.]/g, ''));
}

// Examples
isValidDutchVatFormat("NL123456789B01"); // true
isValidDutchVatFormat("NL12345678B01");  // false (too few digits)
isValidDutchVatFormat("DE123456789");    // false (not Dutch)

Other EU countries use their own formats. Germany uses DE + 9 digits, Belgium uses BE + 10 digits, and so on.

Calling the VIES API

The European Commission provides a REST API for VIES validation:

curl -X POST "https://ec.europa.eu/taxation_customs/vies/rest-api/check-vat-number" \
  -H "Content-Type: application/json" \
  -d '{
    "countryCode": "NL",
    "vatNumber": "123456789B01"
  }'

The response looks roughly like this:

{
  "isValid": true,
  "requestDate": "2025-10-21",
  "userError": "VALID",
  "name": "VOORBEELD B.V.",
  "address": "VOORBEELDSTRAAT 1\n1234AB AMSTERDAM",
  "requestIdentifier": ""
}

JavaScript implementation

async function validateVatNumber(countryCode, vatNumber) {
  const response = await fetch(
    'https://ec.europa.eu/taxation_customs/vies/rest-api/check-vat-number',
    {
      method: 'POST',
      headers: { 'Content-Type': 'application/json' },
      body: JSON.stringify({ countryCode, vatNumber })
    }
  );

  if (!response.ok) {
    throw new Error(`VIES API error: ${response.status}`);
  }

  return response.json();
}

// Usage
const result = await validateVatNumber('NL', '123456789B01');
if (result.isValid) {
  console.log(`Valid! Company: ${result.name}`);
} else {
  console.log('Invalid VAT number');
}

Common problems with VIES

1. Downtime and availability

VIES depends on the systems of each member state. It is common for a specific country to be temporarily unavailable. You will then receive an error such as MS_UNAVAILABLE or SERVICE_UNAVAILABLE.

This does not mean the number is invalid — it means the check could not be performed at that moment.

2. Timeouts

The VIES API can respond slowly, sometimes taking 10 seconds or longer. Set a generous timeout and inform your users that validation may take a moment:

const controller = new AbortController();
const timeout = setTimeout(() => controller.abort(), 15000);

try {
  const response = await fetch(url, {
    method: 'POST',
    headers: { 'Content-Type': 'application/json' },
    body: JSON.stringify({ countryCode, vatNumber }),
    signal: controller.signal
  });
  // process response
} catch (error) {
  if (error.name === 'AbortError') {
    console.log('VIES timeout - please try again later');
  }
} finally {
  clearTimeout(timeout);
}

3. Rate limiting

VIES does not have officially documented rate limits, but sending too many requests in a short time can get you blocked. Always implement a queue for bulk validations.

Caching strategy

Because VIES validation can be slow and unreliable, caching is essential:

async function cachedVatValidation(countryCode, vatNumber) {
  const cacheKey = `vat:${countryCode}:${vatNumber}`;
  const cached = await cache.get(cacheKey);

  if (cached) {
    const age = Date.now() - cached.timestamp;
    // Valid numbers: cache for 7 days
    // Invalid numbers: cache for 1 hour (could be temporary)
    const maxAge = cached.isValid ? 7 * 86400000 : 3600000;
    if (age < maxAge) return cached;
  }

  const result = await validateVatNumber(countryCode, vatNumber);
  await cache.set(cacheKey, { ...result, timestamp: Date.now() });
  return result;
}

The logic behind this: a valid VAT number rarely changes status, so a 7-day cache is safe. An invalid result may be caused by downtime, so you cache it for a shorter period.

Why VAT validation matters

VAT validation is not optional for B2B transactions within the EU. For intra-community supplies (ICS) you must:

  1. Verify your customer’s VAT number
  2. Include the verified number on your invoice
  3. File the ICS declaration correctly with the tax authority

Without valid verification, you risk being liable for VAT on the supply.

Combine KVK and VAT validation

In practice, you often want to validate both the KVK number and the VAT number of a Dutch company. With KVKBase, you can combine this into a single flow: look up the company by KVK number, and you get the associated VAT number directly. You can then validate the VAT number via VIES for EU registration purposes.

Conclusion

VAT number validation via VIES is indispensable for any B2B application operating within the EU. The API is free and public, but not always reliable. Invest in solid error handling, caching, and a fallback strategy to give your users a dependable experience.

By combining KVK lookups and VAT validation, you cover both Dutch business registration and EU-wide tax identification in a single flow.