VAT Validation

Validate EU VAT numbers against the official VIES database. Checking more than one VAT number at a time — an imported customer list, a periodic re-validation run? Use POST /v1/vat/bulk instead, which validates up to 100 in a single call.

Audit-ready by design

Every response includes a consultationNumber — a VIES-issued reference for the exact VIES check — and a permanent checkId + verifiedAt timestamp. Store these alongside your invoice records as audit evidence. Set your own VAT once in the dashboard (Settings → Requester VAT) to obtain the consultation number on every check automatically.

Endpoint

GET /v1/vat/{vatId}

Parameters

ParameterTypeRequiredDescription
vatIdstringYes (path)The VAT number to validate (e.g., IE6388047V)

No other parameters. The consultation number is controlled entirely by your dashboard requester VAT setting — see "Consultation Number & Audit Trail" below.

Request

Basic request
curl https://api.vatnode.dev/v1/vat/IE6388047V \
  -H "Authorization: Bearer vat_live_your_key"

Response

Success Response (200 OK)

Response
{
  "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,
  "commercialName": null,
  "companyStatus": null,
  "secondaryActivities": [],
  "companyAgeYears": null,
  "registryPrivacy": false,
  "verifiedAt": "2026-03-26T14:25:57.209Z",
  "checkId": "019d2a89-a5d9-7b97-b710-57b84604de2b",
  "consultationNumber": "WAPIAAAAZ27qPadm",
  "specialTerritory": null,
  "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

FieldTypeDescription
validbooleantrue if the VAT number is valid and active
vatIdstringThe normalized VAT number
countryCodestringTwo-letter EU country code
countryNamestringFull country name (e.g. "Finland")
companyNamestring | nullCompany name from VIES (may be null)
companyAddressstring | nullCompany address from VIES (may be null)
companyRegistrationDatestring | nullDate the company was registered (ISO 8601 date). Populated from national registries where available (SE, FR, CZ, FI, DK, PL, DE…); null otherwise.
companyFormstring | nullLegal form of the company (e.g. "AB", "GmbH", "SAS"). Populated from national registries where available; null otherwise.
industryDescriptionstring | nullIndustry or business activity description from national registry (e.g. NACE/SNI code label). Populated where available; null otherwise.
registryCodestring | nullNational 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.
registryCodeNamestring | nullName of the registry identifier type (e.g. "Y-tunnus", "SIREN", "CVR", "Registrikood"). Null if registryCode is null.
commercialNamestring | nullTrade or commercial name where the source registry provides one distinct from the legal companyName. Null when the source has only one name or none.
companyStatusstring | nullNormalized operational status from the registry — one of active, inactive, ceased, bankrupt, liquidation, dissolved (or a raw source value). Enrichment only — it never overrides valid. Null when the source has no status.
secondaryActivitiesstring[]Secondary activity/classification descriptions where the registry provides them (the main activity is industryDescription). Empty array when none or unavailable.
companyAgeYearsnumber | nullFull years between companyRegistrationDate and verifiedAt. Null when companyRegistrationDate is null.
registryPrivacybooleanTrue when the source register marks the record as restricted from publication at the registrant's request (French "non-diffusible", Dutch afscherming). The data is still returned in full — the flag matters if you republish it.
countryVat.vatNamestringOfficial local name of the tax (e.g. "Arvonlisävero", "Mehrwertsteuer")
countryVat.vatAbbrstringShort abbreviation (e.g. "ALV", "MwSt", "TVA")
countryVat.currencystringISO 4217 currency code (e.g. "EUR", "BGN")
countryVat.standardRatenumberStandard VAT rate in percent (e.g. 25.5)
countryVat.reducedRatesnumber[]Reduced VAT rates, empty array if none
countryVat.superReducedRatenumber | nullSuper-reduced rate (CY, ES, FR, GR, IT, LU, PL, PT)
countryVat.parkingRatenumber | nullParking rate (transitional, only some countries)
countryVat.vatNumberFormatstringHuman-readable VAT number format (e.g. "DE + 9 digits")
countryVat.vatNumberPatternstringRegex for local format validation.
countryVat.countryVatUpdatedAtstringDate the VAT rates were last updated from EC TEDB
checkIdstringUUID v7 uniquely identifying this check — matches the record in the audit log
verifiedAtstringISO 8601 timestamp of the check
consultationNumberstring | nullEU Commission-issued reference for this VIES check (the VIES requestIdentifier). Present when you have set your EU VAT as requester in dashboard Settings and VIES is used directly. Null when served from cache, when national fallback was used, or when no requester is configured. Store this alongside your invoice record as audit evidence.
sourcestringIdentifies 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.
specialTerritoryobject | nullSet when the trader address falls in an EU special-VAT territory whose VAT treatment diverges from the member state — e.g. the Canary Islands (ES, IGIC not VAT), the French overseas departments (FR), or the Åland Islands (FI). Carries code, name, memberState, euVatArea (false = outside the EU VAT area, true = inside with regional rates, i.e. Madeira/Azores), and source. Null for mainland traders and whenever no address is returned. See the note below.

specialTerritory is inferred from the address, not an official tax determination. No special territory has its own VAT-number prefix — a Canary Islands business carries a normal ES number, Åland a normal FI number — so the territory can only be inferred from the postal code in the trader address, and only when VIES returns one.

It reflects the registered address, not the place of supply that governs a transaction's VAT treatment. Because some member states (notably ES and DE) don't expose a trader address through VIES, their territories are rarely flagged in practice. Use it as a hint that a special-territory context may apply, and confirm treatment with your tax adviser.

When the address does fall in a special territory, the object is populated — here a Finnish trader registered in the Åland Islands, a fiscal border outside the EU VAT area:

Response with a special territory
{
  "valid": true,
  "vatId": "FI01130235",
  "countryCode": "FI",
  "countryName": "Finland",
  "companyName": "EXAMPLE ÅLAND OY",
  "companyAddress": "TORGGATAN 1, 22100 MARIEHAMN",
  "companyRegistrationDate": null,
  "companyForm": null,
  "industryDescription": null,
  "registryCode": null,
  "registryCodeName": null,
  "commercialName": null,
  "companyStatus": null,
  "secondaryActivities": [],
  "companyAgeYears": null,
  "registryPrivacy": false,
  "verifiedAt": "2026-04-22T09:14:23.000Z",
  "checkId": "019d2a89-a5d9-7b97-b710-57b84604de2b",
  "consultationNumber": null,
  "source": "VIES",
  "specialTerritory": {
    "code": "aland-islands",
    "name": "Åland Islands",
    "memberState": "FI",
    "euVatArea": false,
    "source": "ADDRESS_INFERRED"
  }
}

Consultation Number & Audit Trail

The consultationNumber is a VIES-issued reference for a specific VIES check — the requestIdentifier returned by the VIES checkVatApprox operation. It documents 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 — though on its own it does not establish zero-rate or reverse-charge entitlement.

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 set your ownEU VAT number — setting vatnode's VAT would document vatnode's check, not yours.

Set it once under Dashboard > Account > Requester VAT. Every live validation from then on returns a consultation number automatically — no code changes, no per-request parameters. There is no per-call way to override it; the setting applies to every check on the account.

Validate a VAT number (cURL)
curl https://api.vatnode.dev/v1/vat/DE811257210 \
  -H "Authorization: Bearer vat_live_your_key"

Once your VAT is set as requester, the response carries the consultation number plus the always-present checkId and verifiedAt timestamp:

Response with consultation number
{
  "valid": true,
  "vatId": "DE811257210",
  "countryCode": "DE",
  "countryName": "Germany",
  "companyName": null,
  "companyAddress": null,
  "companyRegistrationDate": null,
  "companyForm": null,
  "industryDescription": null,
  "registryCode": null,
  "registryCodeName": null,
  "commercialName": null,
  "companyStatus": null,
  "secondaryActivities": [],
  "companyAgeYears": null,
  "registryPrivacy": false,
  "verifiedAt": "2026-04-22T09:14:23.000Z",
  "checkId": "019d2a89-a5d9-7b97-b710-57b84604de2b",
  "consultationNumber": "WAPIAAAAW21qsOHW",
  "specialTerritory": null,
  "source": "VIES"
}

Binary Contract When a Requester Is Set

When a requester is configured in dashboard Settings, 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 get INVALID_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.

INVALID_REQUESTER (422)
{
  "error": {
    "code": "INVALID_REQUESTER",
    "message": "Your requester VAT number is invalid in VIES. Fix or clear it in dashboard Settings.",
    "viesCode": "INVALID_INPUT",
    "requestId": "req_abc123"
  }
}

Caching

Requests from an account with a requester configured 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:

CountryFormatExample
GermanyDE#########DE123456789
FinlandFI########FI12345678
FranceFRXX#########FR12345678901
NetherlandsNL#########B##NL123456789B01
SpainESX########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)

Response
{
  "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)

Response
{
  "error": {
    "code": "VIES_UNAVAILABLE",
    "message": "VIES service is temporarily unavailable",
    "requestId": "req_abc123"
  }
}

Code Examples

JavaScript

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

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}")