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

24 Aug 2026

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

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 after 30 days of history.

The aggregate number is useless

Across every member state and every sample, VIES was available 97.96% of the time.

That number is true and it is worthless. Nothing in your system consumes ‘VIES’. Your system consumes Germany, or Latvia, or Sweden, 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: 30 days, 2026-08-09 to 2026-09-08. 2,871–2,872 samples per state, 80,413 samples total.
  • Rule: only the literal string Available counts 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.

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 three of the 31 calendar days it touches are partly sampled – the first and last, which are window edges, and 2026-09-05, where we hold 76 samples instead of 96. Percentages are computed over samples actually taken, so a thin day is weighted less rather than counted as uptime.

Availability by member state

                                    uptime  missed  eps  longest
DE  ███████████████████████░░░░░   83.43%     476   30    12.3h
LV  ████████████████████████░░░░   87.43%     361   70    13.6h
SE  ██████████████████████████░░   93.21%     195    8    31.2h
LT  ███████████████████████████░   95.89%     118    1    28.5h
CZ  ███████████████████████████░   96.62%      97    5    13.1h
IE  ███████████████████████████░   96.66%      96    3    17.3h
BE  ███████████████████████████░   97.11%      83   11    16.9h
HU  ███████████████████████████░   97.14%      82    1    20.5h
DK  ████████████████████████████   99.09%      26    9     1.6h
FI  ████████████████████████████   99.09%      26    1     6.3h
NL  ████████████████████████████   99.13%      25    6     4.6h
MT  ████████████████████████████   99.37%      18   10     0.8h
SI  ████████████████████████████   99.44%      16    2     2.8h
RO  ████████████████████████████   99.72%       8    8     0.2h
IT  ████████████████████████████   99.86%       4    3     0.5h
LU  ████████████████████████████   99.90%       3    2     0.5h
BG  ████████████████████████████   99.93%       2    2     0.2h
CY  ████████████████████████████   99.93%       2    1     0.8h
EE  ████████████████████████████   99.93%       2    1     0.5h
PT  ████████████████████████████   99.93%       2    2     0.2h
AT  ████████████████████████████  100.00%       0    0     0.0h
EL  ████████████████████████████  100.00%       0    0     0.0h
ES  ████████████████████████████  100.00%       0    0     0.0h
FR  ████████████████████████████  100.00%       0    0     0.0h
HR  ████████████████████████████  100.00%       0    0     0.0h
PL  ████████████████████████████  100.00%       0    0     0.0h
SK  ████████████████████████████  100.00%       0    0     0.0h
XI  ████████████████████████████  100.00%       0    0     0.0h

eps is the number of distinct outage episodes – maximal runs of consecutive failing samples. It is the column that matters.

Eight of 28 member states missed nothing in 30 days. Germany, Latvia and Sweden account for 1,032 of the 1,642 unavailable samples, about 63% of all downtime in the dataset. But 20 states missed at least one sample, so this is not three broken backends and 25 perfect ones – it is a steep head and a real tail.

The percentage is not the shape

Look at Denmark and Finland. Both scored 99.09%. Both missed exactly 26 samples. By every number a status page would show you, they are the same member state.

They are not. Denmark’s 26 missed samples are nine separate episodes, none longer than 1.6 hours, scattered across the month. Finland’s are one episode: 2026-08-23, 07:31 to 13:36 UTC, six hours, and Finland was available in every other sample we took.

Those need opposite handling. Denmark will fail your request occasionally, forever, and the correct response is a retry – it will almost certainly work in twenty minutes. Finland will not fail your request at all, until one morning it fails everything for six hours, and no retry budget you can afford will outlast that. Retries fix Denmark. Only a queue fixes Finland.

Once you start reading the episode column, most of this table stops being a reliability ranking:

  • Lithuania, 95.89% – one episode, 28.5 hours, 2026-08-20 to 08-21. Lithuania was up in literally every other sample.
  • Hungary, 97.14% – one episode, 20.5 hours.
  • Malta, 99.37% – ten episodes, longest 45 minutes. The opposite: chronic, tiny, never serious.

Lithuania’s 95.89% and Malta’s 99.37% sit eight rows apart in that table, and the gap between them describes almost nothing you can build on. Lithuania is one 28-hour window you queue through. Malta is ten blips you retry past. The percentages rank the two states; they do not tell you which one your code has to survive.

Germany: switched off every night

