Guide · Python · REST API · Poland

CEIDG Validation in Python

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.

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
ℹ️The same NIP is used for both KRS companies and CEIDG sole proprietors. The NIP format and checksum are identical — only the registry lookup reveals which category the entity belongs to.

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.

Position12345678910
Weight657234567check
import re


def nip_valid(nip: str) -> bool:
    digits = [int(c) for c in re.sub(r"[\-\s]", "", nip)]
    if len(digits) != 10:
        return False
    weights = [6, 5, 7, 2, 3, 4, 5, 6, 7]
    total = sum(d * w for d, w in zip(digits, weights))
    check = total % 11
    return check != 10 and check == digits[9]
⚠️If total % 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):

PositionDigitWeightProduct
15630
22510
36742
4122
5030
64416
7050
88648
92714

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 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:

1

Format check

Exactly 10 digits, strips hyphens and spaces

2

MOD-11 checksum

Weights [6,5,7,2,3,4,5,6,7], mod 11 must equal the 10th digit

3

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. Python code example

Using the requests library or the standard urllib.request:

# ceidg_validator.py
import os
import requests

API_KEY = os.environ["ISVALID_API_KEY"]
BASE_URL = "https://api.isvalid.dev"


def validate_ceidg(nip: str, lookup: bool = False) -> dict:
    params = {"value": nip}
    if lookup:
        params["lookup"] = "true"
    response = requests.get(
        f"{BASE_URL}/v0/pl/ceidg",
        params=params,
        headers={"Authorization": f"Bearer {API_KEY}"},
        timeout=10,
    )
    response.raise_for_status()
    return response.json()


# ── NIP checksum validation only ────────────────────────────────────────────
result = validate_ceidg("9876543210")

if not result["valid"]:
    print("Invalid NIP")
else:
    print("NIP:", result["nip"])  # "987-654-32-10"

# ── With CEIDG registry lookup ───────────────────────────────────────────────
detailed = validate_ceidg("9876543210", lookup=True)

if detailed["valid"] and detailed.get("ceidg", {}).get("found"):
    c = detailed["ceidg"]
    print("Owner    :", c["firstName"], c["lastName"])
    print("Business :", c["businessName"])
    print("Status   :", c["status"])       # "active" | "suspended" | "deleted"
    print("City     :", c["city"])
    print("PKD      :", c["primaryPkd"])   # e.g. "70.22.Z"
    print("All PKD  :", c["pkd"])          # ["70.22.Z", "74.90.Z", ...]
The 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 }

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.

result = validate_ceidg(nip, lookup=True)

ceidg = result.get("ceidg") or {}
if result["valid"] and ceidg.get("found"):
    if ceidg["status"] != "active":
        # Business is suspended or deleted — do not onboard
        raise ValueError(f"CEIDG status: {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. Fall back to the VAT or KRS endpoints.

ceidg = validate_ceidg(nip, lookup=True)
ceidg_data = ceidg.get("ceidg") or {}

if ceidg["valid"] and ceidg_data.get("checked") and not ceidg_data.get("found"):
    # Not a sole proprietor — try VAT/VIES for companies
    vat = validate_vat(f"PL{nip}", check_vies=True)
    if vat["valid"] and (vat.get("vies") or {}).get("valid"):
        print("KRS company confirmed via VIES:", vat["vies"]["name"])

CEIDG API unavailable

When ceidg["checked"] is False, the CEIDG government API could not be reached. Handle this gracefully — accept provisionally and re-check.

result = validate_ceidg(nip, lookup=True)
ceidg = result.get("ceidg") or {}

if result["valid"] and ceidg and not ceidg.get("checked"):
    # CEIDG API temporarily unavailable
    schedule_recheck(nip, delay="1h")
    return {"status": "provisional", "reason": "ceidg_unavailable"}

Verify required PKD codes

For regulated industries, check that the entity holds the required PKD activity code before contracting.

REQUIRED_PKD = {"64", "65", "66"}  # financial services divisions

result = validate_ceidg(nip, lookup=True)
ceidg = result.get("ceidg") or {}

if ceidg.get("found"):
    pkd_divisions = {code[:2] for code in ceidg.get("pkd", [])}
    if not REQUIRED_PKD & pkd_divisions:
        raise ValueError(
            f"Entity does not hold required financial services PKD codes. "
            f"Has: {pkd_divisions}"
        )

Summary

Validate the NIP MOD-11 checksum before any registry call — it catches typos instantly
Use lookup=True to confirm the entity is active — a valid NIP checksum does not prove the business exists
Check ceidg["status"] — suspended and deleted entries still return found: True
Check primaryPkd for regulated industries — verify the entity holds the required activity codes
Do not assume "not found in CEIDG" means invalid — KRS companies use a different registry
Do not block on checked: False — accept provisionally and re-check when the API recovers

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.