Vatline validates EU VAT numbers against VIES, the VAT Information Exchange System operated by the European Commission, and returns the answer as flat JSON. Covers the 27 member states plus Northern Ireland (XI).
Quick start. Subscribe on RapidAPI (the free plan needs no card), copy your key, and run the curl example below. Your first answer arrives in under a minute.
https://vatline.p.rapidapi.com
Every request carries the two standard RapidAPI headers. Billing, keys and quotas are handled by RapidAPI.
X-RapidAPI-Key: YOUR_KEY
X-RapidAPI-Host: vatline.p.rapidapi.com
Content-Type: application/json
Validate one VAT number.
| Field | Type | Required | Notes |
|---|---|---|---|
vat_number | string | yes | With or without the country prefix. FR40303265045 and 40303265045 (with country) are equivalent. Spaces, dots and dashes are ignored. |
country | string | no | Two-letter code. Omit it when the prefix is already in vat_number. GR is accepted as an alias of EL. |
requester | object | no | {"country": "FR", "vat_number": "40303265045"}: your own VAT number. Supplying it returns a VIES consultation number (see below). |
curl -X POST https://vatline.p.rapidapi.com/v1/check \
-H 'Content-Type: application/json' \
-H 'X-RapidAPI-Key: YOUR_KEY' \
-H 'X-RapidAPI-Host: vatline.p.rapidapi.com' \
-d '{"vat_number": "IT00743110157"}'
import requests
r = requests.post(
"https://vatline.p.rapidapi.com/v1/check",
json={"vat_number": "IT00743110157"},
headers={"X-RapidAPI-Key": "YOUR_KEY",
"X-RapidAPI-Host": "vatline.p.rapidapi.com"},
timeout=30,
)
data = r.json()
if r.ok:
print(data["valid"], data["name"])
elif data["error"]["retryable"]:
pass # upstream hiccup: back off and retry later
else:
raise ValueError(data["error"]["message"]) # fix the input
const r = await fetch("https://vatline.p.rapidapi.com/v1/check", {
method: "POST",
headers: {
"Content-Type": "application/json",
"X-RapidAPI-Key": process.env.RAPIDAPI_KEY,
"X-RapidAPI-Host": "vatline.p.rapidapi.com",
},
body: JSON.stringify({ vat_number: "IT00743110157" }),
});
const data = await r.json();
{
"valid": true,
"country": "IT",
"vat_number": "00743110157",
"name": "MOTOROLA SOLUTIONS ITALIA SRL",
"address": "LARGO FRANCESCO RICHINI 6 \n20122 MILANO MI",
"name_available": true,
"address_available": true,
"consultation_number": null,
"checked_at": "2026-10-05T22:20:58+00:00",
"source": "upstream",
"fetched_at_unix": 1791238858.412,
"breaker_open": false
}
| Field | Meaning |
|---|---|
valid | Whether the member state recognises the number. An invalid number is still HTTP 200: read this field. |
name, address | As returned by the member state, or null when not disclosed. |
name_available, address_available | Read these before treating a null as a failure. Germany and Spain never disclose name or address through VIES: the flags are false and the null is expected. |
consultation_number | Only when you sent requester. Keep it as evidence of verification in case of a tax audit. |
checked_at | When the underlying VIES answer was obtained (ISO 8601, UTC), not when you called us. |
source | upstream (fresh from VIES), cache, coalesced (shared with an identical concurrent request) or cache_stale (expired entry served because the member state is down). |
fetched_at_unix | Same instant as checked_at, as a Unix timestamp. |
breaker_open | true when the circuit breaker for that member state is open (only together with cache_stale). |
Up to 100 numbers in one call (paid plans). Each item takes the same fields as /v1/check. Duplicates share one upstream lookup. A failed item never fails the batch: it carries its own error object.
curl -X POST https://vatline.p.rapidapi.com/v1/check/batch \
-H 'Content-Type: application/json' \
-H 'X-RapidAPI-Key: YOUR_KEY' \
-H 'X-RapidAPI-Host: vatline.p.rapidapi.com' \
-d '{"items": [
{"vat_number": "IT00743110157"},
{"vat_number": "FR40303265045"},
{"country": "DE", "vat_number": "129273398"}
]}'
The response holds count and a results array in the same order as your items. Each entry has the shape of a single check plus input, the number you sent.
VIES only issues a consultation number when the request includes the requester's own VAT number. Add requester and Vatline switches to the VIES checkVatApprox operation. VIES issues the number only when the requester number is itself valid; otherwise the field stays null and the rest of the answer is unchanged.
Every error has the same shape. Branch on code (stable); message is for humans and may change.
{"error": {"code": "MS_MAX_CONCURRENT_REQ", "message": "...", "retryable": true}}
retryable tells you what to do: true means the failure is upstream and transient, so back off and retry; false means the same request will fail the same way.
| Code | HTTP | Retry | What happened |
|---|---|---|---|
INVALID_INPUT | 400 | no | The number does not match its member state's format or check digit. Rejected locally, never sent to VIES. The message states the expected structure. |
UNAUTHORIZED | 401 | no | Missing or wrong RapidAPI headers. |
PAYLOAD_TOO_LARGE | 413 | no | Request body over 64 KB. |
RATE_LIMITED | 429 | yes | Too many requests in a short window. Honour Retry-After. |
MS_MAX_CONCURRENT_REQ | 503 | yes | The member state is saturated (country-wide VIES limit). |
GLOBAL_MAX_CONCURRENT_REQ | 503 | yes | VIES as a whole is saturated. |
MS_UNAVAILABLE | 503 | yes | The member state's system is down. |
SERVICE_UNAVAILABLE, TIMEOUT, UPSTREAM_TIMEOUT | 503 | yes | VIES did not answer in time or is unavailable. |
COUNTRY_BREAKER_OPEN | 503 | yes | Repeated failures for this country and nothing in cache: we stopped calling it for a short cooldown. |
MS_INVALID_REQUEST | 502 | no | The member state rejected the request. |
INTERNAL | 502 | no | Unexpected error on our side. It is logged; contact us if it persists. |
GET /health needs no key and returns the relay's live limits, cache statistics and per-country breaker states: api.vatlineapi.com/health. GET /health/{country} returns the breaker state of one member state; check it before sending a large batch for that country.
A valid answer is cached for 24 hours, an invalid one for 15 minutes (a number may become valid the same day). Cached answers cost the same quota but never touch VIES and come back in milliseconds. The cache survives restarts, so a redeploy never sends a burst of calls upstream.
checked_at and, for B2B invoices, consultation_number next to the customer record.Vatline is an independent service. It is not affiliated with, endorsed by, or partnered with the European Commission or any national tax administration. VIES is cited only as the upstream source of the data.