How to Handle VIES Downtime in Your Application
VIES national nodes go offline. It is not an edge case — it happens regularly, especially for Germany, which is both the most important EU market and one of the most unreliable VIES nodes. Here is how to build a system that handles it correctly.
Why VIES goes down and how often
VIES is not a single system — it is a network of 27 national nodes, each operated by a member state's tax authority. When you query VIES for an Irish VAT number, the European Commission infrastructure routes the request to Ireland's Revenue Commissioners system in real time. If that system is unavailable, VIES returns an error.
Individual nodes go offline for planned maintenance (typically outside business hours), for unplanned outages, and in Germany's case, for rate limiting — the BZSt node throttles requests during peak periods. Outages can last minutes or hours. There is no official status page for VIES node availability.
The VIES_UNAVAILABLE error
When the vatnode API cannot reach VIES for a given country, it returns HTTP 503 with a structured error body:
{
"error": {
"code": "VIES_UNAVAILABLE",
"message": "The VIES service for IE is temporarily unavailable. Retry later."
}
}This is distinct from an invalid VAT number (HTTP 200 with "valid": false) or a format error (HTTP 400 with INVALID_FORMAT). A 503 always means "try again later" — not "this VAT number is invalid".
The right pattern: queue, not block
The most common mistake is blocking a checkout or invoice on VIES unavailability. This is wrong for two reasons:
- The customer is almost certainly legitimate. VIES being down does not mean the VAT number is invalid. The customer provided it in good faith and their registration is almost certainly still active.
- You cannot verify either way. Blocking is not a safety measure — it is just friction that loses you a sale.
The correct approach: allow the transaction to proceed, apply standard VAT (the safe default), and queue an async job to re-validate when VIES recovers.
async function validateVatForCheckout(vatId: string, customerId: string) {
const res = await fetch(
`https://api.vatnode.dev/v1/vat/${encodeURIComponent(vatId)}`,
{ headers: { Authorization: `Bearer ${process.env.VATNODE_API_KEY}` } }
)
if (res.status === 503) {
// VIES unavailable — queue for retry, do not block
await queue.add('validate-vat-retry', {
vatId,
customerId,
scheduledFor: Date.now() + 5 * 60 * 1000, // retry in 5 minutes
})
return {
valid: null, // unknown — pending retry
vatRate: 0.20, // apply standard VAT in the meantime
requiresRetry: true,
}
}
const data = await res.json()
return {
valid: data.valid,
vatRate: data.valid ? 0 : 0.20,
checkId: data.checkId,
requiresRetry: false,
}
}Germany: BZSt fallback
Germany is a special case. The VIES DE node has historically been rate-limited or unavailable more frequently than other member states. The German Bundeszentralamt für Steuern (BZSt) operates a separate VAT validation service — eVatR — that vatnode uses as an automatic fallback.
When you validate a German VAT number (prefix DE) and the VIES DE node is unavailable, vatnode automatically retries against BZSt. If BZSt responds, the response includes "source": "BZST" instead of "source": "VIES". Note that BZSt returns valid/invalid status only — company name and address are not available from this source.
This means German VAT numbers almost never return VIES_UNAVAILABLE from vatnode — the BZSt fallback keeps the validation working even when the VIES DE node is down.
Retry strategy
When VIES is unavailable, a good retry strategy uses exponential backoff with a maximum delay. VIES outages typically resolve within 30–60 minutes:
// Retry intervals (minutes): 5, 15, 30, 60, 120
// After 5 retries (~3.5 hours), flag for manual review
const RETRY_DELAYS = [5, 15, 30, 60, 120]
async function scheduleVatRetry(vatId: string, customerId: string, attempt = 0) {
if (attempt >= RETRY_DELAYS.length) {
// Flag for manual review after exhausting retries
await flagForManualReview({ vatId, customerId })
return
}
const delayMs = RETRY_DELAYS[attempt] * 60 * 1000
await queue.add(
'validate-vat-retry',
{ vatId, customerId, attempt: attempt + 1 },
{ delay: delayMs }
)
}Build a resilient VAT integration with vatnode
vatnode handles retries, BZSt fallback, and structured error codes so your application can implement the right pattern. Free plan, 20 requests/month.