Guide · Node.js · REST API

REGON Validation in Node.js

The Polish statistical business registry number — two lengths, two different weight sets, and an optional GUS BIR lookup. Here's how to validate it correctly.

1. What is REGON?

REGON (Rejestr Gospodarki Narodowej) is the Polish National Business Registry Number assigned by GUS (Glowny Urzad Statystyczny — Central Statistical Office). Every legal entity and natural person conducting business activity in Poland receives a REGON number. It is used in:

  • Tax filings and invoices
  • Public procurement
  • National Court Register (KRS) entries
  • Social security (ZUS) registration

Two types exist:

  • 9-digit REGON identifies the entire entity (company, NGO, sole proprietor)
  • 14-digit REGON identifies a local unit (branch, office, factory) of an entity
EntityREGONType
Zaklad Ubezpieczen Spolecznych0173443909-digit (entity)
ZUS Oddzial w Warszawie0173443900031714-digit (local unit)

2. Structure — 9-digit vs 14-digit

Both forms are purely numeric strings. Their internal structure determines how the check digit is computed:

FormPositionsDescription
9-digit1–8Base number
9Check digit (computed over digits 1–8)
14-digit1–9Full 9-digit REGON of the parent entity
10–13Local unit number
14Check digit (computed over digits 1–13)
ℹ️A 14-digit REGON always starts with its parent entity's 9-digit REGON. If the embedded 9-digit prefix fails its own checksum, the entire 14-digit number is invalid.

3. The MOD-11 checksum algorithm

REGON uses a weighted MOD-11 checksum. Each digit is multiplied by a fixed weight, the products are summed, and the remainder after dividing by 11 produces the check digit. There are two separate weight sets — one for the 9-digit form and another for the 14-digit form.

9-digit REGON

  1. Multiply each of the first 8 digits by its weight: [8, 9, 2, 3, 4, 5, 6, 7]
  2. Sum all products
  3. Calculate sum mod 11
  4. If the remainder is 10, the check digit is 0; otherwise the check digit equals the remainder
  5. Compare the result with the 9th digit

14-digit REGON

  1. First, validate the embedded 9-digit prefix using the 9-digit algorithm above
  2. Then multiply each of the first 13 digits by weights: [2, 4, 8, 5, 0, 9, 7, 3, 6, 1, 2, 4, 8]
  3. Sum all products, compute sum mod 11, same rule: if remainder is 10, check digit is 0
  4. Compare the result with the 14th digit

Here is a straightforward JavaScript implementation of both variants:

function validateRegon9(regon) {
  const d = regon.split('').map(Number);
  const weights = [8, 9, 2, 3, 4, 5, 6, 7];
  const sum = weights.reduce((acc, w, i) => acc + d[i] * w, 0);
  const check = sum % 11 === 10 ? 0 : sum % 11;
  return check === d[8];
}

function validateRegon14(regon) {
  if (!validateRegon9(regon.slice(0, 9))) return false;
  const d = regon.split('').map(Number);
  const weights = [2, 4, 8, 5, 0, 9, 7, 3, 6, 1, 2, 4, 8];
  const sum = weights.reduce((acc, w, i) => acc + d[i] * w, 0);
  const check = sum % 11 === 10 ? 0 : sum % 11;
  return check === d[13];
}
⚠️A REGON that passes the checksum is not necessarily active. Companies get dissolved, sole proprietors close their business — the number remains structurally valid but refers to a non-existent entity. Use the ?lookup=true parameter to check the GUS BIR registry.

4. Worked example — step by step

Let's walk through the 9-digit REGON 123456785 to see the MOD-11 algorithm in action:

PositionDigitWeightProduct
1188
22918
3326
44312
55420
66530
77642
88756

Sum = 8 + 18 + 6 + 12 + 20 + 30 + 42 + 56 = 192

192 mod 11 = 192 − 17 × 11 = 192 − 187 = 5

The remainder is 5, which is not 10, so the check digit is 5.

The 9th digit of 123456785 is 5 — it matches. The REGON is structurally valid.


5. Why format checks alone are not enough

A valid checksum does not mean the entity exists

Companies close, merge, or are deleted from the registry. The number remains mathematically valid — the MOD-11 checksum will still pass — but the entity behind it is gone. Relying solely on checksum validation gives you a false sense of confidence.

GUS BIR has the authoritative data

The Central Statistical Office maintains the official REGON registry through its BIR (Baza Internetowa REGON) system. Only a BIR lookup can confirm the entity's name, NIP, address, and whether business activity is still ongoing. The checksum tells you nothing about any of these.

14-digit REGONs add a second layer of complexity

A 14-digit REGON can have a valid checksum at both levels — the 9-digit prefix passes its own MOD-11 check, and the full 14-digit number passes its separate MOD-11 check — and still refer to a closed local unit. Double validation (structure + registry) is needed to be sure.


6. The right solution: one API call

The IsValid REGON API handles the full validation stack in a single GET request:

1

Format check

9 or 14 digits, all numeric

2

MOD-11 checksum

Correct weight set for 9-digit and 14-digit forms

3

GUS BIR lookup (optional)

Name, NIP, address, and activity status from the official registry

Get your free API key at isvalid.dev. The free tier includes 100 calls per day with no credit card required.

Full parameter reference and response schema: REGON Validation API docs →


7. Node.js code example

Using the built-in fetch API available in Node.js 18+:

// regon-validator.mjs
const API_KEY = process.env.ISVALID_API_KEY;
const BASE_URL = 'https://api.isvalid.dev';

