Error Handling
vatnode uses standard HTTP status codes and returns detailed error information in JSON format.
Error Response Format
All errors follow this structure:
{
"error": {
"code": "ERROR_CODE",
"message": "Human-readable error message",
"requestId": "req_abc123",
"details": {}
}
}| Field | Type | Description |
|---|---|---|
| code | string | Machine-readable error code |
| message | string | Human-readable description |
| requestId | string | Unique request ID for debugging |
| details | object | Additional context (optional) |
HTTP Status Codes
| Status | Meaning |
|---|---|
| 200 | Success |
| 400 | Bad Request - Invalid input |
| 401 | Unauthorized - Invalid or missing API key |
| 403 | Forbidden - Insufficient permissions |
| 404 | Not Found - Resource doesn't exist |
| 422 | Unprocessable Entity — configured requester VAT is invalid in VIES |
| 429 | Too Many Requests — Free plan monthly quota exhausted |
| 500 | Internal Server Error |
| 502 | Bad Gateway - Upstream error (VIES) |
| 503 | Service Unavailable |
| 504 | Gateway Timeout - VIES timeout |
Error Codes
INVALID_FORMAT
The VAT number format is invalid — either the country code is not a recognised EU member state, the number doesn't match the expected pattern, or VIES itself rejected the input.
{
"error": {
"code": "INVALID_FORMAT",
"message": "Invalid VAT ID format for DE",
"requestId": "req_abc123"
}
}INVALID_REQUESTER
Returned only when a requester is supplied — either via the account-level setting (dashboard Settings → Your EU VAT) or per-call query params (requesterCountryCode + requesterVatNumber) — and VIES rejected that requester VAT with INVALID_INPUT (deregistered, typo, or otherwise unknown to VIES).
{
"error": {
"code": "INVALID_REQUESTER",
"message": "Your requester VAT number is invalid in VIES. Fix or clear it in dashboard Settings (or pass a valid requesterCountryCode + requesterVatNumber). Clearing the requester disables consultation numbers but allows national fallback when VIES is down.",
"viesCode": "INVALID_INPUT",
"requestId": "req_abc123"
}
}How to handle:
- Open dashboard Settings → Your EU VAT and either correct the VAT or clear the field.
- Clearing the requester disables consultation numbers on every response but re-enables national-registry fallback when VIES is temporarily down.
- vatnode deliberately does not silently retry the call without the requester: a successful response without a consultation number would strip the audit evidence the caller opted into.
RATE_LIMITED
Only returned for the Free plan once the monthly quota of 100 requests is exhausted. Starter, Pro, and Enterprise plans are never rate-limited — requests past the included quota are billed at a pay-as-you-go rate and the service continues uninterrupted.
{
"error": {
"code": "RATE_LIMITED",
"message": "Monthly quota exceeded. Upgrade to Starter or Pro to continue.",
"requestId": "req_abc123"
}
}How to handle:
- Free plan: upgrade to Starter or Pro to continue past the monthly quota
- Cache validation results to avoid repeating the same checks
Note: API key holders are never subject to per-minute IP rate limiting — that applies only to unauthenticated requests from the web interface.
VIES_UNAVAILABLE
The VIES service is temporarily unavailable. Behaviour depends on whether a requester is set:
- No requester: vatnode first tries the national registry fallback for supported countries. This error is returned only when both VIES and the fallback fail, or when no fallback exists for the country.
- Requester set: national fallback is deliberately skipped (a result without a consultation number would strip the audit evidence you opted into), so the error is returned as soon as VIES fails. Retry when VIES recovers, or temporarily clear the requester in dashboard Settings to accept results without consultation numbers.
{
"error": {
"code": "VIES_UNAVAILABLE",
"message": "VIES service is temporarily unavailable",
"viesCode": "SERVICE_UNAVAILABLE",
"requestId": "req_abc123"
}
}VIES_ERROR
An error occurred while communicating with VIES.
{
"error": {
"code": "VIES_ERROR",
"message": "An error occurred while validating VAT",
"viesCode": "MS_UNAVAILABLE",
"requestId": "req_abc123"
}
}VIES-specific codes returned in the viesCode field:
| viesCode | Description |
|---|---|
| MS_UNAVAILABLE | Member state's national service is down or not responding |
| MS_MAX_CONCURRENT_REQ | Member state's service is busy; max concurrent requests exceeded |
| MS_MAX_CONCURRENT_REQ_TIME | Member state's service is busy; concurrent request timed out |
| GLOBAL_MAX_CONCURRENT_REQ | VIES global concurrent request limit exceeded |
| GLOBAL_MAX_CONCURRENT_REQ_TIME | VIES global concurrent request timeout |
| SERVICE_UNAVAILABLE | Central VIES service is unavailable |
| TIMEOUT | VIES request timed out |
| VAT_BLOCKED | The requested VAT number is blocked in VIES |
| IP_BLOCKED | The requesting IP address is blocked by VIES |
ALREADY_EXISTS
The resource you are trying to create already exists (e.g., duplicate API key label or webhook URL).
{
"error": {
"code": "ALREADY_EXISTS",
"message": "A resource with this identifier already exists.",
"requestId": "req_abc123"
}
}CREATION_FAILED
An internal error occurred while creating a resource. Retrying the request is safe.
{
"error": {
"code": "CREATION_FAILED",
"message": "Failed to create resource. Please try again.",
"requestId": "req_abc123"
}
}Best Practices
1. Always Check HTTP Status
const response = await fetch(url, options);
if (!response.ok) {
const error = await response.json();
switch (response.status) {
case 400:
console.error('Invalid request:', error.error.message);
break;
case 429:
const retryAfter = response.headers.get('Retry-After');
console.error('Rate limited, retry after:', retryAfter);
break;
case 503:
console.error('Service unavailable, retrying...');
break;
default:
console.error('Error:', error.error.message);
}
}2. Implement Retry Logic
async function fetchWithRetry(url, options, maxRetries = 3) {
for (let attempt = 1; attempt <= maxRetries; attempt++) {
try {
const response = await fetch(url, options);
if (response.ok) return response.json();
if (response.status === 429 || response.status >= 500) {
const retryAfter = response.headers.get('Retry-After') || Math.pow(2, attempt);
await new Promise(r => setTimeout(r, retryAfter * 1000));
continue;
}
// Client error, don't retry
const error = await response.json();
throw new Error(error.error.message);
} catch (error) {
if (attempt === maxRetries) throw error;
}
}
}3. Log Request IDs
Always log the requestId from error responses. This helps us investigate issues:
try {
const result = await validateVat('IE6388047V');
} catch (error) {
console.error('Validation failed:', {
message: error.message,
requestId: error.requestId, // Include this in support tickets
});
}4. Handle VIES Downtime Gracefully
VIES has scheduled maintenance and occasional outages. Consider showing a user-friendly message:
async function validateVatWithFallback(vatId) {
try {
return await validateVat(vatId);
} catch (error) {
if (error.code === 'VIES_UNAVAILABLE') {
return {
valid: null, // Unknown
vatId,
message: 'VAT validation temporarily unavailable. Will verify later.',
pendingVerification: true,
};
}
throw error;
}
}Support
If you encounter persistent errors:
- Check the vatnode status page for known issues
- Review your API key and rate limits in the Dashboard
- Contact support with your
requestIdfor investigation