How to Validate EU VAT Numbers in Python

2026-03-26 · European Business API

Why VAT validation matters

If you sell to businesses across the EU, you need to verify their VAT numbers. A valid VAT number determines whether you charge VAT or apply the reverse charge mechanism. Getting it wrong means either overcharging your customer or underreporting to tax authorities.

The official way to validate is through VIES (VAT Information Exchange System), operated by the European Commission. But VIES has reliability issues — timeouts, downtime, and inconsistent responses. That's where an API wrapper helps.

The quick way: European Business API

The fastest approach is a single API call:


import httpx

response = httpx.get(
    "https://frenchbusinessapi.com/vat/validate/FR89421940101",
    headers={"X-API-Key": "your_api_key"}
)
data = response.json()
print(data)

Response:


{
  "valid": true,
  "country_code": "FR",
  "vat_number": "FR89421940101",
  "format_valid": true,
  "company_name": "GOOGLE FRANCE",
  "company_address": "8 RUE DE LONDRES 75009 PARIS",
  "vies_available": true
}

One call gives you: format validation, VIES live check, company name and address. No need to parse SOAP XML yourself.

How VAT numbers work

Every EU VAT number starts with a two-letter country prefix followed by digits (and sometimes letters). The format varies by country:

Each country has its own format rules. A proper validation needs to check both the format AND the live status via VIES.

Format validation vs VIES validation

There are two levels of validation:

Format validation checks if the number matches the expected pattern for its country. This is fast and offline — you can do it with a regex. But a correctly formatted number can still be invalid (expired, cancelled, never issued).

VIES validation checks the number against the actual EU database. This confirms the number is currently active and returns the registered company name and address. It requires a network call and can be slow or unavailable.

A robust implementation does both: format check first (fast rejection of obviously wrong numbers), then VIES check for format-valid numbers.

Checking reverse charge eligibility

When you sell B2B across EU borders, the reverse charge mechanism applies: you invoice without VAT, and the buyer self-assesses VAT in their country. But this only applies when specific conditions are met:

The API handles this logic:


response = httpx.get(
    "https://frenchbusinessapi.com/vat/check-reverse-charge",
    params={
        "seller_country": "FR",
        "buyer_country": "DE",
        "buyer_is_business": True
    },
    headers={"X-API-Key": "your_api_key"}
)
print(response.json())

Response:


{
  "reverse_charge_applies": true,
  "vat_rate_to_apply": 0.0,
  "explanation": "Intra-EU B2B supply from FR to DE. Reverse charge applies.",
  "seller_country": "FR",
  "buyer_country": "DE",
  "buyer_is_business": true
}

Getting VAT rates for all 27 EU countries

Need the current VAT rates? The API provides standard, reduced, super-reduced, and parking rates for all 27 EU member states:


response = httpx.get(
    "https://frenchbusinessapi.com/vat/rates",
    headers={"X-API-Key": "your_api_key"}
)
for country in response.json():
    print(f"{country['country_code']}: {country['standard_rate']}%")

This data is updated when EU countries change their rates. No need to maintain a hardcoded table.

Handling VIES downtime

VIES is not always available. Individual country services go down regularly. The API response includes a `vies_available` field that tells you whether the live check was possible:


data = response.json()
if data["vies_available"]:
    # VIES confirmed the result
    is_valid = data["valid"]
else:
    # VIES was down, fall back to format check only
    is_valid = data["format_valid"]
    # Consider retrying later for a definitive answer

This lets you handle degraded mode gracefully instead of failing hard.

Batch validation

If you need to validate multiple VAT numbers (e.g., cleaning up a customer database), loop through them with reasonable rate limiting:


import time

vat_numbers = ["FR89421940101", "DE123456789", "NL123456789B01"]

for vat in vat_numbers:
    response = httpx.get(
        f"https://frenchbusinessapi.com/vat/validate/{vat}",
        headers={"X-API-Key": "your_api_key"}
    )
    result = response.json()
    status = "valid" if result["valid"] else "invalid"
    print(f"{vat}: {status} — {result.get('company_name', 'N/A')}")
    time.sleep(0.1)  # Respect rate limits

Common pitfalls

Next steps

Ready to build for Europe?

One API for company lookup, VAT validation, IBAN verification, SEPA payments, and more.

Get your API key