Validate VAT Numbers from a CSV with Python
Read VAT numbers from a CSV, validate them with the Avatcado batch API, and write the results to a new file. The script below keeps each input row and records individual lookup errors.
Prepare the input file
You need Python 3.9 or later, SDK 0.6.0, and a Pro or Business account. Batch validation requires a paid plan, including when you use a test key. These examples were checked with Python 3.14.3. Create an account to get your API key and choose a batch-enabled plan.
Save the following as input.csv in UTF-8. The header must contain vat_number. Additional columns are ignored. Quoted CSV values and a UTF-8 byte-order mark are supported.
vat_number
DE111111111
DE000000000
DE123
DE111111111python -m pip install avatcado==0.6.0
export AVATCADO_API_KEY="avat_test_replace_with_your_key"Use a test key from your Pro or Business account. On PowerShell, set the key with $env:AVATCADO_API_KEY="avat_test_replace_with_your_key".
Run the CSV validation script
Save this as validate_csv.py. Keep the API key in your environment.
import argparse
import csv
from itertools import islice
import os
from pathlib import Path
import sys
from avatcado import Avatcado, AvatcadoError, is_batch_success
FIELDS = ["input_number", "normalized_number", "status",
"company_name", "source", "error_code"]
def check_rows(client, rows):
numbers = [(row.get("vat_number") or "").strip() for row in rows]
nonempty = [number for number in numbers if number]
results = iter(client.vat.validate_batch(nonempty).results) if nonempty else iter(())
output = []
for row, number in zip(rows, numbers):
record = dict.fromkeys(FIELDS, "")
record["input_number"] = row.get("vat_number") or ""
record["status"] = "error"
if not number:
record["error_code"] = "missing_vat_number"
else:
item = next(results)
if is_batch_success(item):
record["normalized_number"] = item.data.vat_number
record["status"] = "valid" if item.data.valid else "invalid"
record["company_name"] = (item.data.company.name or "") if item.data.company else ""
record["source"] = item.meta.source or ""
else:
record["normalized_number"] = item.error.vat_number or ""
record["error_code"] = item.error.code
output.append(record)
return output
def main():
parser = argparse.ArgumentParser(description="Validate a CSV of VAT numbers")
parser.add_argument("input", type=Path)
parser.add_argument("output", type=Path)
args = parser.parse_args()
if args.input.resolve() == args.output.resolve():
parser.error("Input and output must use different filenames")
key = os.environ.get("AVATCADO_API_KEY")
if not key:
parser.error("Set AVATCADO_API_KEY before running the script")
completed = 0
try:
with args.input.open(newline="", encoding="utf-8-sig") as source:
reader = csv.DictReader(source)
if not reader.fieldnames or "vat_number" not in reader.fieldnames:
parser.error("The input CSV needs a vat_number column")
with args.output.open("x", newline="", encoding="utf-8") as target:
writer = csv.DictWriter(target, fieldnames=FIELDS)
writer.writeheader()
target.flush()
with Avatcado(key, timeout=60.0) as client:
while rows := list(islice(reader, 50)):
output = check_rows(client, rows)
writer.writerows(output)
target.flush()
completed += len(output)
except AvatcadoError as error:
print(f"Stopped after {completed} input rows: {error.code}: {error.message}. "
"Completed rows remain in the output file.", file=sys.stderr)
return 1
except (OSError, csv.Error) as error:
print(f"File error: {error}", file=sys.stderr)
return 1
print(f"Wrote {completed} rows to {args.output}")
return 0
if __name__ == "__main__":
sys.exit(main())python validate_csv.py input.csv results.csvChoose a new output filename. The script refuses to overwrite an existing file. It sends at most 50 numbers in each request and writes completed groups to disk. Empty values receive missing_vat_numberwithout an API request. Empty physical lines are skipped by the CSV reader; use a quoted empty value ("") for a row that contains an empty VAT field.
Read the output
The test input above produces these columns and results:
input_number,normalized_number,status,company_name,source,error_code
DE111111111,DE111111111,valid,"Test GmbH",test,
DE000000000,DE000000000,invalid,,test,
DE123,DE123,error,,,invalid_vat_format
DE111111111,DE111111111,valid,"Test GmbH",test,CSV writers may omit quotes around values that contain no commas or quotes. The column values remain the same. Company names can be empty. A successful lookup with valid: false produces an invalid row. Malformed input and failed lookups produce an error row.
The output preserves duplicates and input order. The API deduplicates normalized numbers within a batch for quota counting, then returns a result for every submitted item. Repeated numbers in separate batches can consume quota again. The source column identifies the source of successful results.
Handle a failed batch request
An authentication error, insufficient plan, quota limit, or request-level failure stops the script. Its message reports the number of input rows already written. Those rows remain in the output file. The current group and remaining input rows have no output yet.
Resolve the reported error, then copy the unprocessed input rows into a new CSV with the same header. Use the completed row count to locate the next input record. Run the script again with a new output filename. The sample does not retry requests automatically.
Rerun individual errors
Filter completed results to status=error. Correct missing or malformed numbers, then copy the input_number values into a new file with a vat_number header. Retry unavailable registries later. Keep the original result file for reference.
Validate live records
Replace the test key with your avat_live_ key and use your customer or supplier VAT numbers. Live checks use your plan quota. Treat imported CSV content as data when opening the output in a spreadsheet; disable formula evaluation for untrusted values.
See the Python single-number guide for SDK setup and exceptions. For background jobs, use async batch validation. The CRM guide covers updating customer records after validation.
Sources
- Avatcado Python SDK 0.6.0 Avatcado, accessed September 22, 2026
- Python CSV documentation Python Software Foundation, accessed September 22, 2026
- Avatcado batch validation Avatcado, accessed September 22, 2026
Related guides
How to Validate VAT Numbers in Python
Validate VAT numbers with the Python SDK. Follow working examples for API keys, company details, error handling, test mode, and async requests.
Async VAT Validation: Webhooks and Batch Processing
Learn how to validate VAT numbers asynchronously using webhooks and batch processing. Covers checkout latency, bulk revalidation, and VIES downtime resilience.
Validate VAT Numbers in Your CRM
How to add real-time VAT number validation to CRM account creation and lead qualification workflows. Covers Salesforce, HubSpot, and no-code automation tools.