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.

Updated 8 October 2026

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.

Customer dashboard screenshot: a VAT number checked in the ConsoleCustomer dashboard screenshot: a VAT number checked in the Console
GET /vat/{number} in the dashboard's Console: send the request, read the VIES answer and the matching company record (1 credit). Open it in the dashboard

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, not GR. The endpoint remaps that for you. Great Britain (GB) left VIES with Brexit — use XI for 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_UNAVAILABLE or SERVICE_UNAVAILABLE. The endpoint translates those to a 503 upstream_unavailable so 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, not 6388047V). A bare number returns 400 validation_error.
  • Treating 503 as invalid. The single most common mistake. A 503 means 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: true but 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 XI for 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