Tutorial · Python & Node
VAT number validation API (VIES) — Python & Node
One REST call validates an EU VAT number against the official VIES service across the 27 EU states + XI (Northern Ireland), and returns the registered name and address when it's valid. No SOAP, no scraping.
The problem
You're onboarding an EU B2B counterparty, or issuing a cross-border invoice under the reverse-charge rule, and you need to confirm two things: that the VAT number is real and currently valid, and that the name behind it matches who you think you're dealing with. The official source is VIES — but it's a SOAP/REST service that returns cryptic member-state errors and, worse, doesn't clearly separate "this VAT is invalid" from "the German register happens to be down right now." Get that distinction wrong and you'll block a legitimate customer, or wave through an unverified one. Prometiam's GET /vat/{vatNumber} handles both.
The request
Pass the VAT number (with its 2-letter country prefix) as the path segment. Authenticate with a Bearer key.
GET /functions/v1/risk-api/vat/IE6388047V Host: api.prometiam.com Authorization: Bearer rk_live_…
Spaces and punctuation are stripped for you — IE 6388047 V and IE6388047V resolve identically.
What you get back
{
"data": {
"vat_number": "IE6388047V",
"country_code": "IE",
"number": "6388047V",
"is_valid": true,
"name": "GOOGLE IRELAND LIMITED",
"address": "3RD FLOOR, GORDON HOUSE, BARROW STREET, DUBLIN 4",
"request_date": "2026-07-23+01:00",
"source": "VIES — VAT Information Exchange System (European Commission)"
},
"meta": { "query_ms": 412, "rate_limit": { "remaining": 999 } }
}When is_valid is false, name and address are null — the number is well-formed for its country but not registered. Note that a handful of member states (Germany and Spain among them) suppress the name/address for privacy even on a valid number, so is_valid: true with name: null is a normal, valid result there.


