Guide · Python · SDK · REST API

CNPJ Validation in Python

The Brazilian business registry number — 14 digits, two weighted MOD-11 check digits, and an optional company lookup via BrasilAPI. Here's how to validate it correctly.

1. What is a CNPJ?

CNPJ (Cadastro Nacional da Pessoa Jurídica) is the Brazilian National Registry of Legal Entities. It is the mandatory identification number for every company, association, foundation, or any other legal entity operating in Brazil. The CNPJ is issued and managed by the Receita Federal (Brazilian Federal Revenue Service).

A CNPJ is required for:

  • Opening bank accounts and obtaining credit
  • Issuing invoices (notas fiscais)
  • Signing contracts and participating in public procurement
  • Tax filings and social security obligations
  • Registering with state and municipal authorities
EntityCNPJType
Headquarters11.222.333/0001-81Branch 0001 (matriz)
Branch office11.222.333/0002-62Branch 0002 (filial)
ℹ️Every Brazilian company has at least one CNPJ with branch number 0001, which identifies the headquarters (matriz). Additional branches receive incrementing branch numbers (0002, 0003, etc.) but share the same 8-digit base.

2. CNPJ anatomy

A CNPJ is a 14-digit number. When formatted, it uses the pattern XX.XXX.XXX/XXXX-XX. Internally, it consists of three parts:

Example: 11.222.333/0001-81

11222333Base (8 digits)
0001Branch (4 digits)
81Check (2 digits)
PartPositionsDescription
Base number1–8Uniquely identifies the company. Assigned sequentially by Receita Federal.
Branch number9–120001 = headquarters (matriz). Higher values identify branch offices (filiais).
Check digits13–14Two verification digits computed with a weighted MOD-11 algorithm.
ℹ️The formatted representation uses dots, a slash, and a hyphen as separators: XX.XXX.XXX/XXXX-XX. The API accepts both the formatted and the raw 14-digit form.

3. The check digit algorithm

CNPJ uses two consecutive weighted MOD-11 sums. The first pass computes check digit 1 (position 13) from digits 1–12. The second pass computes check digit 2 (position 14) from digits 1–13 (including the freshly computed check digit 1).

First check digit (position 13)

  1. Multiply each of the first 12 digits by the weight sequence: [5, 4, 3, 2, 9, 8, 7, 6, 5, 4, 3, 2]
  2. Sum all products
  3. Calculate remainder = sum mod 11
  4. If the remainder is less than 2, the check digit is 0; otherwise the check digit is 11 − remainder

Second check digit (position 14)

  1. Multiply each of the first 13 digits (including check digit 1) by the weight sequence: [6, 5, 4, 3, 2, 9, 8, 7, 6, 5, 4, 3, 2]
  2. Sum all products
  3. Calculate remainder = sum mod 11
  4. Same rule: if the remainder is less than 2, the check digit is 0; otherwise 11 − remainder

Here is a step-by-step worked example for the CNPJ 11222333000181:

PositionDigitWeight (1st)Product
1155
2144
3236
4224
52918
63824
73721
83618
9050
10040
11030
12122

Sum = 5 + 4 + 6 + 4 + 18 + 24 + 21 + 18 + 0 + 0 + 0 + 2 = 102

102 mod 11 = 102 − 9 × 11 = 102 − 99 = 3

The remainder is 3 (not less than 2), so check digit 1 = 11 − 3 = 8.

The second pass uses digits 1–13 (including the 8) with weights [6, 5, 4, 3, 2, 9, 8, 7, 6, 5, 4, 3, 2], yielding check digit 2 = 1.

Both check digits match the CNPJ 11222333000181 — it is structurally valid.

Here is a Python implementation:

import re


def validate_cnpj(cnpj: str) -> bool:
    """Validate a Brazilian CNPJ using the two-pass MOD-11 algorithm."""
    # Strip formatting
    digits = re.sub(r"[^\d]", "", cnpj)
    if len(digits) != 14:
        return False

    # Reject known invalid patterns (all same digit)
    if len(set(digits)) == 1:
        return False

    d = [int(c) for c in digits]

    # First check digit (position 13)
    w1 = [5, 4, 3, 2, 9, 8, 7, 6, 5, 4, 3, 2]
    sum1 = sum(d[i] * w1[i] for i in range(12))
    rem1 = sum1 % 11
    check1 = 0 if rem1 < 2 else 11 - rem1
    if check1 != d[12]:
        return False

    # Second check digit (position 14)
    w2 = [6, 5, 4, 3, 2, 9, 8, 7, 6, 5, 4, 3, 2]
    sum2 = sum(d[i] * w2[i] for i in range(13))
    rem2 = sum2 % 11
    check2 = 0 if rem2 < 2 else 11 - rem2
    return check2 == d[13]


