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.
-
VIES (REST)
Can confirm valid and invalidVIES (REST): busy (MS_MAX_CONCURRENT_REQ), finished after the response. -
VIES (SOAP)
Can confirm valid and invalidVIES (SOAP): valid · first definitive answer. -
KBO Public Search
Can only confirm validKBO Public Search: valid · confirms, finished after the response. -
vatcomply
Can confirm valid and invalidvatcomply: valid · agrees, finished after the response. -
controleerbtwnummer.eu
Can only confirm validcontroleerbtwnummer.eu: valid · agrees, finished after the response. -
KBO open data
Register lookup, no VAT verdictKBO open data: found · adds register details.
Step by step
From request to stored record
-
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.
-
~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.
-
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[].
-
first definitive
Respond
The first answer that is enough on its own becomes the response. Sources still running show up as pending.
-
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.
-
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.
| 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: truein the record and every later response. - The next lookup within 24 hours gets this merged record from cache.
- 1 VIES (REST) VIES (SOAP)
- 2 KBO Public Search
- 3 vatcomply
- 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.