Guide · Python · Validation

REGON Validation in Python

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 in Python.

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 Python implementation of both variants using sum() and zip() for the weighted sum:

def validate_regon_9(regon: str) -> bool:
    digits = [int(c) for c in regon]
    weights = [8, 9, 2, 3, 4, 5, 6, 7]
    weighted_sum = sum(d * w for d, w in zip(digits, weights))
    check = 0 if weighted_sum % 11 == 10 else weighted_sum % 11
    return check == digits[8]


def validate_regon_14(regon: str) -> bool:
    if not validate_regon_9(regon[:9]):
        return False
    digits = [int(c) for c in regon]
    weights = [2, 4, 8, 5, 0, 9, 7, 3, 6, 1, 2, 4, 8]
    weighted_sum = sum(d * w for d, w in zip(digits, weights))
    check = 0 if weighted_sum % 11 == 10 else weighted_sum % 11
    return check == digits[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.

Here is the same calculation expressed in Python:

regon = "123456785"
digits = [int(c) for c in regon]
weights = [8, 9, 2, 3, 4, 5, 6, 7]

weighted_sum = sum(d * w for d, w in zip(digits, weights))
print(f"Sum: {weighted_sum}")  # Sum: 192

remainder = weighted_sum % 11
check = 0 if remainder == 10 else remainder
print(f"Check digit: {check}")  # Check digit: 5
print(f"Valid: {check == digits[8]}")  # Valid: True

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

Using the requests library — the de facto standard for HTTP in Python. Install it with pip install requests.

# regon_validator.py
import os
import requests

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


def validate_regon(regon: str, *, lookup: bool = False) -> dict:
    """Validate a Polish REGON number using the IsValid API.

    Args:
        regon: 9-digit or 14-digit REGON number.
        lookup: If True, query the GUS BIR registry for entity details.

    Returns:
        Validation result as a dictionary.

    Raises:
        requests.HTTPError: If the API returns a non-2xx status.
    """
    params = {"value": regon}
    if lookup:
        params["lookup"] = "true"

    response = requests.get(
        f"{BASE_URL}/v0/pl/regon",
        params=params,
        headers={"Authorization": f"Bearer {API_KEY}"},
    )
    response.raise_for_status()
    return response.json()


# ── Basic validation ────────────────────────────────────────────────────────
result = validate_regon("123456785")

if not result["valid"]:
    print("Invalid REGON")
else:
    print(f"Type: {result['type']}")  # "entity" or "local-unit"

# ── With GUS BIR lookup ─────────────────────────────────────────────────────
detailed = validate_regon("123456785", lookup=True)

if detailed["valid"] and detailed.get("regon", {}).get("found"):
    print(f"Name     : {detailed['regon']['name']}")
    print(f"NIP      : {detailed['regon']['nip']}")
    print(f"City     : {detailed['regon']['city']}")
    print(f"Street   : {detailed['regon']['street']}")

In a web application, you might use it like this with Flask:

# app.py (Flask)
from flask import Flask, request, jsonify

app = Flask(__name__)


@app.post("/verify-company")
def verify_company():
    data = request.get_json()

    try:
        result = validate_regon(data["regon"], lookup=True)
    except requests.RequestException:
        return jsonify(error="REGON validation service unavailable"), 502

    if not result["valid"]:
        return jsonify(error="Invalid REGON number"), 400

    regon_data = result.get("regon", {})
    if regon_data.get("found") and regon_data.get("activityEndDate"):
        return jsonify(error="Company is no longer active"), 400

    return jsonify(
        valid=True,
        companyName=regon_data.get("name"),
        nip=regon_data.get("nip"),
        city=regon_data.get("city"),
    )
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.

result = validate_regon("123456785", lookup=True)

if result["valid"] and "regon" in result:
    regon_data = result["regon"]
    if not regon_data["checked"]:
        # GUS BIR was unreachable — checksum passed but no registry data
        print("GUS BIR unavailable, retry later")
    elif regon_data["found"]:
        print(f"Entity: {regon_data['name']}")
    else:
        print("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.

result = validate_regon("123456785", lookup=True)

if result["valid"] and result.get("regon", {}).get("found"):
    if result["regon"]["activityEndDate"]:
        print(f"Entity dissolved on: {result['regon']['activityEndDate']}")
        # Reject for onboarding — this company is no longer active
    else:
        print("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
result = validate_regon("12345678500000")
print(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 an integer would turn 017344390 into 17344390, which is only 8 digits and will fail validation.

# WRONG — loses the leading zero
regon = int("017344390")  # 17344390 — 8 digits, invalid

# CORRECT — preserve as string
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.