# Example usage
print(validate_cnpj("11.222.333/0001-81"))  # True
print(validate_cnpj("11222333000181"))       # True
print(validate_cnpj("00000000000000"))       # False
⚠️A CNPJ that passes the checksum is not necessarily an active, registered company. The number may belong to a dissolved or suspended entity. Use the ?lookup=true parameter to check the company's status via BrasilAPI.

4. Why manual validation isn't enough

Checksum only catches typos

The MOD-11 algorithm detects transposition errors and single-digit mistakes, but it cannot tell you whether the CNPJ was actually issued by Receita Federal. A number can pass the checksum and still be completely fabricated.

Companies can be inactive or suspended

A CNPJ remains structurally valid even after the company is dissolved, has its registration suspended, or is declared unfit (inapta) by Receita Federal. Only a registry lookup can reveal the current status.

You need company details for compliance

KYC, invoicing, and procurement workflows require more than a boolean. You need the company's legal name (razão social), trade name (nome fantasia), activity code (CNAE), and registration status. The checksum gives you none of this.


5. The right solution: one API call

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

1

Checksum validation

Two-pass MOD-11 algorithm on all 14 digits

2

Company lookup (optional)

Legal name, trade name, CNAE, status, and address from BrasilAPI

3

Headquarters detection

Identifies whether the CNPJ is a headquarters (0001) or a branch

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: CNPJ Validation API docs →


6. Python code example

Using the isvalid-sdk Python SDK or the popular requests library. Install either with pip install isvalid-sdk or pip install requests.

# cnpj_validator.py
import os
from isvalid_sdk import IsValidConfig, create_client

iv = create_client(IsValidConfig(api_key=os.environ["ISVALID_API_KEY"]))

# -- Basic validation --------------------------------------------------------
result = iv.br.cnpj("11.222.333/0001-81")

if not result["valid"]:
    print("Invalid CNPJ")
else:
    print("Formatted:", result["formatted"])       # "11.222.333/0001-81"
    print("HQ?      :", result["isHeadquarters"])   # True

# -- With company lookup -----------------------------------------------------
detailed = iv.br.cnpj("11222333000181", lookup=True)

if detailed["valid"] and detailed.get("found"):
    print("Company  :", detailed["razaoSocial"])
    print("Trade    :", detailed["nomeFantasia"])
    print("Status   :", detailed["situacao"])
    print("CNAE     :", detailed["cnaeFiscal"], detailed["cnaeDescricao"])
    print("State    :", detailed["uf"])

In a Django or Flask view you might use it like this:

# views.py (Django / Flask pattern)
from django.http import JsonResponse
import requests


def validate_company(request):
    cnpj = request.GET.get("cnpj", "").strip()
    if not cnpj:
        return JsonResponse({"error": "cnpj parameter required"}, status=400)

    try:
        result = validate_cnpj(cnpj, lookup=True)
    except requests.Timeout:
        return JsonResponse({"error": "validation service timeout"}, status=502)
    except requests.HTTPError as exc:
        return JsonResponse({"error": str(exc)}, status=502)

    if not result["valid"]:
        return JsonResponse({"error": "Invalid CNPJ"}, status=422)

    if result.get("found") and result.get("situacao") != "Ativa":
        return JsonResponse({
            "error": f"Company is not active (status: {result['situacao']})"
        }, status=422)

    return JsonResponse({
        "cnpj": result["formatted"],
        "company": result.get("razaoSocial"),
        "tradeName": result.get("nomeFantasia"),
        "status": result.get("situacao"),
        "state": result.get("uf"),
    })
The lookup parameter is optional. Use it when you need company details like legal name, status, or CNAE code. Omit it for fast checksum-only validation.

7. cURL examples

Basic CNPJ validation:

curl -H "Authorization: Bearer YOUR_API_KEY" \
  "https://api.isvalid.dev/v0/br/cnpj?value=11.222.333/0001-81"

With company lookup via BrasilAPI:

curl -H "Authorization: Bearer YOUR_API_KEY" \
  "https://api.isvalid.dev/v0/br/cnpj?value=11222333000181&lookup=true"

8. Understanding the response

Valid CNPJ (basic)

{
  "valid": true,
  "formatted": "11.222.333/0001-81",
  "base": "11222333",
  "branch": "0001",
  "checkDigits": "81",
  "isHeadquarters": true
}

Valid CNPJ (with lookup)

