Se você já construiu um cadastro, checkout ou fluxo de faturamento para o Brasil (ou para a América Latina), já esbarrou nisso: CPF e CNPJ carregam dígitos verificadores que pegam erros de digitação antes de chegarem ao seu banco de dados — e cada país vizinho tem seus próprios formatos.
Os algoritmos são públicos, mas os snippets que circulam por aí costumam errar nos casos de borda — e, com o CNPJ alfanumérico de 2026, muita validação antiga vai quebrar. Aqui está como CPF e CNPJ funcionam de verdade, mais alguns documentos de outros países, e no final uma API gratuita que criei para validar 19 tipos.
🇬🇧 English version: https://dev.to/ialatam/how-check-digits-work-for-latin-american-ids-cuit-cpf-rfc-and-an-api-that-validates-19-of-them-4cj4
👉 Exemplos prontos para copiar (curl, Python, JS): validalatam.ia-latam.app/pt
CPF: dois dígitos verificadores
O CPF tem 9 dígitos base + 2 dígitos verificadores. O primeiro usa pesos 10..2; o segundo usa 11..2 sobre os 10 primeiros dígitos. Nos dois casos o dígito é (soma * 10) % 11, e 10 vira 0. Sequências de um único dígito repetido, como 111.111.111-11, passam na conta, mas são explicitamente inválidas.
def cpf_ok(c: str) -> bool:
if len(set(c)) == 1:
return False
for n in (9, 10):
s = sum(int(c[i]) * (n + 1 - i) for i in range(n))
if (s * 10) % 11 % 10 != int(c[n]):
return False
return True
cpf_ok("11144477735") # True
CNPJ (inclusive o alfanumérico)
O CNPJ segue a mesma ideia de dois dígitos verificadores, com pesos diferentes: 12 posições base (8 da raiz + 4 da filial, como 0001) e 2 dígitos verificadores, calculados com módulo 11.
A partir de 2026, a Receita Federal passa a emitir CNPJ alfanumérico: as 12 primeiras posições podem conter letras, e os dois verificadores continuam numéricos. O cálculo é o mesmo, mas o valor de cada caractere passa a ser ord(ch) - 48, ou seja, 0..9 continuam valendo 0..9 e A = 17, B = 18, etc. Se o seu validador faz int(ch), ele vai quebrar.
Outros países
-
CUIT / CUIL (Argentina): módulo 11 com pesos
5,4,3,2,7,6,5,4,3,2; resultado 11 vira 0 e 10 nunca é válido.
def cuit_ok(c: str) -> bool:
w = [5, 4, 3, 2, 7, 6, 5, 4, 3, 2]
dv = 11 - sum(int(a) * b for a, b in zip(c[:10], w)) % 11
return dv != 10 and (0 if dv == 11 else dv) == int(c[10])
cuit_ok("20123456786") # True
cuit_ok("20123456780") # False
O RUC do Peru usa os mesmos pesos, mas mapeia 10 → 0 e 11 → 1, e o RUT do Chile percorre da direita para a esquerda com pesos cíclicos 2..7, onde 10 vira K.
-
RFC (México): 4 letras + data de nascimento (AAMMDD) + homoclave de 3 caracteres, mapeados por um dicionário de 39 símbolos — o
&e oÑsão onde validadores caseiros costumam falhar:
D = "0123456789ABCDEFGHIJKLMN&OPQRSTUVWXYZ Ñ"
def rfc_ok(r: str) -> bool:
r = r.upper().rjust(13) # company RFCs (12 chars) are left-padded
s = sum(D.index(ch) * (13 - i) for i, ch in enumerate(r[:12]))
dv = 11 - s % 11
expected = "0" if dv == 11 else "A" if dv == 10 else str(dv)
return r[12] == expected
rfc_ok("GODE561231GR8") # True
-
CLABE (MX) e CBU/CVU (AR) usam módulo 10 ponderado (
3,7,1na CLABE; dois blocos no CBU) -
CURP (MX) mapeia
A=10 … Ñ=24 … Z=36, pesos 18..2, módulo 10 -
IBAN: move os 4 primeiros caracteres para o final, converte letras em números,
mod 97 == 1
Um dígito verificador válido significa que o número está bem formado, não que ele existe. Só a Receita (ou o órgão fiscal de cada país) pode confirmar isso. Mesmo assim, ele pega a maioria dos erros de digitação na hora do preenchimento, sem nenhuma chamada a serviço do governo.
Usando a API
Empacotei tudo isso na ValidaLatam, uma API stateless que valida e formata 19 tipos: AR_CUIT, AR_CUIL, AR_DNI, AR_CBU, AR_CVU, BR_CPF, BR_CNPJ, MX_RFC, MX_CURP, MX_CLABE, CL_RUT, CO_NIT, PE_RUC, UY_RUT, UY_CI, EC_CEDULA, EC_RUC, PY_RUC e IBAN. É determinística, não armazena nada e tem CORS aberto. Documentação: https://validalatam.ia-latam.app
A API direta é gratuita para testar (com limite diário).
curl
curl "https://validalatam.ia-latam.app/validate/AR_CUIT?value=20-12345678-6"
{"valid":true,"type":"AR_CUIT","country":"AR","input":"20-12345678-6","normalized":"20123456786","formatted":"20-12345678-6","error":null,"details":{"entity":"person","dni":"12345678"}}
Se você não sabe qual é o tipo, use AUTO — por exemplo, com um CNPJ:
curl -X POST https://validalatam.ia-latam.app/validate \
-H 'content-type: application/json' \
-d '{"type":"AUTO","value":"11.222.333/0001-81"}'
{"valid":true,"type":"BR_CNPJ","country":"BR","input":"11.222.333/0001-81","normalized":"11222333000181","formatted":"11.222.333/0001-81","error":null,"details":{"branch":"0001","alphanumeric":false}}
Valores inválidos retornam valid: false com o motivo:
{"valid":false,"type":"AR_CUIT","country":"AR","input":"20123456780","normalized":"20123456780","formatted":null,"error":"check digit mismatch","details":null}
Python (batch)
import requests
r = requests.post("https://validalatam.ia-latam.app/batch", json={"items": [
{"type": "CL_RUT", "value": "11.111.111-1"},
{"value": "002010077777777771", "country": "MX"}, # AUTO + country hint
{"type": "BR_CPF", "value": "111.111.111-11"},
]})
for res in r.json()["results"]:
print(res["type"], res["valid"], res["formatted"] or res["error"])
CL_RUT True 11.111.111-1
MX_CLABE True 002 010 07777777777 1
BR_CPF False repeated digits are invalid
O endpoint batch aceita até 1000 itens.
JavaScript (navegador ou Node)
Como o CORS é aberto, dá para chamar direto de um formulário:
const res = await fetch(
"https://validalatam.ia-latam.app/validate/MX_RFC?value=GODE561231GR8"
);
const { valid, formatted, error } = await res.json();
console.log(valid ? `OK: ${formatted}` : `Invalid: ${error}`);
Dados de teste
GET /generate/{type}?count=N&seed=S retorna valores sintéticos com formato válido para fixtures. Cada um vem marcado com "fake": true.
curl "https://validalatam.ia-latam.app/generate/BR_CPF?count=2&seed=42"
# {"fake":true,"items":[{"value":"15860461607","formatted":"158.604.616-07",...}, ...]}
Para produção, com chave e rate limits, há uma listagem na RapidAPI com plano gratuito (500 req/mês): https://rapidapi.com/MatiasPengue/api/validalatam
Para agentes de IA: servidor MCP
LLMs são péssimos com dígitos verificadores: eles vão afirmar com toda a confiança que um CPF é válido. Se você usa Claude Desktop, Cursor ou outro cliente MCP, pode dar ao agente uma ferramenta determinística:
{
"mcpServers": {
"validalatam": { "command": "uvx", "args": ["validalatam-mcp"] }
}
}
Código-fonte: https://github.com/elmatis172/validalatam-mcp (também está no registro oficial de MCP). Aí você pode pedir coisas como "valide estes 50 CNPJs da planilha e formate os válidos", e o agente chama a ferramenta em vez de chutar.
Também há um actor no Apify para processamento em lote: https://apify.com/ialatam/latam-document-validator
Concluindo
Se você só precisa de CPF e CNPJ, os snippets acima bastam. Copie e adicione testes com valores conhecidos. Se você lida com vários países, ou quer formatação e detecção automática, a API pode economizar seu tempo.
👉 Exemplos prontos para copiar (curl, Python, JS): validalatam.ia-latam.app/pt
Sou o Matias, desenvolvedor da Argentina. Qual documento devo adicionar a seguir? NIT da Bolívia, RIF da Venezuela e cédula da Costa Rica estão na lista. Relatos de bugs em casos de borda são muito bem-vindos.
Top comments (0)