Skip to Content
GuidesValidate VAT

Validate VAT

VerifNow validates EU VAT numbers in two independent steps: it checks the number’s structure against the rules of its member state, then checks its registration against VIES, the European Commission’s VAT Information Exchange System.

Those two answers come from different places, and only one of them is always available. This guide is mostly about that difference, because it is where VAT validation actually goes wrong in production.


Basic VAT validation

Send a POST request to /api/v1/validate/vat with the number, country prefix included:

POST https://api.verifnow.io/api/v1/validate/vat X-API-KEY: your_api_key_here Content-Type: application/json { "value": "IE6388047V" }

Example response

{ "valid": true, "message": "Valid VAT number", "normalizedValue": "IE6388047V", "originalValue": "IE6388047V", "validationLevel": "STANDARD", "vatDetails": { "format_valid": true, "registered": true, "country_code": "IE", "source": "LIVE", "checked_at": "2026-09-08T02:21:25Z", "trader_name": "GOOGLE IRELAND LIMITED", "trader_address": "3RD FLOOR, GORDON HOUSE, BARROW STREET, DUBLIN 4", "vies_available": true } }

trader_name and trader_address are only present when the member state chooses to disclose them. Germany, for instance, does not.


The problem this endpoint exists to solve

VIES is the only authoritative source for EU VAT registration, and it is unreliable by design: it is a federation of 27 national registries, each of which can go down on its own. The Commission publishes no SLA, and individual member states drop out several times a month.

Most VAT validation APIs handle this in one of two ways, and both are wrong:

  • They return an error, so your checkout fails for a customer whose VAT number is perfectly valid.
  • They return valid: false, which is worse — you tell a legitimate business that their VAT number is not registered, when the truth is that you could not ask.

registered is nullable, and null never means “no”. A VAT number whose registration could not be checked is not an unregistered VAT number. If you collapse the two, you will reject real customers during someone else’s outage.


The source field

Every VAT response carries a source telling you where the registration verdict came from. Branch on this, not on valid, whenever the distinction matters for your own compliance.

sourceWhat it meansregistered
LIVEConfirmed against VIES during this request.true / false
CACHEServed from a VIES answer less than 24 hours old.true / false
STALEVIES was unreachable, so an older cached answer was used.true / false
UNVERIFIEDVIES was unreachable and nothing was cached.null
NOT_APPLICABLEThe country is outside VIES; no registry lookup exists.null

How the fallback works

  1. Structure is checked locally, always, and never depends on VIES.
  2. A fresh cached answer (under 24 hours) is served directly.
  3. Otherwise VIES is called live, with a 4-second budget.
  4. If VIES is unreachable, a stale cached answer is used — up to 30 days old.
  5. If nothing is cached either, the response is UNVERIFIED.

Stale answers are served on purpose. VAT registrations change on the order of months, so a three-week-old confirmation is far better evidence than refusing a customer outright.


Handling degraded responses

The pattern that survives a VIES outage:

import { VerifNow } from '@verifnow/sdk'; const client = new VerifNow({ apiKey: process.env.VERIFNOW_API_KEY }); const result = await client.validateVat('IE6388047V'); const { formatValid, registered, source } = result.vatDetails; if (!formatValid) { // Structurally impossible. Safe to reject — this never depended on VIES. return reject('That VAT number is not correctly formed.'); } if (registered === false) { return reject('That VAT number is not registered.'); } if (registered === null) { // source is UNVERIFIED. Do not reject: accept and re-check later. await queueForRecheck(vatNumber); return accept({ verified: false }); } return accept({ verified: true, stale: source === 'STALE' });

The SDK maps the API’s snake_case fields onto camelCase, so format_valid in the JSON above reads as formatValid here. registered keeps its three states — the SDK maps it with its own helper precisely so null is not flattened into undefined. Needs @verifnow/sdk 1.1.0 or later.

For reverse-charge invoicing you generally need evidence that you checked, not a guarantee that the registry was up. Store the source and checked_at you received alongside the invoice.


Checking VIES availability yourself

VerifNow publishes per-country VIES availability:

GET https://api.verifnow.io/api/v1/status/vies

This endpoint needs no API key. The human-readable version lives at verifnow.io/en/status .

Two things are combined there. The Commission publishes a per-country status feed about itself, but only as a current snapshot — no history, so you cannot tell a country that has been down for an hour from one that has been flaky all month. We sample that feed every five minutes and keep the history. On top of it, a country we have actually failed to reach three times in a row is reported as unavailable even if the Commission’s feed still claims otherwise; each country in the response says which of the two sources decided its verdict.

Use it to decide whether to run a bulk re-check now or wait, or to explain to your own users why a verification is pending.


Supported countries

All 27 member states, plus:

  • EL — Greece uses EL, not GR, in VAT numbers.
  • XI — Northern Ireland, which remains inside the EU VAT area for goods.

Each member state has its own structural rule, and several allow letters inside the number: IE6388047V, NL123456789B01, ESX1234567X, ATU12345678. A validator that assumes the body is all digits will produce false negatives on legitimate registrations.


Common errors

SituationResponse
Missing country prefix (6388047V)valid: false, format_valid: false
Country outside the EUsource: NOT_APPLICABLE, registered: null
Correct structure, absent from the registryvalid: false, registered: false
VIES down for that countrysource: UNVERIFIED, registered: null

See Error Handling for transport-level failures and status codes.


Next steps

  • VAT Rates — the standard, reduced and regional rates of the 27 member states, no key needed
  • Validate IBAN — the other field a billing form depends on
  • Node.js example — full integration with @verifnow/sdk
  • Rate Limits — quotas and concurrency
Last updated on