DE
  00:00  █░░░░░░░░░░░░░░░░░░░░░░░    5.75%
  01:00  ███████████████████████░   94.39%
  02:00  ███████████████████████░   96.67%
  03:00  ███████████████████████░   96.67%
  04:00  ███████████████████████░   96.67%
  05:00  ████████████████████████   98.59%
  06:00  ████████████████████████  100.00%
  07:00  ████████████████████████  100.00%
  08:00  ████████████████████████  100.00%
  09:00  ████████████████████████  100.00%
  10:00  ████████████████████████  100.00%
  11:00  ████████████████████████  100.00%
  12:00  ████████████████████████  100.00%
  13:00  ████████████████████████  100.00%
  14:00  ████████████████████████  100.00%
  15:00  ████████████████████████  100.00%
  16:00  ████████████████████████  100.00%
  17:00  ███████████████████████░   97.24%
  18:00  ███████████████████████░   96.67%
  19:00  ███████████████████████░   96.67%
  20:00  ███████████████████████░   96.67%
  21:00  ███████░░░░░░░░░░░░░░░░░   28.44%
  22:00  ░░░░░░░░░░░░░░░░░░░░░░░░    0.00%
  23:00  ░░░░░░░░░░░░░░░░░░░░░░░░    0.00%

Germany has 30 outage episodes in 30 days, one per night, and not a single night was skipped. Twenty-nine of them start between 21:17 and 21:38 UTC and end between 00:38 and 01:08 UTC – a window that varies by about twenty minutes at each edge and never moves further. Each one runs between 3.3 and 4.1 hours.

The thirtieth is the exception: on Sunday 2026-08-16 the window opened at 17:17 instead of 21:20 and ran twelve hours to 05:17. That is the same nightly job, started early and run long – and it is the only night in the month that deviated.

Germany’s 83.43% is not flakiness, and it is not a reliability score. It is a scheduled shutdown – the single largest contributor to the aggregate, and entirely predictable. The residual 3% loss in the 02:00–05:00 and 17:00–20:00 hours is that one long Sunday smeared across the hours it touched, not a second pattern. The 01:00 hour, at 94.39%, is the only hour outside the nightly window that carries anything besides that Sunday: the Sunday is 60 of its 101 lost minutes, one night ran past the hour to 01:08, and ten more closed just before 01:00 and are credited half a sample interval past it.

We do not know why. The Commission’s endpoint reports state, not reason, and we found no published maintenance calendar for it. The window is predictable, and predictable downtime is the easy kind.

Latvia: degrades under load

LV
  00:00  ███████████████████████░   94.01%
  01:00  ██████████████████████░░   90.82%
  02:00  ██████████████████████░░   90.97%
  03:00  ███████████████████████░   96.67%
  04:00  ███████████████████████░   95.26%
  05:00  ███████████████████████░   94.00%
  06:00  ███████████████████████░   95.74%
  07:00  ███████████████████████░   97.85%
  08:00  ████████████████████████   99.09%
  09:00  ███████████████████████░   96.67%
  10:00  ████████████████████████   98.57%
  11:00  ███████████████████████░   96.16%
  12:00  █████████████████████░░░   87.21%
  13:00  ████████████████████░░░░   85.21%
  14:00  ████████████████████░░░░   82.18%
  15:00  ██████████████████░░░░░░   76.14%
  16:00  ████████████████░░░░░░░░   67.30%
  17:00  ██████████████████░░░░░░   74.24%
  18:00  █████████████████████░░░   85.45%
  19:00  ████████████████████░░░░   81.69%
  20:00  ███████████████████░░░░░   77.37%
  21:00  ███████████████░░░░░░░░░   62.94%
  22:00  █████████████████████░░░   87.82%
  23:00  ██████████████████████░░   92.44%

Latvia is the only member state in this dataset whose hour-of-day chart describes a genuine rate – Germany’s chart is a schedule and Sweden’s, below, is a single event wearing a rate’s clothing. Latvia’s is the real thing, and that is because Latvia produced 70 separate episodes – 40% of all 176 episodes in the dataset, and more than twice as many as any other member state. They are short: the median is three samples, about 45 minutes, and the longest single stretch is 13.6 hours. They are spread across 25 of the 30 days.

With that many independent events, the shape is real. Latvia is clean in the morning, degrades through the afternoon, bottoms out at 67.30% at 16:00 UTC and 62.94% at 21:00 UTC, and recovers overnight. 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. Finland you wait out. Latvia will answer if you ask again in ten minutes and 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.

Sweden: one incident wearing a percentage

Sweden’s hour-of-day chart is the most misleading chart in this post. That is why it is here.