{
  "valid": true,
  "formatted": "11.222.333/0001-81",
  "base": "11222333",
  "branch": "0001",
  "checkDigits": "81",
  "isHeadquarters": true,
  "found": true,
  "razaoSocial": "EMPRESA EXEMPLO LTDA",
  "nomeFantasia": "EXEMPLO",
  "situacao": "Ativa",
  "dataInicioAtividade": "2010-05-15",
  "cnaeFiscal": "6201501",
  "cnaeDescricao": "Desenvolvimento de programas de computador sob encomenda",
  "naturezaJuridica": "206-2 - Sociedade Empresaria Limitada",
  "porte": "ME",
  "capitalSocial": 50000,
  "uf": "SP",
  "municipio": "SAO PAULO",
  "cep": "01310100",
  "dataSource": "brasilapi"
}

Invalid CNPJ

{ "valid": false }
FieldTypeDescription
validbooleanWhether the CNPJ passed format and checksum validation
formattedstringCNPJ in XX.XXX.XXX/XXXX-XX format
basestring8-digit company identifier
branchstring4-digit branch number (0001 = headquarters)
checkDigitsstringThe two verification digits
isHeadquartersbooleanTrue when the branch number is 0001
foundbooleanWhether the company was found in BrasilAPI (only with lookup=true)
razaoSocialstringLegal name of the company
nomeFantasiastringTrade name / brand name
situacaostringRegistration status (e.g. "Ativa", "Baixada", "Inapta")
dataInicioAtividadestringDate when business activity started (YYYY-MM-DD)
cnaeFiscalstringPrimary economic activity code (CNAE)
cnaeDescricaostringHuman-readable description of the CNAE code
naturezaJuridicastringLegal nature (e.g. "206-2 - Sociedade Empresaria Limitada")
portestringCompany size classification (ME, EPP, etc.)
capitalSocialnumberRegistered share capital in BRL
ufstringTwo-letter state code (e.g. "SP", "RJ")
municipiostringCity / municipality name
cepstringPostal code (8 digits, no hyphen)
dataSourcestringData provider identifier ("brasilapi")

9. Edge cases to handle

(a) Branch vs headquarters

A CNPJ with branch number 0001 identifies the headquarters (matriz). Any other branch number (0002, 0003, etc.) identifies a branch office (filial). The isHeadquarters field tells you which one it is. For KYC or supplier onboarding, you may want to require the headquarters CNPJ.

result = validate_cnpj("11222333000262")

if result["valid"] and not result["isHeadquarters"]:
    print("This is a branch office (filial)")
    print("Branch number:", result["branch"])  # "0002"
    # Consider requesting the headquarters CNPJ instead

(b) Inactive companies

A CNPJ that passes checksum validation may belong to a company with status "Baixada" (deregistered) or "Inapta" (unfit). Always check the situacao field in the lookup response for compliance workflows.

result = validate_cnpj("11222333000181", lookup=True)

if result["valid"] and result.get("found"):
    if result["situacao"] != "Ativa":
        print(f"Company is not active: {result['situacao']}")
        # "Baixada" = deregistered, "Inapta" = unfit, "Suspensa" = suspended
    else:
        print("Company is active")

(c) CNPJ formatting

The API accepts both formatted (11.222.333/0001-81) and raw (11222333000181) input. The response always includes the formatted field for display. Store the raw 14-digit string in your database and format only for display.

import re

# Both produce the same result:
a = validate_cnpj("11.222.333/0001-81")
b = validate_cnpj("11222333000181")

# Always store the raw form
cnpj_for_db = re.sub(r"[^\d]", "", result["formatted"])

# Use the formatted version for display
print("Display:", result["formatted"])  # "11.222.333/0001-81"

(d) MEI vs ME vs EPP

The porte field in the lookup response indicates the company size classification. MEI (Microempreendedor Individual) is a sole proprietor with revenue up to R$81,000/year. ME (Microempresa) has revenue up to R$360,000/year. EPP (Empresa de Pequeno Porte) goes up to R$4,800,000/year. This matters for tax bracket calculations and supplier eligibility.

result = validate_cnpj("11222333000181", lookup=True)

if result["valid"] and result.get("found"):
    porte = result.get("porte", "")
    labels = {
        "MEI": "Sole proprietor (MEI)",
        "ME": "Micro enterprise",
        "EPP": "Small enterprise",
    }
    print(labels.get(porte, f"Company size: {porte}"))

Summary

Do not rely on checksum alone — it cannot verify the company is registered or active
Do not store CNPJ as an integer — leading zeros will be lost
Do not skip the situacao check — deregistered companies still pass validation
Validate both check digits with the two-pass MOD-11 algorithm
Use lookup=True for KYC flows — returns legal name, status, CNAE, and address
Check isHeadquarters to distinguish matriz from filial

See also

Validate CNPJ numbers instantly

Free tier includes 100 API calls per day. No credit card required. Checksum validation and optional company lookup from BrasilAPI included.