VIES Uptime, Measured — 28 Member States, One Sample Every 15 Minutes
24 August 2026

The European Commission runs an endpoint that reports whether each member state's VAT validation backend is reachable right now. It answers "is Germany up". It keeps no history, so it cannot answer the question that actually changes your code: "is Germany reliably down at the time my nightly job runs".
So we started sampling it every 15 minutes and keeping every sample. This is what the 28 member states look like once you have history.
The aggregate number is useless
Across every member state and every sample, VIES was available 98.71% of the time.
That number is true and it is worthless. Nothing in your system consumes "VIES". Your system consumes Germany, or Ireland, or Latvia, and those three behave nothing like each other and nothing like the average.
What we measure
- Source: the Commission's own
GET /taxation_customs/vies/rest-api/check-status, which returns a per-country availability string. - Interval: one sample per member state every 15 minutes, 96 per state per day.
- Coverage: 28 codes — the EU-27 plus XI (Northern Ireland). Greece appears as
EL, following VIES rather than ISO 3166. - Window in this post: 5 days, 548 samples per state, 15,344 samples total.
- Rule: only the literal string
Availablecounts as up. Every other state the Commission reports — including strings we have not seen before — counts as down rather than being silently dropped.
Two caveats before the charts. They bound everything that follows.
This is the Commission's self-report, not an independent probe. It is what VIES believes about its own member states. It can disagree with what an actual checkVat call does, and it can lag a member state going down.
And the history is short. Five days is enough to establish a pattern that repeats daily. It is not enough to claim an annual uptime figure, and this post does not.
Availability by member state
DE ████████████████████████░░░░ 83.94% 88 missed
LV █████████████████████████░░░ 88.87% 61 missed
IE ███████████████████████████░ 95.44% 25 missed
BE ████████████████████████████ 98.91% 6 missed
MT ████████████████████████████ 98.91% 6 missed
EE ████████████████████████████ 99.45% 3 missed
IT ████████████████████████████ 99.64% 2 missed
RO ████████████████████████████ 99.64% 2 missed
CZ ████████████████████████████ 99.82% 1 missed
DK ████████████████████████████ 99.82% 1 missed
NL ████████████████████████████ 99.82% 1 missed
PT ████████████████████████████ 99.82% 1 missed
SE ████████████████████████████ 99.82% 1 missed
AT ████████████████████████████ 100.00% 0 missed
BG ████████████████████████████ 100.00% 0 missed
CY ████████████████████████████ 100.00% 0 missed
EL ████████████████████████████ 100.00% 0 missed
ES ████████████████████████████ 100.00% 0 missed
FI ████████████████████████████ 100.00% 0 missed
FR ████████████████████████████ 100.00% 0 missed
HR ████████████████████████████ 100.00% 0 missed
HU ████████████████████████████ 100.00% 0 missed
LT ████████████████████████████ 100.00% 0 missed
LU ████████████████████████████ 100.00% 0 missed
PL ████████████████████████████ 100.00% 0 missed
SI ████████████████████████████ 100.00% 0 missed
SK ████████████████████████████ 100.00% 0 missed
XI ████████████████████████████ 100.00% 0 missed
15 of 28 member states missed nothing at all. Of the 198 unavailable samples in the whole dataset, 174 belong to three countries. The distribution is not a bell curve with a long tail. It is a handful of specific backends with specific problems, and everyone else is fine.
Which means the useful question is never "how reliable is VIES". It is "what does this member state do, and when".
Germany: switched off every night
DE
00:00 ░░░░░░░░░░░░░░░░░░░░░░░░ 0.00% n=24
01:00 ████████████████████████ 100.00% n=22
02:00 ████████████████████████ 100.00% n=23
03:00 ████████████████████████ 100.00% n=23
04:00 ████████████████████████ 100.00% n=24
05:00 ████████████████████████ 100.00% n=24
06:00 ████████████████████████ 100.00% n=23
07:00 ████████████████████████ 100.00% n=24
08:00 ████████████████████████ 100.00% n=23
09:00 ████████████████████████ 100.00% n=22
10:00 ████████████████████████ 100.00% n=24
11:00 ████████████████████████ 100.00% n=25
12:00 ████████████████████████ 100.00% n=23
13:00 ████████████████████████ 100.00% n=25
14:00 ████████████████████████ 100.00% n=24
15:00 ████████████████████████ 100.00% n=24
16:00 ████████████████████████ 100.00% n=21
17:00 ████████████████████████ 100.00% n=20
18:00 ████████████████████████ 100.00% n=20
19:00 ████████████████████████ 100.00% n=20
20:00 ████████████████████████ 100.00% n=20
21:00 ███████░░░░░░░░░░░░░░░░░ 27.27% n=22
22:00 ░░░░░░░░░░░░░░░░░░░░░░░░ 0.00% n=24
23:00 ░░░░░░░░░░░░░░░░░░░░░░░░ 0.00% n=24
Germany is available in every single sample between 01:00 and 20:00 UTC, and unavailable in every single sample between 22:00 and 00:59 UTC. The 21:00 hour is the boundary: 27% available, so the window opens somewhere around 21:40 UTC.
That is roughly 21:40–01:00 UTC, and it occurred on all five nights — weekends included. Germany's 83.94% is not flakiness. It is a scheduled shutdown, and it is the single largest contributor to the aggregate.
We do not know why. The Commission's endpoint reports state, not reason, and we found no published maintenance calendar for it. What matters for your code is that the window is predictable, and predictable downtime is the easy kind.
Ireland: one outage, one block
Ireland's misses are not spread out. Every one of them falls inside a single contiguous stretch: Monday 22:00 UTC through Tuesday 04:00 UTC, unavailable in every sample from 23:00 to 03:59, partially available at the two edges. Outside that block, Ireland missed nothing.
Six hours, once, in five days. That is 95.44%, and it is a different failure from Germany's. Nothing about Monday night predicts next Monday night. An unplanned outage looks like a step function and then it is over.
If you build a retry schedule from Ireland's 95.44%, you will build the wrong thing. The number describes an event, not a rhythm.
Latvia: degrades under load
LV
00:00 ████████████████████████ 100.00% n=24
01:00 ████████████████████████ 100.00% n=22
02:00 ████████████████████████ 100.00% n=23
03:00 ████████████████████████ 100.00% n=23
04:00 ████████████████████████ 100.00% n=24
05:00 ████████████████████████ 100.00% n=24
06:00 ████████████████████████ 100.00% n=23
07:00 ████████████████████████ 100.00% n=24
08:00 ████████████████████████ 100.00% n=23
09:00 ████████████████████░░░░ 81.82% n=22
10:00 █████████████████████░░░ 87.50% n=24
11:00 ██████████████████████░░ 92.00% n=25
12:00 █████████████████░░░░░░░ 69.57% n=23
13:00 ██████████████████░░░░░░ 76.00% n=25
14:00 █████████████░░░░░░░░░░░ 54.17% n=24
15:00 ████████████░░░░░░░░░░░░ 50.00% n=24
16:00 ███████████░░░░░░░░░░░░░ 47.62% n=21
17:00 ██████████████████████░░ 90.00% n=20
18:00 ████████████████████████ 100.00% n=20
19:00 ████████████████████████ 100.00% n=20
20:00 ████████████████████████ 100.00% n=20
21:00 ██████████████████████░░ 90.91% n=22
22:00 ███████████████████████░ 95.83% n=24
23:00 ████████████████████████ 100.00% n=24
Latvia is clean overnight — every sample from 23:00 through 08:00 UTC came back available — and then falls apart across the working day, bottoming out at 47.62% availability at 16:00 UTC, early evening local time. It never goes fully down for a whole hour and it never stays up for one either.
This is the shape that breaks naive retry code. Germany you route around. Ireland you wait out. Latvia will answer if you ask again in ten minutes, and it will refuse if you ask again in ten seconds — but a fixed exponential backoff cannot tell those apart, so it burns its whole budget inside the same degraded minute and gives up.
Three states, three failure modes, one aggregate number covering all of them.
What VIES actually returns when it fails
Availability is only half the problem. The other half is that the SOAP API's failure modes are underdocumented, and several of them are not really failures.
The fault strings are the useful part. VIES answers a failed checkVat with a SOAP fault whose faultstring is a bare code. MS_UNAVAILABLE means that one member state is down. SERVICE_UNAVAILABLE means the Commission's own gateway is. MS_MAX_CONCURRENT_REQ and GLOBAL_MAX_CONCURRENT_REQ are rate limits, per-state and global. INVALID_INPUT is a malformed number. Treating these as one generic error throws away every signal you need: three of them say "retry later", one says "retry somewhere else", one says "never retry this".
Not every fault is a code. Pass a requester country that is not an EU member state and production VIES answers with a human-readable Invalid Requester member state instead of a documented code. Pass an invalid requester VAT under a valid country and you get the exact INVALID_REQUESTER_INFO string. Same class of error, two different response formats, and only one of them is in the docs. If you match on exact codes only, the first one falls through to your generic 502 handler.
--- is not an address. VIES fills unavailable name and address fields with a literal three-hyphen placeholder. It is a string, it is truthy, and it will end up rendered in your UI and stored in your database as a company name unless you normalise it to null.
checkVat and checkVatApprox disagree about field names. The plain call returns name and address. The approx call returns traderName, and an address that may arrive flat as traderAddress or split across traderStreet, traderPostcode and traderCity. Same data, three shapes, depending on the member state.
A mandatory field can be empty. checkVatApprox returns a requestIdentifier — the consultation number, the Commission-issued evidence that a check took place, the thing you keep next to the invoice when a tax authority asks you to prove you validated a customer. The schema declares it mandatory. It is also schema-legal to return it as an empty string. A response can therefore be simultaneously valid, successful, and useless as evidence. Fail loudly on that rather than storing the empty string and finding out during an audit.
Why national registers are a worse fallback than they look
The obvious move when a member state's VIES node is down is to ask that country's own register directly. It works, but only for some countries, and the reason is not technical.
A national VAT register and a national company register answer different questions. Poland's MF, Romania's ANAF and the Czech ARES dic status expose the VAT register itself — asking them "is this VAT number registered" gets you the same answer VIES would have given. Those are real fallbacks.
Finland's PRH, France's SIRENE, Denmark's CVR, Sweden's Bolagsverket, the Dutch KVK, the Belgian CBE are company registers. A company can be listed and active there without being VAT-registered at all — below the threshold, on a small-business exemption, or exempt by activity. Some of them exclude sole traders entirely. Deriving valid: true from "the company exists in the register" produces false positives precisely when you are least able to check them, which is during an outage. We use those sources to enrich a result, never to decide one.
Germany is the interesting case. The BZSt runs eVatR, which looks like the answer to Germany's nightly window. It is not: it only answers a German business asking about a foreign VAT number. Confirming a German number through it requires an entitlement we do not hold, and every request we made returned evatr-0006, not authorised. Germany's nightly window has no fallback. You wait, or you queue.
What to do with this
- Track availability per country, not globally. A single circuit breaker in front of VIES opens because Latvia is having an afternoon and stops you from validating the other 27.
- Do not schedule German validation between 21:00 and 01:00 UTC. If your nightly batch runs at midnight UTC, it currently fails every German number, every night, and the fix is a cron expression.
- Make backoff per-country and jittered. Latvia's shape needs minutes of spacing, not seconds. Retrying four times in ninety seconds is four failures.
- Distinguish "unavailable" from "invalid". They are different words in the response and they must be different words in your database. A number that could not be checked is not a number that failed the check — and treating an outage as an invalid VAT ID is how you accidentally charge a B2B customer domestic VAT.
- Queue, don't block. Nothing in a checkout flow should sit on a synchronous call to a service that is switched off for three hours a night.
What this does not measure
The limits, plainly:
- It is the Commission's self-report. We sample what VIES says about its member states, not what a
checkVatcall returns. An independent probe would be a stronger instrument, and it is the obvious next thing to build. - The window is 5 days. Long enough to establish that Germany's window repeated on every night observed. Not long enough for a monthly or annual figure, and we make no such claim.
- Ireland's 95.44% is one event. A single six-hour outage in a short window produces a percentage that looks like a reliability rating and is not one. The same is true in reverse for the 15 states at 100%: five clean days is not a guarantee.
- Availability is not correctness. A member state can be up and still return a stale or wrong answer. We do not measure that here.
Reproduce it
The data behind every chart is public and unauthenticated:
curl -s https://api.vatnode.dev/v1/vies-status | jq '.uptime'
That returns the per-country uptime, the sample counts, and the weekday × hour heatmap the charts are built from. There is a rendered version at vatnode.dev/vies-status, updated continuously, and the underlying Commission endpoint is at ec.europa.eu/taxation_customs/vies/rest-api/check-status if you would rather sample it yourself.
The first chart in this post is the whole of this script, standard library only:
import json, urllib.request
url = "https://api.vatnode.dev/v1/vies-status"
data = json.load(urllib.request.urlopen(url))
for u in sorted(data["uptime"], key=lambda u: u["uptime30d"] or 0):
up = u["uptime30d"] or 0
filled = round(up * 28)
missed = round(u["sampleCount"] * (1 - up))
print(f"{u['countryCode']} {'█' * filled}{'░' * (28 - filled)}"
f" {up * 100:6.2f}% {missed:>4} missed")
The hour-of-day charts come out of the same document: data["countryHeatmaps"]["DE"] is a list of weekday × hour cells, each with an availability fraction and a sample count. Group by hourUtc, weight each cell by its samples, and you have the German profile above.
If you want the retry and fallback behaviour described here without building it, that is what vatnode does.