VIES Is Down Again: How to Keep EU VAT Checks Working During VIES Downtime
#vat#api#ecommerce#webdev
If you sell B2B in the EU, you probably call VIES (the European Commission's VAT Information Exchange System) to check a customer's VAT number before applying the reverse charge. And if you have done that for more than a few weeks, you have seen this:
{
"actionSucceed": false,
"errorWrappers": [{ "error": "MS_UNAVAILABLE" }]
}
Your checkout freezes, your invoicing job retries forever, or worse, your code treats "VIES didn't answer" as "this VAT number is invalid" and charges a legitimate business customer VAT they shouldn't pay.
This post explains why VIES downtime happens, the different failure modes, and a pattern that keeps VAT checks working anyway.
VIES is a router, not a database
The key thing to understand: VIES does not hold VAT data itself. When you ask it about DE123456789, it forwards the request to Germany's national system and relays the answer. So there are really 28 services that can fail (27 member states + Northern Ireland via XI), plus the central hub.
That gives you several distinct failure modes:
| VIES error | What it means | What you should do |
|---|---|---|
MS_UNAVAILABLE |
That member state's system is offline (often scheduled maintenance, frequently nights and weekends) | Retry later; other countries still work |
TIMEOUT |
The member state didn't answer in time | Retry with backoff |
SERVICE_UNAVAILABLE |
The central VIES service is down | Retry later; nothing works |
MS_MAX_CONCURRENT_REQ / GLOBAL_MAX_CONCURRENT_REQ |
You (or everyone) are being throttled | Back off, then retry |
INVALID_INPUT |
The number is malformed | Don't retry, the input is wrong |
You can see live per-country availability yourself:
curl -s https://ec.europa.eu/taxation_customs/vies/rest-api/check-status
On the evening we wrote this, it listed Germany as "Unavailable" while every other member state was up. That's normal VIES behaviour, not a one-off.
The three mistakes almost everyone makes
1. Treating "unavailable" as "invalid." A VIES outage tells you nothing about the VAT number. Collapsing both into valid: false blocks real customers.
2. Calling VIES on every request. The same customer's VAT number gets checked at signup, at every checkout, and on every invoice. Every one of those calls is another chance to hit an outage or a throttle.
3. No memory of past answers. If you confirmed IE6388047V was valid yesterday, that's very strong evidence it's still valid today. Throwing that away the moment VIES hiccups is wasteful.
A pattern that survives VIES downtime
The fix is a small, boring layer between your app and VIES:
- Syntax check first. Every country has a known format (
ATU+ 8 digits,NL+ 9 digits +B+ 2 digits, ...). Reject obvious typos locally and never bother VIES. - Fresh cache (24h). Store each successful answer for a day. Repeat checks are instant and don't touch VIES at all.
- Stale cache (30 days). Keep a longer-lived "last known good" copy. If VIES or the member state is down, serve that copy and flag it clearly as stale with the original check timestamp.
- Explicit errors otherwise. If you have never seen the number and VIES is down, return a distinct
service_unavailableormember_state_unavailableerror, nevervalid: false.
Your application logic then becomes simple:
if (vat.valid === true) applyReverseCharge();
else if (vat.valid === false) chargeVat();
else queueForRecheck(vat.error.code); // unknown: don't guess
Doing it with one API call
We built exactly this as a hosted API so you don't have to maintain it: VAT & IBAN Validation API. One endpoint, VIES under the hood (UK numbers via HMRC coming soon), with the caching and fallback described above.
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": "DE811907980"}'
When Germany is down but we have a recent answer, you get:
{
"vat": {
"valid": true,
"vat_number": "DE811907980",
"country_code": "DE",
"source": "vies",
"cached": true,
"stale": true,
"checked_at": "2026-10-15T09:12:00.000Z",
"warning": {
"code": "member_state_unavailable",
"message": "The member state's VAT database is temporarily unavailable via VIES. ..."
}
},
"billable": false
}
Note billable: false: cache hits, stale fallbacks and errors never count against your quota. You only pay for fresh, successful validations.
If we have nothing cached, you get an honest HTTP 503 with member_state_unavailable and a Retry-After header, so your job queue knows exactly what to do.
You can also check live status before a batch run:
curl https://api.vatpulse.workers.dev/status
What about compliance?
Serving a stale result is a business decision. For most SaaS and e-commerce flows, "this number was confirmed valid by VIES 2 days ago" is a perfectly reasonable basis to proceed and re-check later. Because every response includes checked_at, source and a stale flag, you can store an audit trail and decide your own policy, for example "accept stale results up to 7 days old, otherwise hold the invoice."
Try it
- Free tier: 100 validations/month: get a key on RapidAPI
- Docs and OpenAPI spec: https://api.vatpulse.workers.dev/docs
- Paid plans: Basic $19/mo (2,000) and Pro $49/mo (10,000)
VIES will keep going down on Sunday nights. Your checkout doesn't have to.
Try it free
100 validations/month. Get a free key on RapidAPI · Docs