1. Normalize and check the format
Customers type VAT numbers with spaces, dots and dashes. Normalize first, then match the country’s pattern (all formats):
const FORMATS = {
DE: /^DE\d{9}$/,
FR: /^FR[0-9A-HJ-NP-Z]{2}\d{9}$/,
NL: /^NL\d{9}B\d{2}$/,
// … one entry per country
};
function looksValid(input) {
const vat = input.toUpperCase().replace(/[\s.\-]/g, "");
const pattern = FORMATS[vat.slice(0, 2)];
return Boolean(pattern && pattern.test(vat));
}
2. Check digits (Python)
python-stdnum implements the official check-digit algorithms of every EU country:
from stdnum.eu import vat
vat.is_valid("DE 136 695 976") # True
vat.compact("DE 136 695 976") # 'DE136695976'
3. Check the registration with an API
JavaScript (Node.js)
const res = await fetch(
`https://api.vatcheckapi.com/v2/check?vat_number=${encodeURIComponent(vat)}`,
{ headers: { apikey: process.env.VATCHECKAPI_KEY } },
);
const result = await res.json();
if (result.registration_info?.is_registered) {
// valid: apply the reverse charge, store result.registration_info.checked_at
}
Python
import os, requests
r = requests.get(
"https://api.vatcheckapi.com/v2/check",
params={"vat_number": "LU26375245"},
headers={"apikey": os.environ["VATCHECKAPI_KEY"]},
timeout=30,
)
info = r.json().get("registration_info", {})
print(info.get("is_registered"), info.get("name"))
PHP
$ch = curl_init('https://api.vatcheckapi.com/v2/check?vat_number=' . urlencode($vat));
curl_setopt_array($ch, [
CURLOPT_RETURNTRANSFER => true,
CURLOPT_HTTPHEADER => ['apikey: ' . getenv('VATCHECKAPI_KEY')],
CURLOPT_TIMEOUT => 30,
]);
$result = json_decode(curl_exec($ch), true);
$registered = $result['registration_info']['is_registered'] ?? false;
Handle errors
- 422: the number could not be validated (wrong format or unsupported country). Show the format hint to the user.
- 503: the national service behind VIES is unavailable. Accept the order, mark the number as unchecked and retry later.