How to Validate EU VAT Numbers with the VIES API
VAT number validation is not optional. Since the 2020 Quick Fixes to the EU VAT Directive, a valid VAT identification number is a substantive requirement for zero-rating intra-community supplies under Art. 138 and for applying the reverse charge under Art. 196. If your buyer's VAT number is invalid and you do not charge VAT, you may be liable for the missing tax. This guide covers how to validate EU VAT numbers programmatically using the VIES API.
What is VIES?
VIES (VAT Information Exchange System) is the European Commission's official service for verifying the validity of VAT identification numbers issued by EU member states. It queries the national tax databases of all 27 member states in real time and returns whether a given VAT number is currently valid, along with the registered name and address.
The Commission provides both a web interface and a REST API. The old SOAP-only service has been replaced by a modern REST API that accepts JSON requests.
The VIES REST API
The current VIES API endpoint accepts POST requests with a JSON body:
POST https://ec.europa.eu/taxation_customs/vies/rest-api/check-vat-number
{
"countryCode": "DE",
"vatNumber": "123456789"
}
The response includes:
valid— boolean indicating whether the number is currently validname— the registered company name (not always returned by all member states)address— the registered address (not always returned)requestDate— the date of the check
Important: The countryCode and vatNumber must be provided separately. Do not include the country prefix in the VAT number field. For DE123456789, use countryCode: "DE" and vatNumber: "123456789".
Python example
import requests
def validate_vat(country_code: str, vat_number: str) -> dict:
"""Validate an EU VAT number via the VIES REST API."""
url = "https://ec.europa.eu/taxation_customs/vies/rest-api/check-vat-number"
payload = {
"countryCode": country_code.upper().strip(),
"vatNumber": vat_number.strip().replace(" ", ""),
}
try:
resp = requests.post(url, json=payload, timeout=10)
resp.raise_for_status()
data = resp.json()
return {
"valid": data.get("valid", False),
"name": data.get("name", ""),
"address": data.get("address", ""),
"request_date": data.get("requestDate", ""),
}
except requests.Timeout:
return {"valid": None, "error": "VIES timeout"}
except requests.RequestException as e:
return {"valid": None, "error": str(e)}
# Usage
result = validate_vat("DE", "123456789")
if result["valid"]:
print(f"Valid: {result['name']}")
elif result["valid"] is None:
print(f"VIES unavailable: {result['error']}")
else:
print("Invalid VAT number")
JavaScript / Node.js example
async function validateVat(countryCode, vatNumber) {
const url =
"https://ec.europa.eu/taxation_customs/vies/rest-api/check-vat-number";
const payload = {
countryCode: countryCode.toUpperCase().trim(),
vatNumber: vatNumber.trim().replace(/\s/g, ""),
};
try {
const resp = await fetch(url, {
method: "POST",
headers: { "Content-Type": "application/json" },
body: JSON.stringify(payload),
signal: AbortSignal.timeout(10000),
});
if (!resp.ok) throw new Error(`HTTP ${resp.status}`);
const data = await resp.json();
return {
valid: data.valid,
name: data.name || "",
address: data.address || "",
};
} catch (err) {
return { valid: null, error: err.message };
}
}
// Usage
const result = await validateVat("FR", "12345678901");
if (result.valid) console.log("Valid:", result.name);
else if (result.valid === null) console.log("VIES error:", result.error);
else console.log("Invalid VAT number");
VAT number format by country
Before calling VIES, validate the format locally. This catches obvious errors without an API call.
| Country | Code | Format | Example |
|---|---|---|---|
| Germany | DE | 9 digits | DE123456789 |
| France | FR | 2 chars + 9 digits | FR12345678901 |
| Netherlands | NL | 9 digits + B + 2 digits | NL123456789B01 |
| Italy | IT | 11 digits | IT12345678901 |
| Spain | ES | letter + 7 digits + letter | ESX1234567X |
| Ireland | IE | 7 digits + 1-2 chars | IE1234567FA |
| Poland | PL | 10 digits | PL1234567890 |
| Sweden | SE | 12 digits | SE123456789012 |
A comprehensive regex library by country is beyond the scope of this article, but the general approach is: strip the country prefix if present, validate character count and type against the country-specific pattern, then call VIES for authoritative validation.
Common pitfalls
1. VIES downtime
VIES queries individual member state databases, and these go offline regularly — sometimes for hours. When a specific country's database is unavailable, VIES returns an error for that country while other countries work fine. Build retry logic with exponential backoff, and decide what happens when validation fails: most tax advisors recommend treating the sale as B2C if you cannot validate the VAT number.
2. Rate limiting
The Commission does not publish official rate limits, but aggressive polling can result in temporary blocks. Cache validation results for a reasonable period (24-72 hours for active customers) and batch validations where possible.
3. Including the country prefix in the number
A common mistake: sending vatNumber: "DE123456789" instead of vatNumber: "123456789". Always strip the two-letter country prefix before calling the API.
4. Not storing the validation result
You need to prove that the VAT number was valid at the time of the transaction. Store the VIES response (valid/invalid, name, address, timestamp) alongside the transaction record. This is your evidence in an audit.
Best practices
- Validate at checkout, not after. A VAT number valid last month may have been deactivated. Check in real time.
- Cache results. For subscription businesses, revalidate periodically (monthly or quarterly) rather than on every charge.
- Handle failures gracefully. If VIES is down, allow the transaction to proceed but flag it for manual review. Do not block a customer's purchase because of VIES downtime.
- Strip formatting. Remove spaces, dots, and dashes before validation.
DE 123.456.789should becomeDE+123456789. - Log everything. Timestamp, request, response, and outcome. This is your audit trail for B2B/B2C classification.
UK VAT numbers (post-Brexit)
UK VAT numbers with the GB prefix are no longer in VIES since January 1, 2021. To validate UK VAT numbers, use HMRC's separate API. You need to register on the HMRC Developer Hub to obtain API credentials. The endpoint and response format differ from VIES, so your validation logic needs to handle both systems.
Frequently asked questions
Why is VAT number validation important?
Since the 2020 Quick Fixes, a valid VAT number is a substantive requirement for zero-rating intra-community supplies and applying the reverse charge. If the buyer's number is invalid, you may be liable for the VAT.
Is the VIES API free to use?
Yes. The VIES service is free, with no API keys or authentication required. However, it can be unreliable during peak hours or when individual member state databases are down.
How do I validate UK VAT numbers after Brexit?
UK VAT numbers (GB prefix) are no longer in VIES. Use HMRC's separate VAT API, which requires registration on the HMRC Developer Hub for API credentials.
DeterminedAI validates VAT numbers automatically as part of transaction processing. Every determination includes B2B/B2C classification with full audit trail.