EU VAT Validation API: Using VIES Correctly, Including the Parts That Trip Everyone Up

VIES is the only authoritative source for EU VAT validation, and it has three behaviours that quietly corrupt naive integrations: it answers 200 for everything, two member states withhold identity, and national systems go offline. Here's how to handle all three.
TL;DR
- •VIES is the European Commission's VAT Information Exchange System — the only authoritative check. It forwards each query to the member state's own tax database in real time.
- •VIES answers HTTP 200 for everything. The real outcome lives in a userError field: VALID, INVALID, INVALID_INPUT, or one of several 'system is down' codes. Treating 200 as success is the classic bug.
- •Germany and Spain never disclose trader name and address through VIES. That is policy, not missing data — so the response flags it with name_disclosed rather than returning an empty string.
- •Greece files VAT under EL, not its ISO code GR. Northern Ireland is XI. Great Britain left VIES after Brexit and is not covered.
- •When a member state's system is offline the answer is 'ask again later', not 'invalid'. That returns 503 and is never charged — billing a customer for a Belgian maintenance window is not acceptable.
One source, three traps
If you sell B2B across EU borders, you have to validate VAT numbers, and there is exactly one authoritative place to do it: VIES, the European Commission's VAT Information Exchange System. VIES does not hold a database of its own. It forwards each query, in real time, to the tax authority of the member state that issued the number. That is what makes it authoritative — and also what makes it behave unlike any other API you integrate.
Three of its behaviours quietly break naive integrations. Each one produces a bug that looks like a data problem rather than an integration problem, which is why they survive so long in production.
Trap 1: everything is HTTP 200
VIES returns 200 whether the number is valid, invalid, malformed, or the member state is on fire. The real outcome lives in a userError field: VALID, INVALID, INVALID_INPUT, or one of several unavailability codes. Any client that treats a 200 as success and reads only isValid will read an outage as a customer with a fake VAT number.
EU VAT Validator translates that into ordinary HTTP semantics: a real answer is 200, malformed input is 400, and an unavailable member state is 503 — uncharged, because you should not pay for a Belgian maintenance window.
curl "https://www.apipick.com/api/validate-vat?vat_number=IE6388047V" \
-H "x-api-key: $APIPICK_KEY"
{
"vat_number": "IE6388047V",
"country_code": "IE",
"number": "6388047V",
"valid": true,
"name": "GOOGLE IRELAND LIMITED",
"address": "3RD FLOOR, GORDON HOUSE, BARROW STREET, DUBLIN 4",
"name_disclosed": true,
"consultation_number": null,
"source": "European Commission VIES"
}Trap 2: Germany and Spain withhold the name
Validate a German number and the trader name comes back empty. The number is confirmed registered — but DE and ES do not disclose name and address through VIES as a matter of policy.
{
"vat_number": "DE811907980",
"valid": true,
"name": null,
"address": null,
"name_disclosed": false
}The practical consequence: do not build onboarding that requires a trader name to proceed. A German customer with a perfectly valid VAT number will never supply one through this route, and blocking them is your bug, not theirs.
Trap 3: Greece is EL
For VAT purposes Greece files under EL, not its ISO 3166 code GR. If your signup form validates the country prefix against an ISO list — which is the obvious thing to do — every Greek customer is rejected before the request ever leaves your server. Northern Ireland is XI under the Windsor Framework, and Great Britain left VIES after Brexit, so GB numbers cannot be validated here at all.
The endpoint accepts the full number with its prefix, strips the spaces, dots and hyphens that invoices habitually carry, and rejects a non-member prefix with a 400 that names the valid set rather than silently failing.
Reverse charge, and why you keep the receipt
The commercial reason this matters: on a cross-border B2B sale within the EU you zero-rate the invoice and the buyer accounts for the VAT. The burden of having verified the buyer's registration sits with you. If the number turns out to be invalid and you cannot show you checked, the tax authority can come after the unpaid VAT.
So store the evidence, not just the boolean. When VIES issues a request identifier it comes back as consultation_number, and that is what a tax authority accepts as proof you verified on a given date. Persist it with the invoice.
import httpx, os
def verify_for_invoice(vat_number: str) -> dict:
r = httpx.get("https://www.apipick.com/api/validate-vat",
params={"vat_number": vat_number},
headers={"x-api-key": os.environ["APIPICK_KEY"]})
if r.status_code == 503:
raise RetryLater(r.json()["message"]) # outage, not an invalid number
if r.status_code == 400:
raise MalformedVat(r.json()["message"]) # fix the input, do not retry
r.raise_for_status()
d = r.json()
return {
"valid": d["valid"],
"legal_name": d["name"], # may be None by policy
"name_withheld": d["valid"] and not d["name_disclosed"],
"proof": d["consultation_number"], # keep with the invoice
"checked_at": d["request_date"],
}Where it fits with the rest of onboarding
A VAT number tells you a business is registered for cross-border trade. It does not tell you the corporate identity behind it, and it covers only the EU. For counterparty identity — official legal name, jurisdiction, national register number, ownership — pair it with LEI Entity Lookup, which is global and published under CC0. For US public companies, Company Facts covers the SEC side.
Build vs. call
| VIES directly | API Pick | |
|---|---|---|
| Outcome signalling | 200 for everything, read userError | Real HTTP status codes |
| Outage handling | You classify 5 unavailability codes | 503, uncharged |
| Non-disclosure | Literal "---" strings | null + name_disclosed flag |
| Input cleaning | You strip spaces, dots, hyphens | Handled |
| EL / XI / GB | You encode the exceptions | Validated with a clear error |
| Cost | Free, plus the edge cases | 3 credits/call, only on success |
VIES itself is free and you are welcome to call it directly — it is the same authority either way. What you are buying is the translation layer and the discipline of not charging you when Europe's tax infrastructure is having a quiet afternoon. A free key comes with 100 credits and no card.
Frequently Asked Questions
Is this the official EU check, or a database copy?
It is the official check. The request goes to VIES, the European Commission's VAT Information Exchange System, which forwards the query in real time to the tax database of the member state that issued the number. There is no intermediate copy of the register — the answer comes from the tax authority itself, which is why it matches what the Commission's own web form returns and why it is acceptable as a compliance check.
Why is the trader name empty for German and Spanish numbers?
Germany and Spain do not disclose trader name and address through VIES as a matter of policy. Validity is still confirmed unambiguously; only the identity fields are withheld. The response sets name_disclosed to false so your code can distinguish a policy-based redaction from a genuinely empty field. Storing an empty string as though the trader had no name is how this quietly corrupts a CRM.
Why does Greece use EL instead of GR?
For VAT purposes Greece files under EL, a convention that predates and diverges from its ISO 3166 code GR. If your form validates country prefixes against an ISO list, every Greek VAT number will be rejected before it ever reaches VIES. Northern Ireland uses XI under the Windsor Framework, and Great Britain left VIES after Brexit so GB numbers cannot be validated here at all.
What should happen when a member state's system is down?
The correct answer is 'ask again later', never 'invalid'. VIES routes to each member state's own database and those go offline for maintenance, returning codes like MS_UNAVAILABLE. Treating that as an invalid number will block a legitimate customer at checkout. This endpoint maps those codes to HTTP 503 with a clear message and charges nothing, so a retry costs you no credits.
Can I keep a consultation number as proof for an audit?
Yes, when VIES issues one it comes back in the consultation_number field. That identifier is what a tax authority accepts as evidence that you verified a counterparty's VAT status on a specific date — which matters for reverse-charge invoicing, where the burden of having checked sits with you. Store it alongside the invoice, not just the boolean result.
APIs used in this article
Sarah Choy is the CEO of API Pick. She writes about building production-ready APIs for AI agents and LLM workflows.