← Blog

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:

  1. 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.
  2. Fresh cache (24h). Store each successful answer for a day. Repeat checks are instant and don't touch VIES at all.
  3. 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.
  4. Explicit errors otherwise. If you have never seen the number and VIES is down, return a distinct service_unavailable or member_state_unavailable error, never valid: 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

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