ATverify

Node.js SDK

Official @vatverify/node SDK: typed client for the vatverify API. Validate VAT numbers, run the tax-rules engine, and fetch rates with full TypeScript support.

For offline format + checksum + rates lookup without an API key, use @vatverify/vat-rates instead, same author, MIT licensed, zero dependencies. Most production setups use both: the rates lib as a cheap pre-filter, the SDK for live registry verification.

Install

npm install @vatverify/node

Initialize

import { Vatverify } from '@vatverify/node';

// From a string, also reads VATVERIFY_API_KEY env var automatically
const vat = new Vatverify('vtv_live_xxx');

// Or via config object
const vat = new Vatverify({
  api_key: process.env.VATVERIFY_API_KEY!,
  timeout: 15_000,       // ms, default 30_000
  max_retries: 3,        // default 2 (retries 429/502/503/504)
});

vat.is_test_mode is true when the key starts with vtv_test_.

Validate a VAT number

const result = await vat.validate({ vat_number: 'IE6388047V' });

if (result.data.valid) {
  console.log(result.data.company?.name); // "Apple Distribution International Ltd"
}

Force a fresh registry call (bypass cache):

const result = await vat.validate({ vat_number: 'IE6388047V', cache: false });

Get a VIES consultation number for audit trails:

const result = await vat.validate({
  vat_number: 'IE6388047V',
  requester_vat_number: 'DE100000001',
});

console.log(result.data.verify_id); // VIES consultation number

Validate a batch

const result = await vat.validateBatch({
  vat_numbers: ['IE6388047V', 'DE811569869', 'FR44732829320'],
});

for (const item of result.data.results) {
  if (item.ok) {
    console.log(item.data.vat_number, item.data.company?.name);
  } else {
    console.log(item.error.code);
  }
}

With per-item verify_id (Pro plan, VIES countries only):

const result = await vat.validateBatch({
  vat_numbers: ['IE6388047V', 'DE811569869'],
  requester_vat_number: 'DE100000001',
});

Tax rules (/v1/decide)

Business plan only. Determines whether to charge VAT on a cross-border B2B transaction.

const decision = await vat.decide({
  seller_vat: 'DE811569869',
  buyer_vat: 'FR40303265045',
});

if (!decision.data.charge_vat) {
  console.log(decision.data.invoice_note);
  // "Reverse charge — VAT to be accounted for by the recipient"
}

VAT rates

// All countries
const rates = await vat.rates.list();

// Single country
const ie = await vat.rates.get('IE');
console.log(ie.data.standard_rate); // 23

Audit log

Retrieve a stored request record by its request_id (Pro and Business plans).

const record = await vat.audits.get('019d917e-4fa4-766e-9e0f-d977b47bf2c6');

console.log(record.data.endpoint);    // "validate"
console.log(record.data.response);    // full API response envelope
console.log(record.data.expires_at);  // ISO timestamp

Records are retained for 30 days (Pro) or 90 days (Business). Returns NotFoundError if the record has expired or belongs to a different key.

Webhooks

Manage webhook endpoints (Pro and Business plans). vatverify sends signed validation.completed and batch.completed events to your registered HTTPS endpoints after each request.

// Register an endpoint: secret shown once, store it securely
const endpoint = await vat.webhooks.create('https://example.com/webhooks/vatverify');
console.log(endpoint.secret); // "whsec_..."

// List registered endpoints (secrets not included)
const { data } = await vat.webhooks.list();

// Verify your endpoint is reachable
const result = await vat.webhooks.test('endpoint-id');
console.log(result.delivered, result.status); // true, 200

// Remove an endpoint
await vat.webhooks.delete('endpoint-id');

See the Webhooks guide for payload shapes and signature verification.

Error handling

The SDK throws typed error classes, never raw HTTP errors.

import {
  VatverifyError,
  AuthError,
  ValidationError,
  PlanError,
  WebhookLimitError,
  RateLimitError,
  RegistryError,
  TimeoutError,
} from '@vatverify/node';

try {
  const result = await vat.validate({ vat_number: 'IE6388047V' });
} catch (err) {
  if (err instanceof RateLimitError) {
    console.log(`Quota exceeded. Retry in ${err.retry_after}s`);
    console.log(err.rate_limit); // { limit, remaining, reset }
  } else if (err instanceof RegistryError) {
    // 502 (upstream VIES/HMRC/BFS/brreg down)
  } else if (err instanceof ValidationError) {
    // 400: invalid_format, country_unsupported, etc.
  } else if (err instanceof AuthError) {
    // 401: bad or missing API key
  } else if (err instanceof PlanError) {
    // 402: endpoint requires higher plan
  } else if (err instanceof WebhookLimitError) {
    // 400: webhook_limit_reached, delete an endpoint to free a slot
  } else if (err instanceof TimeoutError) {
    // request timed out (client-side)
  } else if (err instanceof VatverifyError) {
    console.log(err.code, err.status_code, err.request_id);
  }
}

All error classes share:

PropertyTypeDescription
codeErrorCodeMachine-readable code (e.g. rate_limited)
status_codenumberHTTP status
request_idstring | nullFrom response for support traces
response_bodyunknownRaw parsed response
attempt_countnumberHow many attempts were made

Retries

The SDK retries automatically on transient failures so you don't have to wrap calls in your own retry loop.

BehaviourDefault
Max retries2 (3 total attempts)
BackoffExponential with jitter, capped at 2s
Retried onnetwork errors, timeouts, 429, 502, 503, 504
Never retried400, 401, 402, 404 (caller errors, retrying won't change the answer)
Retry-AfterHonored on 429, capped at 30s

Disable retries globally or per request:

// globally
const vat = new Vatverify({ api_key: 'vtv_live_xxx', max_retries: 0 });

// per request; also pass a timeout or AbortSignal
const controller = new AbortController();
await vat.validate(
  { vat_number: 'IE6388047V' },
  { request_options: { max_retries: 0, timeout: 5000, signal: controller.signal } },
);

Every error exposes attempt_count so you can log how many tries it took.

TypeScript types

import type {
  ValidateRequest,
  ValidateResponse,
  ValidateData,
  ValidateBatchRequest,
  ValidateBatchResponse,
  BatchResultItem,
  DecideRequest,
  DecideResponse,
  RatesListResponse,
  RatesSingleResponse,
  WebhookEndpointPublic,
  WebhookEndpointWithSecret,
  WebhookListResponse,
  WebhookTestResponse,
  AuditRecord,
  AuditResponse,
  Meta,
  ErrorEnvelope,
} from '@vatverify/node';

Configuration reference

OptionTypeDefaultDescription
api_keystringRequired. Also read from VATVERIFY_API_KEY env var
base_urlstringhttps://api.vatverify.devOverride for local dev or proxy
timeoutnumber30000Per-request timeout in ms
max_retriesnumber2Retry count for 429/502/503/504
fetchtypeof fetchglobalThis.fetchCustom fetch implementation
user_agent_extrastringAppended to the User-Agent header
on_responsefunctionHook called after every response (useful for logging)