VAT Number Check API: Validate EU VAT Numbers in 5 Minutes
#api#saas#tutorial#python
If you charge EU business customers, you need to check their VAT number before applying the reverse charge. The official tool is VIES, but its web form isn't an integration, and its API has quirks that take a while to learn the hard way.
This guide shows how to add a reliable VAT number check to your app with a single HTTP call.
What a "VAT number check" actually answers
A VAT check answers one question: is this number currently registered for intra-EU trade? Depending on the country, you may also get the registered company name and address.
Things to know up front:
- The prefix matters. EU VAT numbers start with a 2-letter code:
DE,FR,NL... Greece usesEL, notGR. Northern Ireland usesXI. - Name/address availability varies. Some member states (Germany and Spain, for example) don't return them through VIES. That's not an error.
- The UK left VIES. Since 2021,
GBnumbers are checked with HMRC's own API, which needs separate OAuth credentials. - Formats differ wildly.
ATU12345678,NL123456789B01,FRXX123456789,IE1234567WA. A local format check catches typos before any network call.
The one-call version
With the VAT & IBAN Validation API, you send the number as the user typed it (spaces, dots and dashes are fine):
curl -X POST https://vatpulse1.p.rapidapi.com/validate \
-H "X-RapidAPI-Key: $RAPIDAPI_KEY" \
-H "X-RapidAPI-Host: vatpulse1.p.rapidapi.com" \
-H "Content-Type: application/json" \
-d '{"vat": "IE 6388047 V"}'
{
"vat": {
"input": "IE 6388047 V",
"valid": true,
"vat_number": "IE6388047V",
"country_code": "IE",
"country_name": "Ireland",
"name": "GOOGLE IRELAND LIMITED",
"address": "3RD FLOOR, GORDON HOUSE, BARROW STREET, DUBLIN 4",
"source": "vies",
"cached": false,
"stale": false,
"checked_at": "2026-10-17T22:44:26.350Z",
"error": null,
"warning": null
},
"billable": true,
"usage": { "plan": "free", "used": 1, "limit": 100, "remaining": 99, "reset_at": "2026-11-01T00:00:00.000Z" }
}
source tells you who confirmed it (vies or hmrc), and checked_at gives you a timestamp for your audit trail.
JavaScript
async function checkVat(vat) {
const res = await fetch("https://vatpulse1.p.rapidapi.com/validate", {
method: "POST",
headers: {
"X-RapidAPI-Key": process.env.RAPIDAPI_KEY,
"X-RapidAPI-Host": "vatpulse1.p.rapidapi.com",
"Content-Type": "application/json",
},
body: JSON.stringify({ vat }),
});
const data = await res.json();
return data.vat; // { valid: true | false | null, name, address, error, ... }
}
Python
import os, requests
def check_vat(vat: str) -> dict:
r = requests.post(
"https://vatpulse1.p.rapidapi.com/validate",
headers={
"X-RapidAPI-Key": os.environ["RAPIDAPI_KEY"],
"X-RapidAPI-Host": "vatpulse1.p.rapidapi.com",
},
json={"vat": vat},
timeout=15,
)
return r.json()["vat"]
Handle three outcomes, not two
The single most important design decision: valid is three-valued.
valid |
Meaning | Typical action |
|---|---|---|
true |
Registered | Apply reverse charge, show company name |
false |
Not registered or malformed | Ask the user to correct it, or charge VAT |
null |
Couldn't be determined right now | Accept provisionally and re-check later, or ask user to retry |
When valid is false or null, the error.code tells you why:
invalid_format: doesn't match that country's format (caught locally, free)invalid_country: unknown prefixmember_state_unavailable: that country's system is downservice_unavailable: VIES/HMRC is downupstream_rate_limited: VIES is throttling
And when VIES is down but the number was confirmed recently, you get valid: true with stale: true and a warning, so outages don't block your customers. (More on that in our post on VIES downtime.)
UK VAT numbers
UK GB support via HMRC's "Check a UK VAT number" service is coming soon: it's built but not yet enabled, so GB123456789 currently returns service_unavailable (not billed). Once enabled it will return the same response shape with source: "hmrc". Northern Ireland XI numbers work today: they go through VIES.
Caching and cost
Successful results are cached for 24 hours. Repeat checks of the same number (signup, then checkout, then invoice) return cached: true and don't count against your quota. Errors don't count either. You only pay for fresh successful validations.
Your plan quota is shown in your RapidAPI dashboard, and you can also query usage from the API:
curl https://vatpulse1.p.rapidapi.com/usage \
-H "X-RapidAPI-Key: $RAPIDAPI_KEY" \
-H "X-RapidAPI-Host: vatpulse1.p.rapidapi.com"
Pricing
- Free: 100 validations/month
- Basic: $19/month, 2,000 validations
- Pro: $49/month, 10,000 validations
Subscriptions, keys and billing are handled through RapidAPI.
Next steps: get a free key on RapidAPI, then read the API docs or download the OpenAPI spec.
Try it free
100 validations/month. Get a free key on RapidAPI · Docs