Home / Use cases / Python

Python

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. 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. 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. 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. 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:

200 OK (active)valid
{
  "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.

active

VAT number is valid and the business is registered

invalid

VAT number format is wrong or not registered in VIES

service_unavailable

VIES or the national system is temporarily down — retry later

Further reading

Evaluating EU VAT APIs? Compare TaxID with:

Ready to implement?

Get your free API key and start validating EU VAT numbers today.

Get free API key

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...