CEIDG Validation in Node.js
The Polish registry for sole proprietors and civil partnerships — validated by NIP checksum, confirmed by a live CEIDG lookup returning owner name, address, status, and PKD activity codes.
In this guide
1. What is CEIDG?
CEIDG (Centralna Ewidencja i Informacja o Działalności Gospodarczej — Central Registration and Information on Business Activity) is the official Polish government registry for:
- Sole proprietors (JDG — jednoosobowa działalność gospodarcza)
- Civil partnerships (spółki cywilne) — each partner registers separately
CEIDG does not cover companies incorporated under commercial law (sp. z o.o., S.A., sp. k., etc.) — those are registered in the KRS (National Court Register). If you receive a NIP and do not know the entity type, start with CEIDG; if not found, fall back to KRS.
CEIDG is maintained by the Ministry of Economic Development and is publicly accessible. It stores:
- Owner's first and last name
- Business name (if different from the owner's name)
- Registration status (active / suspended / deleted)
- Registered business address
- PKD activity codes (Polish Classification of Activities, based on NACE Rev. 2)
- REGON statistical number and NIP tax number
- Business commencement date
2. NIP — the key identifier
CEIDG entries are identified by NIP (Numer Identyfikacji Podatkowej — Tax Identification Number), the Polish equivalent of a VAT/tax ID. A NIP is:
- Exactly 10 digits, no letters
- Written with hyphens as
NNN-NNN-NN-NN(e.g.526-104-08-28) - Protected by a MOD-11 weighted checksum on the last digit
| NIP | Entity |
|---|---|
| 526-104-08-28 | ORACLE POLSKA SP. Z O.O. (KRS company) |
| 987-654-32-10 | Sole proprietor (CEIDG entry) |
3. The NIP MOD-11 checksum algorithm
The NIP checksum uses a weighted MOD-11 algorithm. The first 9 digits are each multiplied by a fixed weight, the products are summed, and the result modulo 11 must equal the 10th (check) digit. If the remainder is 10, the number is always invalid.
| Position | 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | 9 | 10 |
|---|---|---|---|---|---|---|---|---|---|---|
| Weight | 6 | 5 | 7 | 2 | 3 | 4 | 5 | 6 | 7 | check |
function nipValid(nip) { const digits = nip.replace(/[\-\s]/g, '').split('').map(Number); if (digits.length !== 10) return false; const weights = [6, 5, 7, 2, 3, 4, 5, 6, 7]; const sum = weights.reduce((acc, w, i) => acc + digits[i] * w, 0); const check = sum % 11; return check !== 10 && check === digits[9]; }
sum % 11 === 10, the NIP is always structurally invalid — no valid check digit exists for that combination. This is by design in the Polish NIP specification.4. Worked example — step by step
Let's verify NIP 526-104-08-28 (digits: 5261040828):
| Position | Digit | Weight | Product |
|---|---|---|---|
| 1 | 5 | 6 | 30 |
| 2 | 2 | 5 | 10 |
| 3 | 6 | 7 | 42 |
| 4 | 1 | 2 | 2 |
| 5 | 0 | 3 | 0 |
| 6 | 4 | 4 | 16 |
| 7 | 0 | 5 | 0 |
| 8 | 8 | 6 | 48 |
| 9 | 2 | 7 | 14 |
Sum = 30 + 10 + 42 + 2 + 0 + 16 + 0 + 48 + 14 = 162
162 mod 11 = 162 − 14 × 11 = 162 − 154 = 8
The remainder is 8 (not 10), so the check digit must be 8.
The 10th digit of 5261040828 is 8 — it matches. The NIP is valid.
5. Why format checks alone are not enough
A valid NIP checksum does not mean the business is active
Sole proprietors can suspend or close their business. The NIP remains valid and the MOD-11 checksum still passes — but the CEIDG status will be suspended or deleted. For any onboarding or compliance use case, you need the live registry status.
You need the owner's name and address for KYC
The NIP alone identifies a tax entity but tells you nothing about the person behind it. A CEIDG lookup returns the owner's first and last name, business address, and registration date — all required for customer due diligence (CDD) and Know Your Customer (KYC) flows.
PKD codes define what the business is allowed to do
Polish sole proprietors register the specific PKD (Polska Klasyfikacja Działalności) activity codes under which they operate. For regulated industries — financial services, healthcare, transport — you may need to verify that the supplier or partner holds the correct PKD codes before contracting with them.
6. The right solution: one API call
The IsValid CEIDG API handles the full validation and lookup stack in a single GET request:
Format check
Exactly 10 digits, strips hyphens and spaces
MOD-11 checksum
Weights [6,5,7,2,3,4,5,6,7], mod 11 must equal the 10th digit
CEIDG registry lookup (optional)
Owner name, business name, address, status, REGON, start date, and PKD codes from the official government API
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: CEIDG API docs →
7. Node.js code example
Using the @isvalid-dev/sdk package or the built-in fetch API (Node.js 18+):
// ceidg-validator.mjs import { createClient } from '@isvalid-dev/sdk'; const iv = createClient({ apiKey: process.env.ISVALID_API_KEY }); // ── NIP checksum validation only ──────────────────────────────────────────── const result = await iv.pl.ceidg('9876543210'); if (!result.valid) { console.log('Invalid NIP'); } else { console.log('NIP:', result.nip); // "987-654-32-10" } // ── With CEIDG registry lookup ─────────────────────────────────────────────── const detailed = await iv.pl.ceidg('9876543210', { lookup: true }); if (detailed.valid && detailed.ceidg?.found) { const c = detailed.ceidg; console.log('Owner :', c.firstName, c.lastName); console.log('Business :', c.businessName); console.log('Status :', c.status); // "active" | "suspended" | "deleted" console.log('City :', c.city); console.log('PKD :', c.primaryPkd); // e.g. "70.22.Z" console.log('All PKD :', c.pkd); // ["70.22.Z", "74.90.Z", ...] }
lookup parameter is optional. Omit it for fast checksum-only validation (e.g. validating a form field). Add it when you need to confirm the entity is active and retrieve owner details.8. cURL examples
NIP checksum validation only:
curl -H "Authorization: Bearer YOUR_API_KEY" \ "https://api.isvalid.dev/v0/pl/ceidg?value=9876543210"
With CEIDG registry lookup:
curl -H "Authorization: Bearer YOUR_API_KEY" \ "https://api.isvalid.dev/v0/pl/ceidg?value=9876543210&lookup=true"
Hyphens are accepted and stripped automatically:
curl -H "Authorization: Bearer YOUR_API_KEY" \ "https://api.isvalid.dev/v0/pl/ceidg?value=987-654-32-10"
9. Understanding the response
Valid NIP (checksum only)
{ "valid": true, "nip": "987-654-32-10" }
Valid NIP with CEIDG lookup
{ "valid": true, "nip": "987-654-32-10", "ceidg": { "checked": true, "found": true, "status": "active", "firstName": "JAN", "lastName": "KOWALSKI", "businessName": "JK CONSULTING JAN KOWALSKI", "regon": "146123456", "city": "Warszawa", "postalCode": "00-001", "street": "Marszałkowska", "houseNumber": "1", "flatNumber": null, "startDate": "2015-03-01", "pkd": ["70.22.Z", "74.90.Z", "63.11.Z"], "primaryPkd": "70.22.Z" } }
NIP not found in CEIDG
{ "valid": true, "nip": "526-104-08-28", "ceidg": { "checked": true, "found": false } }
Invalid NIP
{ "valid": false }
| Field | Type | Description |
|---|---|---|
| valid | boolean | NIP passes format and MOD-11 checksum |
| nip | string | NIP in canonical NNN-NNN-NN-NN format. Present only when valid: true |
| ceidg.checked | boolean | Whether the CEIDG API was successfully queried |
| ceidg.found | boolean | Whether the entity was found in CEIDG |
| ceidg.status | string | active, suspended, or deleted |
| ceidg.firstName / lastName | string | null | Owner's full name |
| ceidg.businessName | string | null | Registered business name (may differ from owner name) |
| ceidg.pkd | string[] | All registered PKD activity codes |
| ceidg.primaryPkd | string | null | Primary (predominant) PKD activity code |
10. Edge cases to handle
Entity found but status is suspended or deleted
The NIP is valid and the entity exists in CEIDG, but the business is no longer active. Always check ceidg.status before onboarding.
const result = await validateCeidg(nip, { lookup: true }); if (result.valid && result.ceidg?.found) { if (result.ceidg.status !== 'active') { // Business is suspended or deleted — do not onboard return { status: 'rejected', reason: `CEIDG status: ${result.ceidg.status}`, }; } // Proceed with onboarding }
NIP not found in CEIDG — try KRS
A valid NIP that is not in CEIDG likely belongs to a KRS company (sp. z o.o., S.A., etc.) or a non-business entity (e.g. a state institution). Fall back to the VAT or KRS endpoints.
const ceidg = await validateCeidg(nip, { lookup: true }); if (ceidg.valid && ceidg.ceidg?.checked && !ceidg.ceidg.found) { // Not a sole proprietor — try VAT/VIES for companies const vat = await iv.vat(`PL${nip}`, { checkVies: true }); if (vat.valid && vat.vies?.valid) { console.log('KRS company confirmed via VIES:', vat.vies.name); } }
CEIDG API unavailable
When ceidg.checked is false, the CEIDG government API could not be reached. The NIP passed checksum validation, but no registry data is available. Handle this gracefully — accept provisionally and re-check.
const result = await validateCeidg(nip, { lookup: true }); if (result.valid && result.ceidg && !result.ceidg.checked) { // CEIDG API temporarily unavailable await scheduleRecheck(nip, '1h'); return { status: 'provisional', reason: 'ceidg_unavailable' }; }
NIP with hyphens or spaces
Users and systems often enter NIPs in various formats. The API strips hyphens and spaces before validation, so all these forms are equivalent:
// All of these are accepted and produce the same result: await validateCeidg('9876543210'); await validateCeidg('987-654-32-10'); await validateCeidg('987 654 32 10');
Summary
See also
Try CEIDG validation instantly
Free tier includes 100 API calls per day. No credit card required. NIP checksum validation and optional CEIDG registry lookup included.