EU VAT validation in Python / Django
Validate EU VAT numbers in Python applications using the TaxID REST API. Works with Django, FastAPI, Flask, and any HTTP-capable Python code.
Python's requests library (or the async httpx for FastAPI applications) provides a clean interface for calling the TaxID REST API. The API accepts a country code and VAT number as path parameters and returns a uniform JSON object, making it straightforward to map into a Python dataclass or Pydantic model for type-safe downstream handling.
In a Django application, the natural home for validation logic is a service layer class (e.g. VATValidator) that is called from form validation (clean() methods), REST framework serializers, or Celery tasks. Storing the result in a dedicated VATValidation model — with fields for status, company_name, company_address, request_id, and validated_at — creates an audit table that satisfies EU record-keeping requirements without polluting your main customer or order models.
Handling service_unavailable correctly is the most important resilience concern. Never allow a service_unavailable response to silently pass as valid; instead, raise a dedicated exception (e.g. VIESUnavailableError) that your view or serializer catches and converts to an HTTP 503 or a user-facing message. For background tasks, use Celery's retry mechanism with a countdown of 300 seconds to re-attempt validation when the VIES node recovers.
Implementation steps
- 1
Use requests or httpx library
Install requests for synchronous Django/Flask apps or httpx for async FastAPI applications. Call GET https://api.taxid.pro/v1/validate/{country}/{vat} with headers={'Authorization': f'Bearer {settings.TAXID_API_KEY}'} and set a timeout of 10 seconds to prevent VIES latency from blocking your request threads indefinitely.
- 2
Create VATValidator class
Encapsulate the API call in a VATValidator class with a validate(country, vat_number) method that returns a typed dataclass or Pydantic BaseModel mirroring the response schema: valid (bool), status (Literal['active','invalid','service_unavailable']), company_name (str | None), company_address (str | None), cached (bool), request_id (str). This decouples your business logic from the HTTP transport layer.
- 3
Handle service_unavailable gracefully
In your validator, raise a VIESUnavailableError (subclassing Exception) when status == 'service_unavailable'. Catch this in Django views with a try/except block and return a 503 response or a form error telling the user to retry. In Celery tasks, use self.retry(exc=exc, countdown=300) to schedule a re-attempt; this prevents indefinite pending states while respecting VIES recovery windows.
- 4
Store results in Django model
Create a VATValidation model with fields: customer (FK), country_code (CharField), vat_number (CharField), status (CharField with choices), company_name (CharField nullable), request_id (UUIDField), validated_at (DateTimeField auto_now_add). Query this table first before calling the API to serve cached results, and invalidate cached records older than 24 hours for active numbers and 1 hour for invalid ones.
Code example
Node.js
const res = await fetch(
'http://localhost:3000/api/v1/validate/DE/DE123456789',
{ headers: { 'Authorization': 'Bearer YOUR_API_KEY' } }
);
const { valid, status, company_name, company_address } = await res.json();
if (valid) {
console.log(`Valid EU business: ${company_name}`);
} else if (status === 'service_unavailable') {
// VIES is temporarily down — retry or allow with manual check
console.log('VIES unavailable — check back in a few minutes');
} else {
console.log('Invalid VAT number — charge local tax rate');
}Python
import requests
res = requests.get(
"http://localhost:3000/api/v1/validate/DE/DE123456789",
headers={"Authorization": "Bearer YOUR_API_KEY"}
)
data = res.json()
if data["valid"]:
print(f"Valid: {data['company_name']}")
elif data["status"] == "service_unavailable":
print("VIES temporarily unavailable")
else:
print("Invalid VAT number")API response
The TaxID API returns a consistent JSON response for every validation request:
{
"valid": true,
"status": "active",
"country_code": "DE",
"vat_number": "123456789",
"company_name": "Example GmbH",
"company_address": "Musterstraße 1, 10115 Berlin",
"request_date": "2026-05-10T00:00:00.000Z",
"cached": false,
"request_id": "req_01j..."
}Error handling
The API uses a consistent Stripe-style error format. Always handle service_unavailable separately — VIES has occasional downtime and you should not reject valid customers during outages.
activeVAT number is valid and the business is registered
invalidVAT number format is wrong or not registered in VIES
service_unavailableVIES or the national system is temporarily down — retry later
Further reading
VAT for Developers: The Complete 2026 Implementation Guide
VAT is a consumption tax collected at each stage of the supply chain. For developers, this means implementing rate looku…
EU VAT Number Validation: The Complete Developer Guide (2026)
VIES is SOAP-based, unreliable, and has no caching. This guide explains how EU VAT validation works end-to-end, how to h…
Evaluating EU VAT APIs? Compare TaxID with:
Related use cases
Stripe EU VAT: Validate Tax IDs Before Charging Customers
Stripe EU VAT integration guide: validate EU VAT numbers server-side before applying zero-rate B2B e...
UK VAT validation in Shopify B2B
Validate UK VAT numbers for B2B customers in Shopify. Required under UK Making Tax Digital rules for...
WooCommerce Spain NIF/CIF validation
Validate Spanish NIF and CIF numbers in WooCommerce checkout. Automatically apply B2B tax exemptions...