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.
In this guide
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
| Entity | CNPJ | Type |
|---|---|---|
| Headquarters | 11.222.333/0001-81 | Branch 0001 (matriz) |
| Branch office | 11.222.333/0002-62 | Branch 0002 (filial) |
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
| Part | Positions | Description |
|---|---|---|
| Base number | 1–8 | Uniquely identifies the company. Assigned sequentially by Receita Federal. |
| Branch number | 9–12 | 0001 = headquarters (matriz). Higher values identify branch offices (filiais). |
| Check digits | 13–14 | Two verification digits computed with a weighted MOD-11 algorithm. |
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)
- Multiply each of the first 12 digits by the weight sequence:
[5, 4, 3, 2, 9, 8, 7, 6, 5, 4, 3, 2] - Sum all products
- Calculate
remainder = sum mod 11 - If the remainder is less than 2, the check digit is 0; otherwise the check digit is
11 − remainder
Second check digit (position 14)
- 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] - Sum all products
- Calculate
remainder = sum mod 11 - 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:
| Position | Digit | Weight (1st) | Product |
|---|---|---|---|
| 1 | 1 | 5 | 5 |
| 2 | 1 | 4 | 4 |
| 3 | 2 | 3 | 6 |
| 4 | 2 | 2 | 4 |
| 5 | 2 | 9 | 18 |
| 6 | 3 | 8 | 24 |
| 7 | 3 | 7 | 21 |
| 8 | 3 | 6 | 18 |
| 9 | 0 | 5 | 0 |
| 10 | 0 | 4 | 0 |
| 11 | 0 | 3 | 0 |
| 12 | 1 | 2 | 2 |
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
?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:
Checksum validation
Two-pass MOD-11 algorithm on all 14 digits
Company lookup (optional)
Legal name, trade name, CNAE, status, and address from BrasilAPI
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"), })
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 }
| Field | Type | Description |
|---|---|---|
| valid | boolean | Whether the CNPJ passed format and checksum validation |
| formatted | string | CNPJ in XX.XXX.XXX/XXXX-XX format |
| base | string | 8-digit company identifier |
| branch | string | 4-digit branch number (0001 = headquarters) |
| checkDigits | string | The two verification digits |
| isHeadquarters | boolean | True when the branch number is 0001 |
| found | boolean | Whether the company was found in BrasilAPI (only with lookup=true) |
| razaoSocial | string | Legal name of the company |
| nomeFantasia | string | Trade name / brand name |
| situacao | string | Registration status (e.g. "Ativa", "Baixada", "Inapta") |
| dataInicioAtividade | string | Date when business activity started (YYYY-MM-DD) |
| cnaeFiscal | string | Primary economic activity code (CNAE) |
| cnaeDescricao | string | Human-readable description of the CNAE code |
| naturezaJuridica | string | Legal nature (e.g. "206-2 - Sociedade Empresaria Limitada") |
| porte | string | Company size classification (ME, EPP, etc.) |
| capitalSocial | number | Registered share capital in BRL |
| uf | string | Two-letter state code (e.g. "SP", "RJ") |
| municipio | string | City / municipality name |
| cep | string | Postal code (8 digits, no hyphen) |
| dataSource | string | Data 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
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.