If you sell B2B inside the EU, you need to validate your customers' VAT numbers. A valid number lets you invoice without VAT under the reverse-charge rules; an invalid one means you should charge VAT. The official tool is VIES, the European Commission's VAT Information Exchange System. It is free and public, and its web form checks exactly one number at a time.
This guide shows how to check hundreds of numbers automatically, and the four things that will trip you up when you do.
Disclosure: I built the EU VAT Number Validator used in the examples. Everything it returns comes straight from the official VIES service; the gotchas below apply no matter which tool you use.
Option 1: call VIES yourself
VIES has a REST endpoint. A minimal Python check looks like this:
import httpx def check_vat(country: str, number: str) -> dict: r = httpx.post( "https://ec.europa.eu/taxation_customs/vies/rest-api/check-vat-number", json={"countryCode": country, "vatNumber": number}, timeout=30, ) r.raise_for_status() return r.json() print(check_vat("IE", "6388047V")) That works for a handful of numbers. For a real customer list you will hit the gotchas below within minutes.
The 4 gotchas
1. Member states go offline, and VIES tells you so
VIES does not hold the data itself. It forwards every request to the tax authority of the country in question. When that national system is down or busy, VIES answers with errors such as MS_UNAVAILABLE, TIMEOUT or SERVICE_UNAVAILABLE. This is not "invalid". It means "ask again later".
Fix: treat these as a separate status, retry with backoff (for example after 2, 4 and 8 seconds), and never mark a customer's number as invalid because a national server was busy.
2. Germany (and others) never return a name or address
For some countries VIES only answers "valid" or "invalid", and the name and address come back as ---. In my test, the VAT number of SAP SE (DE143454214) came back valid with no name and no address, while Google Ireland (IE6388047V) came back valid with GOOGLE IRELAND LIMITED and its Dublin address.
Fix: don't build your process around name matching for every country. Store null instead of ---.
3. Formats differ per country, so check them before calling VIES
Every EU country has its own VAT number format. DE is 9 digits, NL ends in B plus two digits, FR has two check characters up front, and Greece uses the prefix EL, not GR. Northern Ireland has its own prefix, XI. Customers type numbers with spaces, dots and dashes, or without a prefix at all.
Fix: normalise first (strip spaces, dots and dashes, uppercase, map GR to EL), then run a per-country format check. A number like DE12345 can be rejected locally, without spending a VIES call or waiting on a timeout.
4. "Valid" today is not proof forever unless you keep a consultation number
If you pass your own VAT number as the requester, VIES returns a request identifier (consultation number). It can serve as evidence that you checked the number on that date. Without it you only have your own log entry.
Fix: always send your requester VAT number and store requestIdentifier and requestDate with the customer record.
Option 2: do it in bulk with one call
The validator wraps all four fixes: normalisation, format pre-checks for all 27 EU countries plus XI, retries for unavailable member states, --- turned into null, and the consultation number when you pass yours:
curl -X POST "https://api.apify.com/v2/acts/fast_data_asap~eu-vat-number-validator/run-sync-get-dataset-items?token=YOUR_APIFY_TOKEN" \ -H "Content-Type: application/json" \ -d '{ "vatNumbers": ["IE6388047V", "DE 143 454 214", "DE12345"], "requesterVatNumber": "DE123456789" }' These were the real results of my test run (3.8 seconds):
| Input | Status | Name | Charged |
|---|---|---|---|
IE6388047V | valid | GOOGLE IRELAND LIMITED | yes |
DE143454214 | valid | null (Germany shares no name) | yes |
DE12345 | format_error | – | no |
Each row also has countryCode, vatNumber, address, requestDate, requestIdentifier and an error text. It costs $0.001 per number VIES actually answered; format errors and unavailable member states are free.
Replace DE123456789 with your own VAT number; the placeholder will not give you a valid consultation number.
Automating it in n8n or Make
A common setup is a weekly re-check of all B2B customers:
- Schedule Trigger (weekly) → Google Sheets / CRM node reads customer VAT numbers.
- HTTP Request node:
POSTthe list to the endpoint above, with your Apify token in a Header Auth credential (Authorization: Bearer …). - IF node on
status:invalid→ create a task for accounting;service_unavailable→ retry next run;valid→ writerequestIdentifierand date back to the sheet.
That turns a manual, one-by-one job into a weekly report that only lists the numbers someone actually needs to look at.
VIES results come from the European Commission's public service. This article is about the technical process, not tax advice. Ask your tax adviser how the reverse-charge rules apply to your business.
