Skip to content
VAT API

How it works

The race, explained

Every uncached lookup starts all sources at the same moment. The first definitive answer goes back to you; the others keep running to verify and enrich the stored record. Here is exactly what happens.

One lookup, five lanes

A Belgian number that nobody looked up before. Every lane starts at t = 0. The response leaves as soon as VIES SOAP says "valid"; the other lanes finish behind the line and are merged into the stored record.

  1. VIES (REST)

    Can confirm valid and invalid
    VIES (REST): busy (MS_MAX_CONCURRENT_REQ), finished after the response.
  2. VIES (SOAP)

    Can confirm valid and invalid
    VIES (SOAP): valid · first definitive answer.
  3. KBO Public Search

    Can only confirm valid
    KBO Public Search: valid · confirms, finished after the response.
  4. vatcomply

    Can confirm valid and invalid
    vatcomply: valid · agrees, finished after the response.
  5. controleerbtwnummer.eu

    Can only confirm valid
    controleerbtwnummer.eu: valid · agrees, finished after the response.
  6. KBO open data

    Register lookup, no VAT verdict
    KBO open data: found · adds register details.
first definitive answer finished later, merged error background, after the response Illustration, not to scale

Step by step

From request to stored record

  1. Before any network call

    Normalise and check the format

    Spaces, dots and lowercase are cleaned up, GR becomes EL. A local syntax check (with the Belgian checksum) rejects impossible numbers in a few milliseconds, without calling anyone.

  2. ~ms

    Seen before? Answer from storage

    If we have a stored result you get it straight away, with source and checked_at. Younger than 24 hours: served_from: "cache". Older: the same instant answer flagged stale: true, while a background job races every source to refresh it. Add fresh=1 to force a live check.

  3. t = 0

    New number: plan the race

    Every source that applies is started at the same moment. Sources that cannot help are skipped up front, with a reason in sources[].

  4. first definitive

    Respond

    The first answer that is enough on its own becomes the response. Sources still running show up as pending.

  5. after the response

    Finish, merge, verify

    The remaining requests are not cancelled. When they finish, all definitive answers are merged by authority into the stored record. Disagreements set conflict: true.

  6. if nothing decides

    Never guess

    If no source can give a definitive answer and nothing is stored, you get valid: null with a Retry-After header instead of a guess.

What counts as an answer

Not every "no" is a no

Some sources can only confirm that a number is valid. If they can't find it, that is not proof it is invalid. So they can win the race with a yes, but never with a no.

Which sources can decide valid and invalid on their own
Source Can say valid Can say invalid
VIES (REST) Can confirm valid and invalid yes yes
VIES (SOAP) Can confirm valid and invalid yes yes
KBO Public Search Can only confirm valid yes no → inconclusive
vatcomply Can confirm valid and invalid yes yes
controleerbtwnummer.eu Can only confirm valid yes no → inconclusive
KBO open data Register lookup, no VAT verdict no no

Background enrichment

Fast first, then right

The fastest source is not always the highest-ranked or the most complete. Once every lane has finished, the stored record is rebuilt from all definitive answers.

  • Validity comes from the highest-ranked source that answered.
  • Name and address come from the best-ranked source that agrees and has them.
  • Conflicts are kept visible: conflict: true in the record and every later response.
  • The next lookup within 24 hours gets this merged record from cache.
authority rank, highest first
  1. 1 VIES (REST) VIES (SOAP)
  2. 2 KBO Public Search
  3. 3 vatcomply
  4. 4 controleerbtwnummer.eu

Resilience

Built for VIES' bad days

Fresh and stale cache

Every stored result is served instantly. Younger than 24 hours it is fresh; older, it is flagged stale and refreshed by a background race, so the next lookup gets the new answer.

Circuit breaker

Repeated failures for one source and country pause that source for that country, reported as circuit_open. After a cool-down it rejoins the race. One busy country never takes a source out for everyone.

Skipped up front

not_applicable
KBO only runs for Belgian numbers.
member_state_unavailable
VIES says that country's system is offline, so VIES lanes are not even started.
circuit_open
A source that kept failing for this country is paused for a cool-down instead of costing a timeout.
quota
A provider's daily quota is nearly used up.
disabled
No credentials configured for that provider.

Start validating in a minute

Free API key by email. No credit card, no password.