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.

22 Sep 2026 9 min de leitura Equipe Python Dev 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

DadoValidação local (dígito/formato)Consulta externaQuando consultar
CPFSim — algoritmo dos dígitos verificadoresReceita / bureau (opcional)Onboarding sensível, crédito, KYC
CNPJSim — algoritmo dos dígitos verificadoresBrasilAPI / fontes públicasCadastro de emitente, fornecedor, NF-e
CEPSim — 8 dígitosViaCEP ou BrasilAPIAutocompletar 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:

  1. Valide CPF/CNPJ/CEP localmente.
  2. Defina timeout curto (3–8 s) e retries só para erros transitórios (429/5xx).
  3. Cacheie respostas de CEP por algumas horas — o endereço muda pouco.
  4. Não bloqueie o cadastro inteiro se a API externa cair: salve o documento validado localmente e reconcilie depois.
  5. 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.

E
Equipe Python Dev BR

Contribuidor do Python Dev BR

Artigos relacionados