VAT Validation
Validate EU VAT numbers against the official VIES database.
Audit-proof by design
Every response includes a consultationNumber — the EU Commission-issued reference for the exact VIES check — and a permanent checkId + verifiedAt timestamp. Store these alongside your invoice records as audit evidence. Requires requesterCountryCode + requesterVatNumber query params to obtain the consultation number.
Endpoint
GET /v1/vat/{vatId}Parameters
| Parameter | Type | Required | Description |
|---|---|---|---|
| vatId | string | Yes (path) | The VAT number to validate (e.g., IE6388047V) |
| requesterCountryCode | string | No (query) | Your two-letter EU country code (e.g. FI). Required together with requesterVatNumber to obtain a VIES consultation number. |
| requesterVatNumber | string | No (query) | Your own EU VAT number without the country prefix (e.g. 12345678). When provided alongside requesterCountryCode, vatnode calls VIES checkVatApprox and returns a consultationNumber in the response. |
Request
curl https://api.vatnode.dev/v1/vat/IE6388047V \
-H "Authorization: Bearer vat_live_your_key"curl "https://api.vatnode.dev/v1/vat/IE6388047V?requesterCountryCode=FI&requesterVatNumber=12345678" \
-H "Authorization: Bearer vat_live_your_key"Response
Success Response (200 OK)
{
"valid": true,
"vatId": "IE6388047V",
"countryCode": "IE",
"countryName": "Ireland",
"companyName": "GOOGLE IRELAND LIMITED",
"companyAddress": "3RD FLOOR, GORDON HOUSE, BARROW STREET, DUBLIN 4",
"companyRegistrationDate": null,
"companyForm": null,
"industryDescription": null,
"registryCode": null,
"registryCodeName": null,
"verifiedAt": "2026-03-26T14:25:57.209Z",
"checkId": "019d2a89-a5d9-7b97-b710-57b84604de2b",
"consultationNumber": "WAPIAAAAZ27qPadm",
"source": "VIES",
"countryVat": {
"vatName": "Value Added Tax",
"vatAbbr": "VAT",
"currency": "EUR",
"standardRate": 23,
"reducedRates": [9, 13.5],
"superReducedRate": null,
"parkingRate": null,
"vatNumberFormat": "IE + 7 digits + 1–2 letters",
"vatNumberPattern": "^IE\d{7}[A-W][A-IW]?$|^IE\d[A-Z+*]\d{5}[A-W]$",
"countryVatUpdatedAt": "2026-03-27"
}
}Response Fields
| Field | Type | Description |
|---|---|---|
| valid | boolean | true if the VAT number is valid and active |
| vatId | string | The normalized VAT number |
| countryCode | string | Two-letter EU country code |
| countryName | string | Full country name (e.g. "Finland") |
| companyName | string | null | Company name from VIES (may be null) |
| companyAddress | string | null | Company address from VIES (may be null) |
| companyRegistrationDate | string | null | Date the company was registered (ISO 8601 date). Populated from national registries where available (SE, FR, CZ, FI, DK, PL, DE…); null otherwise. |
| companyForm | string | null | Legal form of the company (e.g. "AB", "GmbH", "SAS"). Populated from national registries where available; null otherwise. |
| industryDescription | string | null | Industry or business activity description from national registry (e.g. NACE/SNI code label). Populated where available; null otherwise. |
| registryCode | string | null | National company registry identifier (e.g. Y-tunnus for FI, SIREN for FR, Registrikood for EE). Derived from the VAT number where the mapping is known; fetched from national registry for EE and LT. Null for DE and countries without a public registry API. |
| registryCodeName | string | null | Name of the registry identifier type (e.g. "Y-tunnus", "SIREN", "CVR", "Registrikood"). Null if registryCode is null. |
| countryVat.vatName | string | Official local name of the tax (e.g. "Arvonlisävero", "Mehrwertsteuer") |
| countryVat.vatAbbr | string | Short abbreviation (e.g. "ALV", "MwSt", "TVA") |
| countryVat.currency | string | ISO 4217 currency code (e.g. "EUR", "BGN") |
| countryVat.standardRate | number | Standard VAT rate in percent (e.g. 25.5) |
| countryVat.reducedRates | number[] | Reduced VAT rates, empty array if none |
| countryVat.superReducedRate | number | null | Super-reduced rate (CY, ES, FR, GR, IT, LU, PL, PT) |
| countryVat.parkingRate | number | null | Parking rate (transitional, only some countries) |
| countryVat.vatNumberFormat | string | Human-readable VAT number format (e.g. "DE + 9 digits") |
| countryVat.vatNumberPattern | string | Regex for local format validation. |
| countryVat.countryVatUpdatedAt | string | Date the VAT rates were last updated from EC TEDB |
| checkId | string | UUID v7 uniquely identifying this check — matches the record in the audit log |
| verifiedAt | string | ISO 8601 timestamp of the check |
| consultationNumber | string | null | EU Commission-issued reference for this VIES check (the VIES requestIdentifier). Present when requesterCountryCode and requesterVatNumber are supplied and VIES is used directly. Null when served from cache, when national fallback was used, or when no requester was provided. Store this alongside your invoice record as audit evidence. |
| source | string | Identifies which system produced the result: VIES for the normal path, CACHE when the base validation was served from vatnode's cache (up to 15 minutes old), or a national registry code when VIES fell back (e.g. BZST for DE, MF_PL for PL, ANAF_RO for RO). Only VIES can carry a consultation number. |
Consultation Number & Audit Trail
The consultationNumber is the EU Commission-issued reference for a specific VIES check — the requestIdentifier returned by the VIES checkVatApprox operation. It is official proof that a particular requester checked a particular VAT number at a particular point in time. Stored alongside your invoice, it is the strongest available evidence that a VAT validation took place through the official EU system. For the compliance background and what to persist, see the VAT audit trail guide.
A consultation number documents that a check happened via the official EU system. It does not by itself guarantee compliance or provide a legal safe harbour — tax treatment depends on each transaction and jurisdiction. Consult your tax adviser.
Requesting it
VIES issues a consultation number only when you identify yourself as the requester. You must pass your ownEU VAT number — supplying vatnode's VAT would document vatnode's check, not yours. There are two ways to provide it:
- Dashboard (recommended):set your EU VAT once under Settings → Requester VAT. Every live validation then returns a consultation number with no code changes.
- Per-request query params: pass
requesterCountryCodeandrequesterVatNumberon the request URL. Query params override the dashboard setting for that one call. Useful for multi-entity setups where the requester varies per transaction. Both must be supplied together — sending only one returnsINVALID_FORMAT(400).
curl "https://api.vatnode.dev/v1/vat/DE811257210?requesterCountryCode=FI&requesterVatNumber=12345678" \
-H "Authorization: Bearer vat_live_your_key"The response carries the consultation number plus the always-present checkId and verifiedAt timestamp:
{
"valid": true,
"vatId": "DE811257210",
"countryCode": "DE",
"countryName": "Germany",
"companyName": null,
"companyAddress": null,
"verifiedAt": "2026-04-22T09:14:23.000Z",
"checkId": "019d2a89-a5d9-7b97-b710-57b84604de2b",
"consultationNumber": "WAPIAAAAW21qsOHW",
"source": "VIES"
}Binary contract when a requester is set
When a requester is configured (dashboard or query params), vatnode never returns a successful response with consultationNumber: null. The outcome is one of two states:
- a successful result with a consultation number, or
- an error. National registry fallback is bypassed in this mode, so if VIES is down for that country you get
VIES_UNAVAILABLE(503). If your own requester VAT is rejected by VIES (e.g. deregistered, typo) you getINVALID_REQUESTER(422).
This prevents silently stripping audit evidence from a request that opted into it. Without a requester, validation follows the normal path (cache + national fallback) and consultationNumber is null.
{
"error": {
"code": "INVALID_REQUESTER",
"message": "Your requester VAT number is invalid in VIES. Fix or clear it in dashboard Settings (or pass a valid requesterCountryCode + requesterVatNumber).",
"viesCode": "INVALID_INPUT",
"requestId": "req_abc123"
}
}Caching
Requests with a requester bypass the cache so VIES always issues a fresh consultation number tied to that requester — a cached entry would carry no consultation number, or one belonging to a different requester. The consultation number itself is never cached; it is returned only to the caller that made the requester-authenticated call.
VAT Number Format
VAT numbers must include the two-letter country code prefix. Here are some examples:
| Country | Format | Example |
|---|---|---|
| Germany | DE######### | DE123456789 |
| Finland | FI######## | FI12345678 |
| France | FRXX######### | FR12345678901 |
| Netherlands | NL#########B## | NL123456789B01 |
| Spain | ESX######## | ESA12345678 |
Normalization
VAT numbers are automatically normalized:
- Converted to uppercase
- Whitespace and dashes removed
- Leading/trailing spaces trimmed
All of these are equivalent:
IE6388047V
IE 6388047V
IE-6388047V
IE6388047V Country-Specific Notes
Germany (DE) and Spain (ES)
German and Spanish VIES queries do not return company name or address. The companyName and companyAddress fields will always be null for German and Spanish VAT numbers.
Greece (EL)
Greek VAT numbers use the prefix EL (VIES convention), e.g. EL123456789. This is the only prefix accepted by /v1/vat — a GR-prefixed number returns INVALID_FORMAT. The ISO code GR is used only for the VAT-rates endpoint (/v1/rates/GR); the two codes are not interchangeable.
National fallbacks
For select EU countries, vatnode automatically falls back to official national tax authority APIs when the VIES member-state node is unavailable. The fallback is fully transparent — the response format is identical and the result is equally authoritative.
Error Responses
Invalid Format (400)
{
"error": {
"code": "INVALID_FORMAT",
"message": "Invalid VAT ID format. Expected format: country code (2 letters) followed by VAT number",
"requestId": "req_abc123"
}
}VIES Unavailable (503)
{
"error": {
"code": "VIES_UNAVAILABLE",
"message": "VIES service is temporarily unavailable",
"requestId": "req_abc123"
}
}Code Examples
JavaScript
async function validateVat(vatId) {
const response = await fetch(`https://api.vatnode.dev/v1/vat/${vatId}`, {
headers: {
'Authorization': `Bearer ${process.env.VATNODE_API_KEY}`
}
});
if (!response.ok) {
const error = await response.json();
throw new Error(error.error.message);
}
return response.json();
}
// Usage
try {
const result = await validateVat('IE6388047V');
if (result.valid) {
console.log(`Valid: ${result.companyName}`);
}
} catch (error) {
console.error('Validation failed:', error.message);
}Python
import requests
import os
def validate_vat(vat_id):
response = requests.get(
f'https://api.vatnode.dev/v1/vat/{vat_id}',
headers={'Authorization': f'Bearer {os.environ["VATNODE_API_KEY"]}'}
)
response.raise_for_status()
return response.json()
# Usage
try:
result = validate_vat('IE6388047V')
if result['valid']:
print(f"Valid: {result['companyName']}")
except requests.HTTPError as e:
print(f"Validation failed: {e}")