SE
  00:00  ██████████████████████░░   93.33%
  01:00  ██████████████████████░░   93.33%
  02:00  ██████████████████████░░   93.33%
  03:00  ██████████████████████░░   93.33%
  04:00  █████████████████████░░░   87.43%
  05:00  ██████████████████████░░   93.33%
  06:00  ██████████████████████░░   93.33%
  07:00  ███████████████████████░   95.00%
  08:00  ██████████████████████░░   93.33%
  09:00  ██████████████████████░░   93.33%
  10:00  ██████████████████████░░   93.33%
  11:00  ██████████████████████░░   93.33%
  12:00  ██████████████████████░░   93.33%
  13:00  ██████████████████████░░   93.33%
  14:00  ██████████████████████░░   93.33%
  15:00  ██████████████████████░░   93.33%
  16:00  ██████████████████████░░   93.33%
  17:00  ██████████████████████░░   93.33%
  18:00  ██████████████████████░░   93.33%
  19:00  ███████████████████████░   95.32%
  20:00  ███████████████████████░   95.51%
  21:00  ███████████████████████░   94.17%
  22:00  ██████████████████████░░   93.33%
  23:00  ██████████████████████░░   93.33%

Flat. Dead flat, 93.33% at almost every hour of the day. Read as a rate, it says Sweden drops about one request in fifteen, uniformly, around the clock – the profile of a backend under permanent mild load. You would size a retry budget from it.

That reading is wrong in every particular. Sweden has eight episodes, and four of them are this:

2026-08-22T04:58 -> 2026-08-24T04:44   Sweden unavailable, 47 of the 48 hours

A single incident from Saturday morning to Monday morning, logged as four adjacent runs with three brief flickers of availability between them. It is 188 of Sweden’s 195 missed samples. Spread one 48-hour outage across a 30-day window and bucket it by hour of day, and every hour inherits the same 1/15th of downtime – which is exactly the flat line above. The chart is not measuring a rate. It is measuring one weekend, divided by 30.

The remaining four episodes are the actual Sweden: all of them are 1–2 samples long, at 04:17–04:38 UTC, and all four fall on a Monday (08-10, 08-17, 08-31, 09-07 – the one Monday missing from that run, 08-24, is buried inside the bad weekend). That is a weekly maintenance blip of at most half an hour. Outside its bad weekend, Sweden missed seven samples in a month: 99.76%, with a Monday-morning hiccup.

So Sweden’s real operational profile is: ignore it 51 weeks a year, and have a plan for the week it disappears entirely. The 93.21% describes neither of those states, and no amount of hour-bucketing recovers them. Only the episode list does.

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, a record that a check took place and the thing you keep next to the invoice when a tax authority asks you to show 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 that country’s domestic answer. Those are real fallbacks, but not a substitute for VIES: a domestic registration is not a VIES confirmation that the number is valid for intra-EU transactions, and the answer carries no consultation number.

Finland’s PRH, France’s SIRENE, Denmark’s CVR, Sweden’s Bolagsverket, 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. The Netherlands lands in the same place from the other direction: KVK is a company register we do not call at all, so NL is VIES-only – name and address, nothing from a register.

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.
  • Count episodes, not just failures. One number cannot separate Denmark from Finland at 99.09%. Episodes per month and longest episode can, and they are what your retry policy actually depends on.
  • 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.
  • Size your queue for the worst incident, not the longest logged episode. Sweden’s bad weekend ran 48 hours and was logged as four separate episodes, because availability flickered three times in the middle. The number your pending-validation table has to survive is the 48, not the 31.2-hour longest run and certainly not 93.21%.
  • 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 checkVat call returns. An independent probe would be a stronger instrument.
  • The window is 30 days. Long enough to establish that Germany’s window repeated on all 30 nights, and to distinguish chronic states from one-off ones. Not long enough for an annual figure, and we make no such claim.
  • Episode boundaries are sample-resolution. An episode starts at the first failing sample and ends at the last, so a true outage is up to 15 minutes longer at each edge. Durations quoted here credit half an interval on each side, which is why they reconcile with the sample counts to within a third of a percentage point.
  • Single-episode percentages are not rates. Lithuania’s 95.89%, Hungary’s 97.14% and Finland’s 99.09% each come from exactly one event. Repeat the month and you would plausibly get 100%. The same caution applies in reverse to the eight states at 100%: 30 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

Every figure above comes from one script over one frozen dataset, both in the repo:

python3 scripts/vies-report.py

That reads scripts/data/vies-status-2026-09-08.json, the capture this post is written from, and prints the headline numbers, the availability table, the hour-of-day charts for the worst three states, and the full episode log for every state that had one.

The snapshot is pinned deliberately. The live API serves a rolling 30-day window, so it answers ‘how is VIES doing now’, not ‘how was VIES doing in the month this post covers’ – run it tomorrow and Germany’s episode list has moved by a day. Both endpoints are public and unauthenticated:

curl -s https://api.vatnode.dev/v1/vies-status | jq '.uptime'
curl -s https://api.vatnode.dev/v1/vies-status/country/DE | jq '.outages'

The first returns per-country uptime and sample counts; the second adds the outage log, the daily series and the hour and weekday breakdowns for one state. Passing --live to the script fetches those instead of the snapshot, so you can run the same analysis against today’s window and compare. 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.

If you want the retry and fallback behaviour described here without building it, that is what vatnode does.