Bulk VAT number validation via CSV import
Validate hundreds of EU VAT numbers in batch using parallel API requests. Ideal for CRM data cleansing, supplier audits, and initial data migrations.
Bulk VAT validation is typically needed in three scenarios: migrating customer data from a legacy system where VAT numbers were collected but never verified, running a periodic audit of your existing customer or supplier database, and performing a one-time cleanse before filing annual VAT reports. In all three cases the data source is a CSV or database dump, and the goal is to enrich each row with the current VIES status, the VIES-confirmed company name, and a validation timestamp.
The TaxID API is rate-limited, so bulk jobs must control concurrency rather than firing all requests simultaneously. A practical approach is to process rows in batches of 10 concurrent requests using Promise.allSettled (JavaScript) or asyncio.gather with a semaphore (Python), with exponential backoff on 429 responses. Since TaxID caches active numbers for 24 hours, if you run the same batch job on consecutive days, the majority of previously-valid numbers will return from cache in under 10 ms, making re-validation economical.
For very large datasets (tens of thousands of numbers), structure the job as a queue of individual validation tasks (e.g. BullMQ or Celery) so it is resumable after failures, and write partial results to the output file as each batch completes rather than holding everything in memory. This also lets you prioritise re-validation of numbers whose last validated_at timestamp is older than your data freshness policy.
Implementation steps
- 1
Parse CSV with VAT numbers
Use a CSV library (csv-parse in Node.js, Python's built-in csv module, or League\Csv in PHP) to stream-parse the input file row by row rather than loading the entire file into memory. Extract the country_code and vat_number columns; if the CSV does not have separate columns, use a regex to split the two-letter country prefix from the numeric portion (e.g. /^([A-Z]{2})(.+)$/) before calling the API.
- 2
Validate in parallel (max 10 concurrent)
Use a concurrency-limited executor: in Node.js, p-limit(10) wrapped around Promise.allSettled is idiomatic; in Python, asyncio.Semaphore(10) with asyncio.gather achieves the same effect. Limit to 10 concurrent requests to stay within API rate limits and to avoid overwhelming VIES member-state nodes, which are known to become unresponsive under high request volumes.
- 3
Collect results with rate limit awareness
On each API response, inspect the HTTP status code: a 429 means you've exceeded the rate limit — back off exponentially (e.g. 1s, 2s, 4s) and retry the failed row. For status: 'service_unavailable' in the response body, log the row as 'pending' and add it to a retry queue rather than marking it as invalid, since the VIES node may recover within minutes. Store valid, invalid, and pending counts in a running summary to report at job completion.
- 4
Export enriched CSV with status + company name
Write the output CSV with the original columns plus: vat_status (active/invalid/service_unavailable), company_name, company_address, request_id, and validated_at. Include request_id so downstream users can reference the specific TaxID lookup that produced the result — this is valuable for audit purposes when a customer disputes their validation outcome.
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 Number Lookup: A Developer's Guide for 2026
Learn to build a resilient VAT number lookup for your SaaS or e-commerce app. This guide covers VIES pitfalls, API integ…
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
EU VAT compliance for SaaS billing
Handle EU VAT for SaaS subscriptions. Validate customer VAT numbers at signup, determine B2B vs B2C ...
DAC7 marketplace seller VAT verification
Verify seller VAT numbers during marketplace onboarding for DAC7 compliance. EU platforms must colle...
Validate German USt-IdNr. (DE VAT numbers)
Specifically validate German Umsatzsteuer-Identifikationsnummern (USt-IdNr.) via VIES. Understand th...