async function validateRegon(regon, { lookup = false } = {}) {
  const url = new URL('/v0/pl/regon', BASE_URL);
  url.searchParams.set('value', regon);
  if (lookup) url.searchParams.set('lookup', 'true');

  const response = await fetch(url, {
    headers: { Authorization: `Bearer ${API_KEY}` },
  });

  if (!response.ok) {
    throw new Error(`API error: ${response.status}`);
  }
  return response.json();
}

// ── Basic validation ────────────────────────────────────────────────────────
const result = await validateRegon('123456785');

if (!result.valid) {
  console.log('Invalid REGON');
} else {
  console.log('Type:', result.type); // "entity" or "local-unit"
}

// ── With GUS BIR lookup ─────────────────────────────────────────────────────
const detailed = await validateRegon('123456785', { lookup: true });

if (detailed.valid && detailed.regon?.found) {
  console.log('Name     :', detailed.regon.name);
  console.log('NIP      :', detailed.regon.nip);
  console.log('City     :', detailed.regon.city);
  console.log('Street   :', detailed.regon.street);
}
The lookup parameter is optional. Use it when you need the entity's name, NIP, or address. Omit it for fast checksum-only validation.

8. cURL examples

Basic 9-digit validation:

curl -H "Authorization: Bearer YOUR_API_KEY" \
  "https://api.isvalid.dev/v0/pl/regon?value=123456785"

With GUS BIR lookup:

curl -H "Authorization: Bearer YOUR_API_KEY" \
  "https://api.isvalid.dev/v0/pl/regon?value=123456785&lookup=true"

14-digit local unit:

curl -H "Authorization: Bearer YOUR_API_KEY" \
  "https://api.isvalid.dev/v0/pl/regon?value=12345678512347"

9. Understanding the response

Valid REGON (basic)

{
  "valid": true,
  "type": "entity"
}

Valid REGON (with lookup)

{
  "valid": true,
  "type": "entity",
  "regon": {
    "checked": true,
    "found": true,
    "name": "EXAMPLE COMPANY SP. Z O.O.",
    "nip": "1234567890",
    "voivodeship": "MAZOWIECKIE",
    "district": "m. st. Warszawa",
    "community": "Srodmiescie",
    "city": "Warszawa",
    "postalCode": "00-001",
    "street": "ul. Przykladowa",
    "houseNumber": "10",
    "flatNumber": "5",
    "activityEndDate": null
  }
}

Invalid REGON

{ "valid": false }
FieldTypeDescription
validbooleanWhether the REGON passed format and checksum validation
typestring"entity" (9-digit) or "local-unit" (14-digit). Only present when valid: true
regonobjectGUS BIR lookup result. Only present when lookup=true and valid: true
regon.checkedbooleanWhether the GUS BIR API was successfully queried
regon.foundbooleanWhether the entity was found in the registry. Only present when checked: true
regon.namestringFull legal name of the entity
regon.nipstringNIP (tax identification number)
regon.voivodeshipstringVoivodeship (province)
regon.citystringCity
regon.postalCodestringPostal code
regon.streetstringStreet name
regon.houseNumberstringBuilding number
regon.flatNumberstring | nullApartment/unit number
regon.activityEndDatestring | nullDate when business activity ended (null if still active)

10. Edge cases to handle

GUS BIR unavailable

The GUS BIR SOAP API has occasional downtimes. When lookup=true but the API is unreachable, the response includes regon.checked: false with reason: "unavailable". Your code should handle this gracefully.

const result = await validateRegon('123456785', { lookup: true });

if (result.valid && result.regon) {
  if (!result.regon.checked) {
    // GUS BIR was unreachable — checksum passed but no registry data
    console.warn('GUS BIR unavailable, retry later');
  } else if (result.regon.found) {
    console.log('Entity:', result.regon.name);
  } else {
    console.log('Not found in the registry');
  }
}

Dissolved entities

A REGON that passes checksum validation may belong to a dissolved company. The activityEndDate field in the lookup response tells you when the entity stopped operating. Always check this field for KYC/onboarding flows.

const result = await validateRegon('123456785', { lookup: true });

if (result.valid && result.regon?.found) {
  if (result.regon.activityEndDate) {
    console.log('Entity dissolved on:', result.regon.activityEndDate);
    // Reject for onboarding — this company is no longer active
  } else {
    console.log('Entity is active');
  }
}

14-digit with valid prefix but invalid suffix

A 14-digit REGON where the first 9 digits pass the 9-digit checksum but the full 14-digit checksum fails. The API returns { valid: false } in this case — both checksums must pass for the number to be considered valid.

// 9-digit prefix is valid, but full 14-digit checksum fails
const result = await validateRegon('12345678500000');
console.log(result.valid); // false

Leading zeros

REGONs can start with 0 (e.g. 017344390). Do not store them as integers — use strings to preserve leading zeros. Casting to a number would turn 017344390 into 17344390, which is only 8 digits and will fail validation.

// WRONG — loses the leading zero
const regon = Number('017344390'); // 17344390 — 8 digits, invalid

// CORRECT — preserve as string
const regon = '017344390'; // 9 digits, ready for validation

Summary

Do not store REGON as an integer — leading zeros will be lost
Do not assume a valid checksum means the entity is active — use the lookup parameter
Do not skip the 9-digit prefix validation for 14-digit REGONs — both checksums must pass
Validate the MOD-11 checksum — catches transposition and transcription errors
Use lookup=true for KYC flows — returns name, NIP, and full address from GUS BIR
Handle GUS BIR unavailability — check regon.checked before reading regon.found

See also

Try REGON validation instantly

Free tier includes 100 API calls per day. No credit card required. Checksum validation and optional GUS BIR lookup included.