Python
# validate_vat.py — VIES check via Prometiam API
import os, requests
API = 'https://api.prometiam.com/functions/v1/risk-api'
HEAD = {'Authorization': f'Bearer {os.environ["PROMETIAM_API_KEY"]}'}
def validate_vat(vat: str) -> dict:
vat = vat.upper().replace(' ', '')
r = requests.get(f'{API}/vat/{vat}', headers=HEAD, timeout=30)
if r.status_code == 503:
# VIES / member-state register is down — NOT an invalid VAT. Retry later.
return {'status': 'retry', 'reason': r.json()['error']['message']}
if r.status_code == 400:
return {'status': 'bad_input', 'reason': r.json()['error']['message']}
d = r.json()['data']
return {
'status': 'valid' if d['is_valid'] else 'invalid',
'vat': d['vat_number'],
'name': d['name'], # None for privacy-suppressing states (DE/ES)
'address': d['address'],
}
if __name__ == '__main__':
import sys, json
print(json.dumps(validate_vat(sys.argv[1]), indent=2, ensure_ascii=False))Run with python validate_vat.py IE6388047V after exporting PROMETIAM_API_KEY=rk_live_….
Node.js
// validateVat.mjs — VIES check via Prometiam API (Node 18+, native fetch)
const API = 'https://api.prometiam.com/functions/v1/risk-api'
const HEAD = { Authorization: `Bearer ${process.env.PROMETIAM_API_KEY}` }
export async function validateVat(vat) {
const clean = vat.toUpperCase().replace(/\s/g, '')
const res = await fetch(`${API}/vat/${clean}`, { headers: HEAD })
const body = await res.json()
if (res.status === 503) {
// Upstream VIES outage — the VAT was NOT checked. Retry, don't reject.
return { status: 'retry', reason: body.error.message }
}
if (res.status === 400) {
return { status: 'bad_input', reason: body.error.message }
}
const d = body.data
return {
status: d.is_valid ? 'valid' : 'invalid',
vat: d.vat_number,
name: d.name, // null for privacy-suppressing states (DE/ES)
address: d.address,
}
}
const [, , arg] = process.argv
if (arg) console.log(await validateVat(arg))Run with node validateVat.mjs IE6388047V.
What VIES is (and isn't)
VIES — the VAT Information Exchange System — is the European Commission's front door to the national VAT registers of the EU member states. It answers one question: is this VAT number currently valid in its member state? For most states it also returns the registered trader's name and address. It is the authoritative source, and it's free, but talking to it directly has two rough edges that this endpoint smooths over:
- Country prefixes are not ISO country codes everywhere. Greece files under
EL, notGR. The endpoint remaps that for you. Great Britain (GB) left VIES with Brexit — useXIfor Northern Ireland businesses; there is no VIES path for the rest of the UK. - An outage is not an invalid VAT. When a member-state register is unreachable, VIES returns codes like
MS_UNAVAILABLEorSERVICE_UNAVAILABLE. The endpoint translates those to a503 upstream_unavailableso your code never mistakes "couldn't check" for "not valid."
VIES is not a company registry — it confirms VAT status, not directorships, capital, or corporate events. For those, pair it with GET /companies/{id}. And it's a point-in-time check: VIES has no history, so log the request_date for your audit trail.
When to use it — KYB and invoicing
- Reverse-charge invoicing. To zero-rate a cross-border B2B supply within the EU you must hold a valid customer VAT number. Validating against VIES at invoice time — and keeping the
request_date— is your evidence that you checked. - KYB onboarding. A valid VAT number plus a name that matches the registry is a cheap, high-signal first gate before you spend calls on the fuller
/companies/search+ sanctions flow. - Verifactu / SII (Spain). If you're building invoicing software for Spain's Verifactu or SII regimes, VAT-counterparty validation is part of a defensible record. VIES is the reference source for the EU-VAT (NIF-IVA) side.
- Periodic re-checks. VAT registrations get cancelled. For recurring customers, re-validate on a schedule rather than only at onboarding.
See /use-cases/kyb-onboarding for how VAT validation fits the full onboarding record alongside registry lookup and sanctions screening. To resolve the company record from its registry number instead of a VAT number, see the company lookup API.
Common pitfalls
- Missing country prefix. The number must start with the 2-letter prefix (
IE6388047V, not6388047V). A bare number returns400 validation_error. - Treating 503 as invalid. The single most common mistake. A
503means VIES didn't answer — retry with backoff; do not mark the counterparty unverified. - Expecting a name from Germany or Spain. Some states return
is_valid: truebut suppress name/address for privacy. That's a valid VAT — don't fail it for a null name. - Using GB. Post-Brexit, Great Britain isn't in VIES. Use
XIfor Northern Ireland; validate the rest of the UK against HMRC separately. - Rate limits. Free trial is 500 credits over 14 days. For high-volume invoicing, cache a valid result for the day rather than re-checking every line.
Pricing
/vat is registry-data scope, so it runs on the free trial — 500 credits over 14 days, card required, nothing charged for 14 days. The same key also covers company registry search across every registry the API covers, plus VIES here and GLEIF LEI lookup. Only the sanctions (beta) and US export-control endpoints require a paid plan (Professional, €29.99/mo).
FAQ
What is VIES?
The European Commission's VAT Information Exchange System — the authoritative way to check whether an EU VAT number is valid and, for most states, retrieve the registered name and address.
Which countries are covered?
The 27 EU member states plus XI (Northern Ireland). Greece files under EL. GB is out of scope post-Brexit.
Does an "invalid" answer mean the VAT doesn't exist?
Only on a normal 200 with is_valid: false. A 503 means VIES was unreachable — the VAT wasn't checked. Retry.
Why is the name null on a valid VAT?
A few member states (e.g. Germany, Spain) suppress the trader name/address for privacy even when the number is valid.
Is it free?
Yes — /vat is on the 14-day free trial. Only sanctions screening needs a paid plan.
Related
- Verify a Spanish company by NIF in Python — the registry-lookup companion
- SIREN vs SIRET vs NIF — picking the right identifier across countries
- Screen sanctions in JavaScript — the compliance leg after VAT validation
- KYB onboarding pattern — the full production flow