Validar CPF, CNPJ e CEP em Python com BrasilAPI e ViaCEP
Valide CPF e CNPJ com dígito verificador em Python, consulte CEP com ViaCEP ou BrasilAPI, use Pydantic e HTTPX e monte um formulário seguro para cadastros BR.
Para validar CPF e CNPJ em Python, normalize os dígitos, rejeite sequências repetidas e confira os dígitos verificadores; para CEP, consulte ViaCEP ou BrasilAPI com HTTPX, timeout e tratamento de CEP inexistente. Essa combinação resolve a maior parte dos cadastros brasileiros em APIs, formulários, automações de NF-e e pipelines de dados — sem depender de um SDK proprietário só para checar formato.
Quem pergunta a um assistente “como validar CPF em Python?” ou “como consultar CEP com ViaCEP?” precisa de três coisas na mesma página: o algoritmo correto, um cliente HTTP resiliente e um caminho claro para encaixar isso em Pydantic ou FastAPI. É o que este guia entrega, com exemplos testáveis e o ângulo prático de produtos brasileiros.
O que validar localmente e o que consultar na rede
| Dado | Validação local (dígito/formato) | Consulta externa | Quando consultar |
|---|---|---|---|
| CPF | Sim — algoritmo dos dígitos verificadores | Receita / bureau (opcional) | Onboarding sensível, crédito, KYC |
| CNPJ | Sim — algoritmo dos dígitos verificadores | BrasilAPI / fontes públicas | Cadastro de emitente, fornecedor, NF-e |
| CEP | Sim — 8 dígitos | ViaCEP ou BrasilAPI | Autocompletar endereço, frete, fiscal |
Regra prática: sempre valide formato e dígito no seu processo antes de chamar a rede. Isso elimina 90% dos erros de digitação sem gastar cota de API e sem depender de latência. A consulta externa responde outra pergunta: este documento ou CEP existe e está utilizável agora?
Se o seu fluxo já lê XML de nota fiscal, combine este guia com a automação de NF-e. Para outras fontes oficiais, veja também APIs públicas brasileiras.
Preparação do ambiente
Use um ambiente virtual (venv ou uv):
python -m venv .venv
source .venv/bin/activate
pip install "httpx>=0.27" "pydantic>=2.7"
Com uv:
uv init validacao-docs-br
cd validacao-docs-br
uv add httpx pydantic
HTTPX entra pela consulta de CEP e CNPJ; Pydantic, pela validação de formulários e APIs. O algoritmo de CPF/CNPJ usa só a biblioteca padrão.
Normalizar documentos brasileiros
CPF e CNPJ chegam com máscara (123.456.789-09, 12.345.678/0001-95) ou só com dígitos. Centralize a limpeza:
from __future__ import annotations
import re
SOMENTE_DIGITOS = re.compile(r"\D+")
def so_digitos(valor: str) -> str:
"""Remove tudo que não for dígito."""
return SOMENTE_DIGITOS.sub("", valor or "")
def mascarar_cpf(cpf: str) -> str:
d = so_digitos(cpf)
if len(d) != 11:
raise ValueError("CPF precisa ter 11 dígitos para mascarar.")
return f"{d[:3]}.{d[3:6]}.{d[6:9]}-{d[9:]}"
def mascarar_cnpj(cnpj: str) -> str:
d = so_digitos(cnpj)
if len(d) != 14:
raise ValueError("CNPJ precisa ter 14 dígitos para mascarar.")
return f"{d[:2]}.{d[2:5]}.{d[5:8]}/{d[8:12]}-{d[12:]}"
Guarde somente dígitos no banco e formate na apresentação. Isso evita duplicidade (12345678909 vs 123.456.789-09) e facilita índices únicos.
Validar CPF com dígito verificador
O CPF tem 11 dígitos: 9 de base + 2 verificadores. O cálculo oficial multiplica os dígitos por pesos decrescentes, soma, calcula o resto da divisão por 11 e deriva o dígito.
def _digito_verificador(digitos: str, pesos: list[int]) -> str:
total = sum(int(d) * p for d, p in zip(digitos, pesos, strict=True))
resto = total % 11
return "0" if resto < 2 else str(11 - resto)
def cpf_valido(cpf: str) -> bool:
d = so_digitos(cpf)
if len(d) != 11 or d == d[0] * 11:
return False
d1 = _digito_verificador(d[:9], list(range(10, 1, -1)))
d2 = _digito_verificador(d[:9] + d1, list(range(11, 1, -1)))
return d[-2:] == d1 + d2
assert cpf_valido("529.982.247-25") is True
assert cpf_valido("111.111.111-11") is False
assert cpf_valido("52998224726") is False # dígito errado
Pontos de atenção:
- Rejeite sequências iguais (
000...,111...): passam em algumas implementações ingênuas e são inválidas na prática. - Não use um CPF “de exemplo” de produção sem consentimento; em testes, prefira geradores controlados ou fixtures fixas documentadas.
- Validar o dígito não consulta a Receita Federal. Para KYC real você precisa de um provedor homologado.
Validar CNPJ com dígito verificador
O CNPJ tem 14 dígitos. Os pesos do primeiro verificador são 5,4,3,2,9,8,7,6,5,4,3,2; do segundo, 6 seguido da mesma sequência.
PESOS_CNPJ_1 = [5, 4, 3, 2, 9, 8, 7, 6, 5, 4, 3, 2]
PESOS_CNPJ_2 = [6, 5, 4, 3, 2, 9, 8, 7, 6, 5, 4, 3, 2]
def cnpj_valido(cnpj: str) -> bool:
d = so_digitos(cnpj)
if len(d) != 14 or d == d[0] * 14:
return False
d1 = _digito_verificador(d[:12], PESOS_CNPJ_1)
d2 = _digito_verificador(d[:12] + d1, PESOS_CNPJ_2)
return d[-2:] == d1 + d2
assert cnpj_valido("04.252.011/0001-10") is True
assert cnpj_valido("11.111.111/1111-11") is False
Em cadastros de fornecedor, valide o dígito na entrada e, se o risco do negócio exigir, consulte a situação cadastral em seguida. Um CNPJ com dígito correto pode estar baixado ou inapto.
Consultar CEP com ViaCEP
ViaCEP responde JSON com logradouro, bairro, localidade, UF e IBGE. CEP inexistente volta {"erro": true}.
from typing import Any
import httpx
VIACEP_URL = "https://viacep.com.br/ws/{cep}/json/"
class CepNaoEncontrado(Exception):
"""CEP bem formado, mas inexistente na base consultada."""
def consultar_cep_viacep(cep: str, client: httpx.Client | None = None) -> dict[str, Any]:
d = so_digitos(cep)
if len(d) != 8:
raise ValueError("CEP deve ter 8 dígitos.")
owns_client = client is None
client = client or httpx.Client(timeout=5.0)
try:
resp = client.get(VIACEP_URL.format(cep=d))
resp.raise_for_status()
dados = resp.json()
finally:
if owns_client:
client.close()
if dados.get("erro"):
raise CepNaoEncontrado(f"CEP {d} não encontrado no ViaCEP.")
return dados
Exemplo de uso:
endereco = consultar_cep_viacep("01310-100")
print(endereco["logradouro"], endereco["localidade"], endereco["uf"])
# Avenida Paulista São Paulo SP
Sempre configure timeout. Em automações que disparam dezenas de CEPs, reutilize um httpx.Client (ou AsyncClient) com pool de conexões — o mesmo padrão do guia de HTTPX moderno e de timeouts e retries.
Consultar CEP e CNPJ com BrasilAPI
A BrasilAPI oferece endpoints uniformes para CEP, CNPJ, bancos, feriados e outros dados públicos. Útil quando o produto precisa de mais do que endereço.
BRASILAPI_CEP = "https://brasilapi.com.br/api/cep/v2/{cep}"
BRASILAPI_CNPJ = "https://brasilapi.com.br/api/cnpj/v1/{cnpj}"
def consultar_cep_brasilapi(cep: str, client: httpx.Client | None = None) -> dict[str, Any]:
d = so_digitos(cep)
if len(d) != 8:
raise ValueError("CEP deve ter 8 dígitos.")
owns_client = client is None
client = client or httpx.Client(timeout=5.0)
try:
resp = client.get(BRASILAPI_CEP.format(cep=d))
if resp.status_code == 404:
raise CepNaoEncontrado(f"CEP {d} não encontrado na BrasilAPI.")
resp.raise_for_status()
return resp.json()
finally:
if owns_client:
client.close()
def consultar_cnpj_brasilapi(cnpj: str, client: httpx.Client | None = None) -> dict[str, Any]:
d = so_digitos(cnpj)
if not cnpj_valido(d):
raise ValueError("CNPJ com dígito verificador inválido.")
owns_client = client is None
client = client or httpx.Client(timeout=8.0)
try:
resp = client.get(BRASILAPI_CNPJ.format(cnpj=d))
if resp.status_code == 404:
raise LookupError(f"CNPJ {d} não encontrado.")
resp.raise_for_status()
return resp.json()
finally:
if owns_client:
client.close()
Checklist antes de chamar a BrasilAPI em produção:
- Valide CPF/CNPJ/CEP localmente.
- Defina timeout curto (3–8 s) e retries só para erros transitórios (429/5xx).
- Cacheie respostas de CEP por algumas horas — o endereço muda pouco.
- Não bloqueie o cadastro inteiro se a API externa cair: salve o documento validado localmente e reconcilie depois.
- Respeite termos de uso e limites; dados cadastrais mudam e caches ficam velhos.
Fallback: ViaCEP primeiro, BrasilAPI depois
Para autocomplete de endereço, um fallback simples aumenta disponibilidade:
def consultar_cep(cep: str) -> dict[str, Any]:
with httpx.Client(timeout=5.0) as client:
try:
bruto = consultar_cep_viacep(cep, client=client)
return {
"cep": so_digitos(bruto.get("cep", cep)),
"logradouro": bruto.get("logradouro") or "",
"bairro": bruto.get("bairro") or "",
"cidade": bruto.get("localidade") or "",
"uf": bruto.get("uf") or "",
"fonte": "viacep",
}
except (CepNaoEncontrado, httpx.HTTPError):
bruto = consultar_cep_brasilapi(cep, client=client)
return {
"cep": so_digitos(bruto.get("cep", cep)),
"logradouro": bruto.get("street") or bruto.get("logradouro") or "",
"bairro": bruto.get("neighborhood") or bruto.get("bairro") or "",
"cidade": bruto.get("city") or bruto.get("cidade") or "",
"uf": bruto.get("state") or bruto.get("uf") or "",
"fonte": "brasilapi",
}
Normalize o schema na sua camada de aplicação. ViaCEP e BrasilAPI não usam os mesmos nomes de campo; expor essa diferença para o frontend vira bug de formulário.
Encaixe com Pydantic e FastAPI
Com as funções prontas, o schema fica curto e a mensagem de erro fica clara para o cliente da API:
from pydantic import BaseModel, Field, field_validator
class CadastroCliente(BaseModel):
nome: str = Field(min_length=2, max_length=120)
cpf: str
cnpj: str | None = None
cep: str
@field_validator("cpf")
@classmethod
def validar_cpf(cls, valor: str) -> str:
d = so_digitos(valor)
if not cpf_valido(d):
raise ValueError("CPF inválido.")
return d
@field_validator("cnpj")
@classmethod
def validar_cnpj(cls, valor: str | None) -> str | None:
if valor is None or valor == "":
return None
d = so_digitos(valor)
if not cnpj_valido(d):
raise ValueError("CNPJ inválido.")
return d
@field_validator("cep")
@classmethod
def validar_cep(cls, valor: str) -> str:
d = so_digitos(valor)
if len(d) != 8:
raise ValueError("CEP deve ter 8 dígitos.")
return d
No endpoint, valide o body e só então consulte o CEP:
from fastapi import FastAPI, HTTPException
app = FastAPI()
@app.post("/cadastros")
def criar_cadastro(payload: CadastroCliente) -> dict[str, Any]:
try:
endereco = consultar_cep(payload.cep)
except CepNaoEncontrado as exc:
raise HTTPException(status_code=422, detail=str(exc)) from exc
except httpx.HTTPError as exc:
raise HTTPException(
status_code=503,
detail="Serviço de CEP indisponível. Tente novamente.",
) from exc
return {
"nome": payload.nome,
"cpf": payload.cpf,
"cnpj": payload.cnpj,
"endereco": endereco,
}
Esse padrão combina bem com Pydantic Settings para timeouts/base URLs e com segredos e tokens se você adicionar um bureau pago depois.
Testes rápidos com pytest
Teste o algoritmo sem rede e isole as consultas HTTP com transportes mock:
import httpx
import pytest
def test_cpf_rejeita_sequencia_repetida():
assert cpf_valido("00000000000") is False
def test_cnpj_digito_ok():
assert cnpj_valido("04252011000110") is True
def test_viacep_cep_inexistente():
def handler(request: httpx.Request) -> httpx.Response:
return httpx.Response(200, json={"erro": True})
transport = httpx.MockTransport(handler)
with httpx.Client(transport=transport) as client:
with pytest.raises(CepNaoEncontrado):
consultar_cep_viacep("00000000", client=client)
Para o guia completo de fixtures e mocking, use Testes com pytest.
Erros comuns em projetos brasileiros
- Aceitar máscara no banco e dígitos na API — normalize na borda.
- Confiar só no frontend — a máscara do React não valida dígito verificador de verdade.
- Chamar ViaCEP a cada keypress sem debounce — respeite a digitação do usuário e cacheie.
- Tratar timeout como “CEP inválido” — devolva 503/retry; não culpe o CEP.
- Logar CPF/CNPJ completo — mascare (
***.456.789-**) em logs e tickets. - Usar biblioteca abandonada de 2015 — o algoritmo cabe em 20 linhas; dependência velha vira risco de supply chain sem ganho.
Quando isso vira projeto de portfólio
Um repositório pequeno com:
- funções de CPF/CNPJ cobertas por testes;
- cliente de CEP com fallback;
- schema Pydantic + endpoint FastAPI;
- README em português com exemplos reais (sem dados pessoais de terceiros);
é evidência concreta para vagas Python de backend, automação e dados. Combine com um fluxo de planilhas ou CSV no padrão BR e você demonstra o dia a dia de sistemas que lidam com cadastro nacional.
Perguntas frequentes
Como validar CPF em Python sem biblioteca externa?
Normalize o CPF removendo pontuação, confira se tem 11 dígitos e se não é uma sequência repetida, calcule os dois dígitos verificadores com o algoritmo oficial e compare com os dígitos informados. Isso checa formato e dígito; não prova existência na Receita Federal.
ViaCEP ou BrasilAPI: qual usar para consultar CEP?
ViaCEP é simples e suficiente para a maioria dos cadastros. BrasilAPI agrega CEP e outros dados brasileiros com API uniforme. Em produção, use HTTPX com timeout, trate CEP inexistente e tenha fallback entre as duas.
Validar o dígito do CNPJ garante que a empresa existe?
Não. O dígito só detecta erro de digitação. Para situação cadastral, consulte uma fonte pública confiável (como o endpoint de CNPJ da BrasilAPI) e trate atraso, indisponibilidade e limites de uso.
Posso validar CPF e CNPJ com Pydantic?
Sim. Use field_validator chamando suas funções e levantando ValueError. FastAPI rejeita o payload automaticamente com erro 422.
É seguro armazenar CPF e CNPJ no banco?
Trate-os como dados pessoais sob a LGPD: minimize a coleta, controle acesso, evite logs completos e armazene só dígitos normalizados. Em APIs públicas, não exponha o documento sem autenticação.
Próximo passo
Com CPF, CNPJ e CEP sob controle, o próximo salto natural é amarrar o cadastro a um fluxo real de negócio: ler XML de NF-e, consumir dados públicos, proteger webhooks com HMAC ou expor a API com o estilo do FastAPI. Se a dúvida for de carreira, veja também o portfólio Python e as vagas abertas.