# Validate EU VAT in Python

# Validate EU VAT in Python

To validate EU VAT numbers in Python, install the `eurovalidate` package and call `validate_vat()` with any European VAT number. The API checks the number against VIES (the EU's official VAT validation system), returns the company name and address, and caches results for 24 hours so repeat lookups resolve in 1--5 milliseconds. Here is how to get started.

## Install

```bash
pip install eurovalidate
```

## Sync example

```python
from eurovalidate import EuroValidate

client = EuroValidate("ev_live_your_key_here")
result = client.validate_vat("NL820646660B01")

print(result.valid)         # True
print(result.company_name)  # "COOLBLUE B.V."
print(result.company_address)  # "Weena 664 3012CN ROTTERDAM"
```

The `VatResult` dataclass gives you typed access to `valid`, `vat_number`, `country_code`, `company_name`, `company_address`, `status`, `meta`, and `request_id`.

## Async example

For FastAPI, Django async views, or any `asyncio` application:

```python
from eurovalidate import AsyncEuroValidate

async def check_vat():
    async with AsyncEuroValidate("ev_live_your_key_here") as client:
        result = await client.validate_vat("FR40303265045")
        print(result.valid)         # True
        print(result.company_name)  # "SOCIETE GENERALE"
```

The `AsyncEuroValidate` client uses `httpx` under the hood and manages its own connection pool. Always use it as a context manager to ensure connections are closed cleanly.

## Using curl (no SDK)

If you want to test before writing any Python:

```bash
curl -H "X-API-Key: ev_live_your_key_here" \
  https://api.eurovalidate.com/v1/vat/NL820646660B01
```

## Using requests (no SDK)

If you prefer `requests` over a dedicated SDK:

```python
import requests

response = requests.get(
    "https://api.eurovalidate.com/v1/vat/NL820646660B01",
    headers={"X-API-Key": "ev_live_your_key_here"},
    timeout=10,
)
data = response.json()

print(data["status"])        # "valid"
print(data["company_name"])  # "COOLBLUE B.V."
```

This works but you lose typed responses, automatic retry on 429, and error mapping. The SDK is recommended for production use.

## What the API returns

### Valid VAT number

Request: `GET /v1/vat/NL820646660B01`

```json
{
  "vat_number": "NL820646660B01",
  "country_code": "NL",
  "status": "valid",
  "company_name": "COOLBLUE B.V.",
  "company_address": "Weena 664 3012CN ROTTERDAM",
  "request_id": "req_abc123",
  "meta": {
    "confidence": "high",
    "source": "vies_live",
    "cached": false,
    "response_time_ms": 247,
    "last_verified": "2026-04-05T10:15:30Z",
    "upstream_status": "ok"
  }
}
```

### Invalid VAT number

Request: `GET /v1/vat/NL000000000B00`

```json
{
  "vat_number": "NL000000000B00",
  "country_code": "NL",
  "status": "invalid",
  "company_name": null,
  "company_address": null,
  "request_id": "req_def456",
  "meta": {
    "confidence": "high",
    "source": "vies_live",
    "cached": false,
    "response_time_ms": 189,
    "last_verified": "2026-04-05T10:15:35Z",
    "upstream_status": "ok"
  }
}
```

The `status` field is one of: `valid`, `invalid`, `error`, or `unavailable`. Check `meta.confidence` to understand data freshness: `high` means a live check against VIES, `medium` means cached, `low` means the upstream was down and the response is stale.

## Timeout handling

VIES live lookups typically take 150--300 ms, but individual country backends can be slow or unresponsive. Always set a timeout:

```python
from eurovalidate import EuroValidate, EuroValidateError

client = EuroValidate("ev_live_your_key_here")

try:
    result = client.validate_vat("NL820646660B01")
    if result.valid:
        print(f"{result.company_name} at {result.company_address}")
    else:
        print(f"VAT number {result.vat_number} is not valid")
except EuroValidateError as e:
    print(f"Validation failed: {e}")
```

The SDK sets a default timeout of 30 seconds and retries automatically on 429 (rate limit) responses. If VIES is down for a specific country, EuroValidate returns the last cached result with `confidence: "low"` rather than failing outright.

## Caching strategy

EuroValidate operates a dual-layer cache:

1. **Redis** (hot cache) -- sub-5ms reads for recently validated numbers.
2. **PostgreSQL** (persistent cache) -- survives Redis restarts, stores results for the full 24-hour TTL.

For your own application, you generally do not need to add another caching layer. The API already caches aggressively. However, if you are validating the same numbers in a tight loop (for example, re-rendering a checkout page), store the result locally for the duration of the user session:

```python
from functools import lru_cache
from eurovalidate import EuroValidate

client = EuroValidate("ev_live_your_key_here")

@lru_cache(maxsize=256)
def check_vat(vat_number: str) -> bool:
    result = client.validate_vat(vat_number)
    return result.valid
```

This avoids redundant API calls during a single user interaction without introducing stale data risk, since the cache is scoped to the process lifetime.

## Latency

| Scenario | Latency |
|----------|---------|
| Cached result (Redis hit) | 1--5 ms |
| Live VIES lookup | 150--300 ms |
| VIES unavailable, stale cache | 2--8 ms |

The first validation for a given VAT number hits VIES. Every subsequent call within 24 hours is served from cache.

## Common pitfalls

### Germany and Spain return no company name

German (DE) and Spanish (ES) tax authorities do not disclose trader names or addresses through VIES. This is a deliberate data protection policy. When you validate a DE or ES VAT number, `company_name` and `company_address` will be `null` even for valid numbers.

If you need the company name for a German VAT number, EuroValidate cross-references GLEIF data when the company holds a Legal Entity Identifier (LEI). This is not guaranteed coverage, but it fills the gap for many large enterprises.

### Greece: GR vs EL

VIES uses `EL` as the country prefix for Greece, while ISO 3166 uses `GR`. The API accepts both formats. The `country_code` field in the response always returns `GR` (ISO standard).

### Rate limits

The free tier allows 100 requests per hour. Rate limit headers are included in every response:

- `X-RateLimit-Limit` -- your plan's limit
- `X-RateLimit-Remaining` -- calls left in the current window
- `X-RateLimit-Reset` -- Unix timestamp when the window resets

The SDK reads these headers and retries automatically on 429. If you are using `requests` directly, check the `Retry-After` header.

### VIES downtime does not mean failure

VIES has roughly 70% effective uptime across all country backends. EuroValidate uses per-country circuit breakers -- 27 independent breakers, one per EU member state. If Germany is down, France still works. When a country backend is unavailable, the API returns the last known result with `confidence: "low"` and `upstream_status` indicating the outage. Your code should check the confidence level and decide whether stale data is acceptable for your use case.

## Unified validation

Validate a VAT number and an IBAN in a single API call:

```python
import requests

response = requests.post(
    "https://api.eurovalidate.com/v1/validate",
    headers={
        "X-API-Key": "ev_live_your_key_here",
        "Content-Type": "application/json",
    },
    json={
        "vat_number": "FR40303265045",
        "iban": "DE89370400440532013000",
    },
    timeout=10,
)
data = response.json()
print(data["vat"]["status"])  # "valid"
print(data["iban"]["valid"])  # True
```

The `POST /v1/validate` endpoint accepts `vat_number`, `iban`, and `eori_number` in any combination. One request, one API call on your quota.

## Next steps

1. [Get your free API key](https://api.eurovalidate.com/v1/auth/signup) -- no credit card, takes 30 seconds.
2. [Interactive API docs](https://api.eurovalidate.com/docs) -- try every endpoint from your browser.
3. [Batch validation](https://api.eurovalidate.com/docs#/batch) -- validate up to 1,000 VAT numbers asynchronously.
4. [IBAN validation endpoint](https://eurovalidate.com) -- validate IBANs with bank name and BIC lookup.

---

*EuroValidate is a unified REST API for EU business data validation. It replaces VIES, EORI, IBAN, and GLEIF lookups with a single JSON endpoint. 27 EU countries plus the UK, per-country circuit breakers, dual-layer caching, and confidence scoring on every response.*
