Home / VAT API / Integrations / Django
Django EU VAT Validation API
Validate EU VAT numbers in your Django project with the TaxID API. Drop it into a model field validator, a Django REST Framework serializer, or a Celery task so every VAT number is verified against VIES before a B2B order is zero-rated or a customer record is committed to the database.
Quick start
CURL
curl -H "Authorization: Bearer YOUR_API_KEY" \ https://www.taxid.dev/api/v1/validate/DE/DE123456789
Code example
Full integration example including error handling for service_unavailable responses from VIES.
Python (DRF serializer)
# services.py
import requests
from django.conf import settings
def validate_vat(country: str, vat_number: str) -> dict:
resp = requests.get(
f"https://www.taxid.dev/api/v1/validate/{country}/{vat_number}",
headers={"Authorization": f"Bearer {settings.TAXID_API_KEY}"},
timeout=10,
)
resp.raise_for_status()
return resp.json()
# serializers.py
from rest_framework import serializers
from .services import validate_vat
class CustomerSerializer(serializers.ModelSerializer):
def validate_vat_number(self, value):
result = validate_vat(value[:2].upper(), value)
if result["status"] == "invalid":
raise serializers.ValidationError("Not a registered EU VAT number.")
# 'service_unavailable' passes through — re-check via Celery later
return valuePython (Celery re-validation)
# tasks.py — periodic re-validation with Celery
from celery import shared_task
from .models import Customer
from .services import validate_vat
@shared_task(bind=True, max_retries=5)
def revalidate_vat(self, customer_id):
customer = Customer.objects.get(pk=customer_id)
result = validate_vat(customer.vat_country, customer.vat_number)
if result["status"] == "active":
customer.vat_company = result["company_name"]
customer.save(update_fields=["vat_company"])
elif result["status"] == "invalid":
customer.reverse_charge = False
customer.save(update_fields=["reverse_charge"])
else: # service_unavailable — retry in 5 minutes
raise self.retry(countdown=300)API response
The TaxID API returns a consistent JSON response for every validation:
{
"valid": true,
"status": "active",
"country_code": "DE",
"vat_number": "DE123456789",
"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..."
}activeVAT number is valid and the business is registered
invalidVAT number format is wrong or not registered in VIES
service_unavailableVIES or the national authority is temporarily down — retry later, do not silently zero-rate
Implementation steps
- 1
Get a free TaxID API key
Sign up at taxid.dev/signup for a free key with 100 validations/month. Read it from the environment with os.environ or django-environ and expose it through settings.py as TAXID_API_KEY — never hard-code it or commit it to your repository.
- 2
Write a validation helper
Add a small function in a services.py module that uses requests (or httpx for async views) to call https://www.taxid.dev/api/v1/validate/{country}/{vat} with an Authorization: Bearer header and a 10-second timeout. Return the parsed JSON so the rest of your code works with a plain dict.
- 3
Enforce it in a serializer or form
In Django REST Framework, add a validate_vat_number() method to your serializer that calls the helper and raises serializers.ValidationError when status is 'invalid'. For classic Django forms, do the same in clean_vat_number(). This keeps validation at the boundary so bad numbers never reach your view logic.
- 4
Persist the company details
When status == 'active', save the returned company_name, company_address, and a validated_at datetime on your Customer or Company model. Storing the snapshot means you can prove the number was valid at capture time and avoid a redundant VIES call on the next request within the cache window.
- 5
Re-validate with a Celery beat task
Schedule a periodic Celery task that re-validates all customers marked for reverse-charge at least monthly. A registration valid at signup can be revoked later, and tax audits expect evidence for each invoice date. Handle 'service_unavailable' by retrying the task with self.retry(countdown=300) rather than flagging the number invalid.
Frequently asked questions
How do I validate a VAT number in a Django REST Framework serializer?
Add a validate_<fieldname>() method — e.g. validate_vat_number(self, value) — that calls your TaxID helper and raises rest_framework.serializers.ValidationError if the response status is not 'active'. DRF runs it automatically during is_valid(), so an invalid number produces a clean 400 response with your error message.
Can I validate VAT numbers in an async Django view?
Yes. Use httpx.AsyncClient instead of requests and await the call inside an async def view. The TaxID endpoint is a simple GET, so it fits naturally into async views and ASGI deployments without blocking the event loop. Keep the timeout at around 10 seconds.
What should my Celery task do when TaxID returns service_unavailable?
Treat it as transient, not as an invalid number. Call self.retry(countdown=300, max_retries=5) to re-run the task after five minutes. Never overwrite a previously-valid flag on a 'service_unavailable' response, or a temporary VIES outage would incorrectly strip reverse-charge status from real businesses.
Does the django-localflavor VAT field validate against VIES?
No. Packages like django-localflavor only check that a VAT number matches the expected format for a country — they never confirm the number is actually registered. The TaxID API performs the live VIES lookup, so combine format checking with a real registration check before applying any tax exemption.
Country-specific API docs:
In-depth integration guide:
Python VAT API — 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.
Start validating EU VAT numbers in Django
Free plan — 100 validations/month. No credit card required.