🐍 Python🇪🇸 SpainTax Compliance
Spanish Business Compliance — NIF, NIE & VAT Validation
Validate Spanish tax identifiers — NIF (residents), NIE (foreign individuals), and EU VAT numbers — in Python using asyncio.gather for concurrent checks and cross-validation.
Also available in Node.js
Contents
1. Spanish tax identifiers
| Identifier | Endpoint | Who uses it |
|---|---|---|
| NIF | /v0/nif | Spanish citizens and businesses (DNI or CIF) |
| NIE | /v0/nie | Foreign individuals resident in Spain |
| VAT (Spanish) | /v0/vat | EU VAT = "ES" prefix + NIF/CIF number |
2. Validate NIF
Use await iv.nif(value) (SDK) or GET /v0/nif?value=….
{ "valid": true, "type": "DNI", "number": "12345678", "letter": "Z" }
- Check
valid— mod-23 check letter - Check
type—DNI(individual) orCIF(company)
3. Validate NIE
Use await iv.nie(value) (SDK) or GET /v0/nie?value=…. Format: X/Y/Z + 7 digits + check letter.
{ "valid": true, "type": "NIE", "number": "X1234567", "letter": "L" }
💡Route addresses starting with X, Y, or Z to the NIE endpoint; all others to NIF.
4. Validate Spanish VAT
Use await iv.vat(value) (SDK) or GET /v0/vat?value=….
{ "valid": true, "countryCode": "ES", "identifier": "B12345674", "formatted": "ESB12345674" }
- Check
validandcountry_code == "ES" - Use
formattedfor invoices and VIES
5. Parallel validation with asyncio.gather
import asyncio from isvalid_sdk import IsValidConfig, create_client config = IsValidConfig(api_key="YOUR_API_KEY") iv = create_client(config) async def validate_spanish_entity(entity_type: str, data: dict): tasks = {} if data.get("nif"): tasks["nif"] = iv.nif(data["nif"]) if data.get("nie"): tasks["nie"] = iv.nie(data["nie"]) if data.get("vat"): tasks["vat"] = iv.vat(data["vat"]) results = dict(zip(tasks.keys(), await asyncio.gather(*tasks.values()))) if "nif" in results and "vat" in results: nif_r = results["nif"] vat_r = results["vat"] if nif_r.valid and vat_r.valid: nif_number = f"{nif_r.number}{nif_r.letter or ''}" if vat_r.identifier != nif_number: raise ValueError( f"VAT identifier ({vat_r.identifier}) does not match NIF ({nif_number})" ) return results async def main(): result = await validate_spanish_entity("business", { "nif": "B12345674", "vat": "ESB12345674", }) for k, v in result.items(): print(f"{k}: valid={v.valid}") asyncio.run(main())
6. Edge cases
Auto-routing NIF vs NIE
async def validate_any_spanish_personal_id(value: str): upper = value.upper() if upper[0] in ("X", "Y", "Z"): return {"type": "NIE", "result": await iv.nie(upper)} return {"type": "NIF", "result": await iv.nif(upper)}
NIF/VAT cross-check
⚠️Spanish VAT = "ES" + NIF. Cross-check both when a business provides both identifiers.
nif_r, vat_r = await asyncio.gather(iv.nif(nif), iv.vat(vat)) nif_number = f"{nif_r.number}{nif_r.letter or ''}" if nif_r.valid and vat_r.valid and vat_r.identifier != nif_number: raise ValueError("NIF and VAT do not match")
7. Summary checklist
✓Route X/Y/Z-prefixed IDs to NIE endpoint
✓Validate NIF format and mod-23 check letter
✓Validate full ESxxxx VAT for B2B invoicing
✓Cross-check VAT identifier against NIF number
✓Run all calls with asyncio.gather
✓Distinguish DNI vs CIF via type field
✓Use formatted VAT for VIES submissions
✓Return 422 with field-level error messages