vatverify home
All guides

How to validate VAT numbers in Node.js

Validate EU, UK, Swiss, and Norwegian VAT numbers in Node.js with real checksum algorithms and a free live API.

TL;DR

  • Install @vatverify/node, paste your API key, call vat.validate().
  • Free up to 500 requests per month.
  • Handles format + checksum + live registry lookup in one call.
SDK status

@vatverify/node is packaged and ships on npm at the API launch. Reading this before launch? The REST endpoint is live today. Swap any vat.validate({ vat_number }) call for fetch('https://api.vatverify.dev/v1/validate?vat_number=...') with an Authorization: Bearer header. The SDK is a typed wrapper around the same thing.

Why not just use a regex?

Regex can tell you the shape of a VAT number: the country prefix, the digit count, whether there's a U where a U belongs. It cannot tell you whether the mandatory check digit is mathematically correct, and it cannot tell you whether the number is currently registered in VIES or HMRC.

Every EU country bakes a checksum algorithm into its VAT number format: Germany uses a MOD-11 variant, France combines a MOD-97 key with the SIREN-derived body, the Netherlands uses a weighted mod-11, the UK uses HMRC's 97-55 algorithm, Norway and Switzerland use their own MOD-11 schemes. A regex will happily accept DE000000000: nine digits after DE. The checksum says it's invalid. The registry says nobody holds it.

The inverse is also common: a perfectly well-formed and mathematically valid VAT number can still be unregistered (deregistered business, typo for a real number) or have been assigned to a different entity than what your customer claims. Only a live lookup settles that, which is exactly what the live validator does.

Installation

npm install @vatverify/node

Basic validation

validate.ts
import { Vatverify } from '@vatverify/node';

const vat = new Vatverify(process.env.VATVERIFY_API_KEY!);

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

if (result.data.valid) {
  console.log(`Valid: ${result.data.company?.name}`);
}

Handling errors

validate-with-errors.ts
import { Vatverify, VatverifyError } from '@vatverify/node';

const vat = new Vatverify(process.env.VATVERIFY_API_KEY!);

try {
  const result = await vat.validate({ vat_number: 'DE123456789' });
  if (!result.data.valid) {
    console.log(`Invalid: ${result.data.vat_number} is not registered.`);
  }
} catch (err) {
  if (err instanceof VatverifyError) {
    if (err.code === 'rate_limited') {
      // back off based on err.retry_after (seconds until you can retry)
    }
  }
  throw err;
}

Offline-only validation (no API call)

If you only need format + checksum (no live registry lookup), use @vatverify/vat-rates, a zero-dependency offline library. It runs the same real checksum algorithms the live API uses before it hits VIES, so you can reject obviously broken VAT numbers at the form level without spending an API call. Under 50 kB minified + gzipped, works in Node, the browser, Vercel Edge, and Cloudflare Workers:

npm install @vatverify/vat-rates
import { validate } from '@vatverify/vat-rates';

const result = validate('IE6388047V');
// { valid: true }

This runs instantly, no network, no API key.

Next.js server action

For App Router projects the cleanest wiring is a server action. The 'use server' directive ensures the code never ships to the browser, so your API key stays on the server without any extra configuration. Server actions can be called directly from a form's action prop (<form action={validateVat}>) or via useActionState for pending + error state. Full App Router patterns (forms, useActionState, optimistic UI) live in the Next.js guide.

app/actions/validate-vat.ts
'use server';

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

const vat = new Vatverify(process.env.VATVERIFY_API_KEY!);

export async function validateVat(vatNumber: string) {
  const result = await vat.validate({ vat_number: vatNumber });
  return result;
}

Testing with test-mode keys

Use vtv_test_* keys for deterministic responses. See Test mode.

tests/vat.test.ts
import { Vatverify } from '@vatverify/node';

const vat = new Vatverify('vtv_test_xxx');

test('valid Irish VAT returns company name', async () => {
  const result = await vat.validate({ vat_number: 'IE6388047V' });
  expect(result.data.valid).toBe(true);
});

FAQ

Does this work with TypeScript?

Yes, full type definitions ship with @vatverify/node. Types auto-complete in any TypeScript 5+ project.

Can I validate without an API key?

Use @vatverify/vat-rates for offline format + checksum validation. The live registry check requires an API key.

Does Node.js 18 work?

Yes, Node 18 LTS and newer are supported. For best performance, use Node 20 LTS or 22 LTS.

How do I handle VIES downtime?

See handle-vies-downtime. We serve the most recent cached response with meta.source_status: "degraded" when the upstream registry is unavailable.

What happens on an invalid VAT number?

result.data.valid is false when the registry responds but the number is not registered. Obvious format and checksum problems return a VatverifyError with code: "invalid_format" instead; catch it alongside the other SDK errors.

Validate VAT in three lines.

Free up to 500 requests per month. No credit card.

Start free