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/nodeInitialize
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 numberValidate 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); // 23Audit 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 timestampRecords 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:
| Property | Type | Description |
|---|---|---|
code | ErrorCode | Machine-readable code (e.g. rate_limited) |
status_code | number | HTTP status |
request_id | string | null | From response for support traces |
response_body | unknown | Raw parsed response |
attempt_count | number | How 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.
| Behaviour | Default |
|---|---|
| Max retries | 2 (3 total attempts) |
| Backoff | Exponential with jitter, capped at 2s |
| Retried on | network errors, timeouts, 429, 502, 503, 504 |
| Never retried | 400, 401, 402, 404 (caller errors, retrying won't change the answer) |
Retry-After | Honored 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
| Option | Type | Default | Description |
|---|---|---|---|
api_key | string | — | Required. Also read from VATVERIFY_API_KEY env var |
base_url | string | https://api.vatverify.dev | Override for local dev or proxy |
timeout | number | 30000 | Per-request timeout in ms |
max_retries | number | 2 | Retry count for 429/502/503/504 |
fetch | typeof fetch | globalThis.fetch | Custom fetch implementation |
user_agent_extra | string | — | Appended to the User-Agent header |
on_response | function | — | Hook called after every response (useful for logging) |