---
title: "VAT, tax ID and national ID validator: 23 countries, checksum only (VAT formats BE, DE, FR, IT, PL, AT, CH, IE)"
method: GET
path: "/v1/id/national"
tags: ["deterministic", "no-api-key", "id", "identifiers", "validation", "checksum"]
---

# VAT, tax ID and national ID validator: 23 countries, checksum only (VAT formats BE, DE, FR, IT, PL, AT, CH, IE)

`GET /v1/id/national`

## Request

- Base URL: `https://api.duoleads.com`
- URL: `https://api.duoleads.com/v1/id/national`
- Auth: none declared

VAT, tax ID and national ID validator: check a national identity, tax or company registration number against its country's official checksum rule. Covers 23 countries including NL BSN, ES NIF/NIE/CIF, IT codice fiscale, FR SIREN/SIRET, DE Steuer-ID, BE/PL/CZ national numbers, SE/NO/DK/FI personal numbers, BR CPF/CNPJ, AU ABN/TFN, UK NINo, IE PPS, US NPI/EIN. Structure and check digit only, no registry lookup; the number is never stored (logs keep only its length and character types).

**Output (`data`):** valid (boolean), country, kind, normalised (string|null), formatted (string|null), reason (null | length | charset | checksum | structure | reserved_range: a number in a range the issuer reserves or never assigned, e.g. CA SIN starting 0 or 8, UK NINO prefixes BG GB KN NK NT TN ZZ, US EIN unassigned prefixes, NZ IRD outside 10,000,000-150,000,000), algorithm (e.g. elfproef-11, mod-97-10, luhn), derived (object of facts the number encodes, {} when none: birth_date (YYYY-MM-DD) and sex (M|F) for IT cf, SE personnummer, PL pesel, BE rrn, NO fodselsnummer, DK cpr, FI hetu, CZ rc and SK rc; birthplace_code for IT cf; century_assumed (boolean) for SE personnummer, true when a 10-digit form forced the century to be inferred; for BE, NO, DK, FI, CZ and SK the number itself fixes the century (BE by the check digits, NO by the individual number, DK by the sequence digit, FI by the century sign, CZ/SK by the digit count). birth_date is omitted when the encoded date is not a real date, and for NO FH-numbers). FR nir carries no day and no century, so its derived holds sex, birth_year_yy (2 digits), birth_month (1-12, or null for special codes) and department_code instead of birth_date. normalised is the number without separators (SE personnummer keeps a 12-digit input's century and a + sign).

Deterministic: identical input returns identical output, so the response is served with `Cache-Control: private, max-age=31536000, immutable` (the paying client may cache it indefinitely; shared caches must not, because the response is paid).

Price: $0.05 USDC per call (x402, Base). Route contract version 1.0.0. Docs: https://api.duoleads.com/docs#id-national

## Query parameters

- `country` string, required
- `kind` string, required
- `value` string, required

## Response `200`

Paid and settled. `data` is route-specific; a route that judges its input returns 200 with `data.valid: false` for a bad value.

- object
  - `ok` unknown, required
  - `route` '/v1/id/national', required
  - `version` '1.0.0', required
  - `data` object, required: valid (boolean), country, kind, normalised (string|null), formatted (string|null), reason (null | length | charset | checksum | structure | reserved_range: a number in a range the issuer reserves or never assigned, e.g. CA SIN starting 0 or 8, UK NINO prefixes BG GB KN NK NT TN ZZ, US EIN unassigned prefixes, NZ IRD outside 10,000,000-150,000,000), algorithm (e.g. elfproef-11, mod-97-10, luhn), derived (object of facts the number encodes, {} when none: birth_date (YYYY-MM-DD) and sex (M|F) for IT cf, SE personnummer, PL pesel, BE rrn, NO fodselsnummer, DK cpr, FI hetu, CZ rc and SK rc; birthplace_code for IT cf; century_assumed (boolean) for SE personnummer, true when a 10-digit form forced the century to be inferred; for BE, NO, DK, FI, CZ and SK the number itself fixes the century (BE by the check digits, NO by the individual number, DK by the sequence digit, FI by the century sign, CZ/SK by the digit count). birth_date is omitted when the encoded date is not a real date, and for NO FH-numbers). FR nir carries no day and no century, so its derived holds sex, birth_year_yy (2 digits), birth_month (1-12, or null for special codes) and department_code instead of birth_date. normalised is the number without separators (SE personnummer keeps a 12-digit input's century and a + sign).
    - `valid` boolean, required
    - `country` string, required
    - `kind` string, required
    - `algorithm` string, required
    - `normalised` string, nullable, required
    - `formatted` string, nullable, required
    - `reason` string, nullable, required
    - `derived` object, required
      - `birth_date` string
      - `sex` string
      - `birthplace_code` string
      - `century_assumed` boolean
      - `birth_year_yy` string
      - `birth_month` number, nullable
      - `department_code` string
  - `meta` Meta, required
    - `disclaimer` string, required: Legal short disclaimer, identical on every response.
    - `request_id` string, required: ULID.
    - `computed_at` string, date-time, required: RFC 3339 UTC with milliseconds.
    - `price_usd` string, required: Decimal string, "0.05".
    - `deterministic` boolean, required
    - `sources` object[]: Present only on routes that read a vendored dataset: the exact edition that answered.
      - `id` string, required
      - `version` string, required
      - `published` string, required
      - `attribution` string
      - `licence` string

## Other responses

- `400`: MISSING_PARAM, INVALID_PARAM or UNSUPPORTED_VALUE. Not charged: validation runs before the paywall.
- `402`: Payment required. No valid PAYMENT-SIGNATURE was supplied: read the base64 PAYMENT-REQUIRED header (or this body) for the price ($0.05 = 50000 atomic USDC on eip155:8453), sign an EIP-3009 authorization and retry. Also returned when settlement is declined (PAYMENT_FAILED). Nothing is charged on a 402.
- `404`: NOT_FOUND: unknown path. Not charged.
- `405`: METHOD_NOT_ALLOWED: only GET and HEAD are served. Not charged.
- `413`: INPUT_TOO_LARGE: a parameter exceeded its documented maximum length. Not charged.
- `500`: INTERNAL: a server bug. Not charged.

---

[API](https://skmtc.dev/duoleads/apis/duo-data-utilities-iban-isin-vat-tax-id-barcode-timezone-geo.md) · [All operations](https://skmtc.dev/duoleads/apis/duo-data-utilities-iban-isin-vat-tax-id-barcode-timezone-geo/llms.txt) · [OpenAPI document](https://skmtc.dev/duoleads/apis/duo-data-utilities-iban-isin-vat-tax-id-barcode-timezone-geo/revisions/18790d59615e?raw)
