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 Response
{
  "error": {
    "code": "ERROR_CODE",
    "message": "Human-readable error message",
    "requestId": "req_abc123",
    "details": {}
  }
}
FieldTypeDescription
codestringMachine-readable error code
messagestringHuman-readable description
requestIdstringUnique request ID for debugging
detailsobjectAdditional context (optional)

HTTP Status Codes

StatusMeaning
200Success
400Bad Request - Invalid input
401Unauthorized - Invalid or missing API key
403Forbidden - Insufficient permissions
404Not Found - Resource doesn't exist
422Unprocessable Entity — configured requester VAT is invalid in VIES
429Too Many Requests — Free plan monthly quota exhausted
500Internal Server Error
502Bad Gateway - Upstream error (VIES)
503Service Unavailable
504Gateway 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.

400 Bad Request
{
  "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).

422 Unprocessable Entity
{
  "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.

429 Too Many Requests
{
  "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.
503 Service Unavailable
{
  "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.

502 Bad Gateway
{
  "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:

viesCodeDescription
MS_UNAVAILABLEMember state's national service is down or not responding
MS_MAX_CONCURRENT_REQMember state's service is busy; max concurrent requests exceeded
MS_MAX_CONCURRENT_REQ_TIMEMember state's service is busy; concurrent request timed out
GLOBAL_MAX_CONCURRENT_REQVIES global concurrent request limit exceeded
GLOBAL_MAX_CONCURRENT_REQ_TIMEVIES global concurrent request timeout
SERVICE_UNAVAILABLECentral VIES service is unavailable
TIMEOUTVIES request timed out
VAT_BLOCKEDThe requested VAT number is blocked in VIES
IP_BLOCKEDThe 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).

409 Conflict
{
  "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.

500 Internal Server Error
{
  "error": {
    "code": "CREATION_FAILED",
    "message": "Failed to create resource. Please try again.",
    "requestId": "req_abc123"
  }
}

Best Practices

1. Always Check HTTP Status

JavaScript
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

JavaScript
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:

JavaScript
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:

JavaScript
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:

  1. Check the vatnode status page for known issues
  2. Review your API key and rate limits in the Dashboard
  3. Contact support with your requestId for investigation