Como preencher o cadastro pelo CNPJ: endereço automático no formulário
Num cadastro de cliente ou fornecedor, o usuário digita o CNPJ e o resto se preenche sozinho: razão social, nome fantasia, endereço completo com o código IBGE do município, telefone, e-mail e a situação na Receita Federal. Este guia monta isso com uma rota no seu back-end (Node.js ou Python, sem dependências) e um formulário em JavaScript puro. Todo o código foi executado contra a API em 13/10/2026.
Como funciona
- O usuário digita ou cola o CNPJ, com ou sem máscara.
- Ao completar 14 caracteres, o navegador confere o dígito verificador. CNPJ digitado errado para aí, sem chamada nenhuma.
- O navegador chama uma rota do seu back-end:
GET /api/cnpj/34028316000103. - O back-end consulta
https://apipj.com.br/v1/cnpj/{cnpj}?formato=completocom a chave guardada na variável de ambienteAPIPJ_KEY, guarda a resposta em cache e devolve só os campos do formulário. - O formulário se preenche, avisa se a empresa não está ATIVA e deixa tudo editável.
navegador --GET /api/cnpj/{cnpj}--> seu back-end --Bearer APIPJ_KEY--> apipj.com.br/v1/cnpj/{cnpj}
<--só os campos do form-- <--resposta completa--A chave nunca vai para o navegador
A API aceita chamadas de qualquer origem (o CORS é liberado), então dá para chamá-la direto do JavaScript da página. Não faça isso: a chave ficaria no código ou no tráfego do navegador, e qualquer visitante a copia pelo DevTools e consome a cota do seu plano. Com a rota no back-end:
- a chave fica numa variável de ambiente do servidor;
- o cache vale para todos os usuários, não só para uma aba;
- a página recebe só os campos do cadastro, e não sócios, CNAEs e o resto da resposta.
Campo a campo
Os nomes abaixo são os da resposta completa (formato=completo, o padrão). Valores de exemplo do CNPJ 34.028.316/0001-03, na carga de setembro de 2026:
| Campo do formulário | Caminho na resposta completa | Exemplo e cuidados |
|---|---|---|
| Razão social | razao_social | EMPRESA BRASILEIRA DE CORREIOS E TELEGRAFOS |
| Nome fantasia | nome_fantasia | CORREIOS SEDE; null quando não há |
| Logradouro | endereco.tipo_logradouro + endereco.logradouro | SETOR + SBN QUADRA 1 BLOCO A: o tipo vem separado, junte os dois |
| Número | endereco.numero | S/N em endereço sem número |
| Complemento | endereco.complemento | null quando vazio |
| Bairro | endereco.bairro | ASA NORTE |
| CEP | endereco.cep | 70002900: 8 dígitos, sem hífen |
| Cidade | endereco.municipio.nome | BRASILIA: maiúsculas e sem acento, como a Receita publica |
| UF | endereco.uf | DF |
| Código IBGE | endereco.municipio.codigo_ibge | 5300108: 7 dígitos. Não confunda com codigo_rfb (9701), o código do município na Receita |
| Telefone | telefones[]: ddd, numero | [{"ddd":"61","numero":"32144316"}]; a lista pode vir vazia |
email | null quando não há | |
| Situação | situacao_cadastral.descricao | ATIVA; o código fica em situacao_cadastral.codigo (02) |
O código IBGE é o que costuma faltar: nota fiscal eletrônica e vários cadastros fiscais pedem o município pelo código IBGE, não pelo nome. Ele só existe no formato completo. O formato simplificado (formato=simplificado) traz o endereço já formatado (logradouro com o tipo, CEP 70.002-900, telefone (61) 3214-4316), mas não tem o código IBGE; por isso este guia usa o completo.
O back-end: uma rota com cache
A rota GET /api/cnpj/:cnpj faz quatro coisas: valida o CNPJ localmente, consulta a API com a chave, traduz a resposta para os campos do formulário e guarda o resultado. O mesmo servidor entrega a página (public/cadastro.html e public/cadastro.js, mais abaixo). Escolha a linguagem:
import { createServer } from 'node:http';
import { readFile } from 'node:fs/promises';
const API = (process.env.APIPJ_BASE_URL ?? 'https://apipj.com.br').replace(/\/+$/, '');
const CHAVE = process.env.APIPJ_KEY; // só no servidor: nunca no HTML nem no JavaScript do navegador
const PORTA = Number(process.env.PORT ?? 3000);
const DIA = 24 * 60 * 60 * 1000;
const PESOS = [6, 5, 4, 3, 2, 9, 8, 7, 6, 5, 4, 3, 2];
/** Formato e dígitos verificadores, numérico ou alfanumérico (valor = código ASCII - 48). */
function cnpjValido(cnpj) {
if (!/^[A-Z0-9]{12}[0-9]{2}$/.test(cnpj) || /^0+$/.test(cnpj)) return false;
const v = [...cnpj].map((ch) => ch.charCodeAt(0) - 48);
const dv = (n) => {
let soma = 0;
for (let i = 0; i < n; i++) soma += v[i] * PESOS[i + 13 - n];
const resto = soma % 11;
return resto < 2 ? 0 : 11 - resto;
};
return v[12] === dv(12) && v[13] === dv(13);
}
/** Só o que o formulário usa; o resto da resposta (sócios, CNAEs...) não sai do servidor. */
function paraFormulario(e) {
const end = e.endereco;
const tel = e.telefones[0];
return {
cnpj: e.cnpj,
razao_social: e.razao_social,
nome_fantasia: e.nome_fantasia ?? '',
logradouro: [end.tipo_logradouro, end.logradouro].filter(Boolean).join(' '),
numero: end.numero ?? '',
complemento: end.complemento ?? '',
bairro: end.bairro ?? '',
cep: (end.cep ?? '').replace(/^(\d{5})(\d{3})$/, '$1-$2'),
cidade: end.municipio?.nome ?? '',
uf: end.uf ?? '',
codigo_ibge: end.municipio?.codigo_ibge ?? '',
telefone: tel ? `(${tel.ddd}) ${tel.numero.slice(0, -4)}-${tel.numero.slice(-4)}` : '',
email: (e.email ?? '').toLowerCase(),
situacao: e.situacao_cadastral.descricao,
};
}
const cache = new Map(); // cnpj -> { expira, resposta }
async function consultar(cnpj) {
const guardado = cache.get(cnpj);
if (guardado && guardado.expira > Date.now()) return guardado.resposta;
const r = await fetch(`${API}/v1/cnpj/${cnpj}?formato=completo`, {
headers: { Authorization: `Bearer ${CHAVE}` },
signal: AbortSignal.timeout(8000),
});
let resposta;
if (r.status === 200) resposta = { status: 200, corpo: paraFormulario(await r.json()) };
else if (r.status === 404) resposta = { status: 404, corpo: { erro: 'CNPJ não encontrado na base da Receita Federal' } };
else if (r.status === 429) {
return { status: 429, corpo: { erro: 'Limite de consultas atingido; tente de novo em instantes' }, retryAfter: r.headers.get('retry-after') };
} else if (r.status === 400) return { status: 400, corpo: { erro: 'CNPJ inválido' } };
else throw new Error(`apipj respondeu HTTP ${r.status}`); // 401 (chave), 5xx...
// Encontrado: 30 dias (a base é mensal). Não encontrado: 1 dia, para não segurar uma empresa recém-aberta.
if (cache.size >= 10_000) cache.delete(cache.keys().next().value);
cache.set(cnpj, { expira: Date.now() + (resposta.status === 200 ? 30 * DIA : DIA), resposta });
return resposta;
}
function json(res, status, corpo, extras = {}) {
res.writeHead(status, { 'Content-Type': 'application/json; charset=utf-8', ...extras });
res.end(JSON.stringify(corpo));
}
const ESTATICOS = {
'/': ['cadastro.html', 'text/html; charset=utf-8'],
'/cadastro.js': ['cadastro.js', 'text/javascript; charset=utf-8'],
};
createServer(async (req, res) => {
const { pathname } = new URL(req.url, 'http://localhost');
const rota = pathname.match(/^\/api\/cnpj\/(.*)$/);
if (req.method === 'GET' && rota) {
const cnpj = rota[1].replace(/[.-]/g, '').toUpperCase();
if (!cnpjValido(cnpj)) return json(res, 400, { erro: 'CNPJ inválido' }); // nem chega à API
try {
const r = await consultar(cnpj);
return json(res, r.status, r.corpo, r.retryAfter ? { 'Retry-After': r.retryAfter } : {});
} catch (e) {
console.error('apipj:', e.message);
return json(res, 502, { erro: 'Consulta de CNPJ indisponível' });
}
}
const estatico = ESTATICOS[pathname];
if (req.method === 'GET' && estatico) {
res.writeHead(200, { 'Content-Type': estatico[1] });
return res.end(await readFile(new URL(`./public/${estatico[0]}`, import.meta.url)));
}
json(res, 404, { erro: 'Rota não encontrada' });
}).listen(PORTA, () => console.log(`Formulário em http://localhost:${PORTA}`));import json
import os
import re
import threading
import time
import urllib.error
import urllib.request
from http.server import BaseHTTPRequestHandler, ThreadingHTTPServer
from pathlib import Path
API = os.environ.get("APIPJ_BASE_URL", "https://apipj.com.br").rstrip("/")
CHAVE = os.environ["APIPJ_KEY"] # só no servidor: nunca no HTML nem no JavaScript do navegador
PORTA = int(os.environ.get("PORT", "8000"))
PUBLICO = Path(__file__).parent / "public"
DIA = 24 * 60 * 60
PESOS = [6, 5, 4, 3, 2, 9, 8, 7, 6, 5, 4, 3, 2]
FORMATO = re.compile(r"[A-Z0-9]{12}[0-9]{2}")
def cnpj_valido(cnpj):
"""Formato e dígitos verificadores, numérico ou alfanumérico (valor = código ASCII - 48)."""
if not FORMATO.fullmatch(cnpj) or cnpj == "0" * 14:
return False
v = [ord(ch) - 48 for ch in cnpj]
def dv(n):
resto = sum(v[i] * PESOS[i + 13 - n] for i in range(n)) % 11
return 0 if resto < 2 else 11 - resto
return v[12] == dv(12) and v[13] == dv(13)
def para_formulario(e):
"""Só o que o formulário usa; o resto da resposta (sócios, CNAEs...) não sai do servidor."""
end = e["endereco"]
municipio = end.get("municipio") or {}
tel = e["telefones"][0] if e["telefones"] else None
cep = end.get("cep") or ""
return {
"cnpj": e["cnpj"],
"razao_social": e["razao_social"],
"nome_fantasia": e.get("nome_fantasia") or "",
"logradouro": " ".join(p for p in (end.get("tipo_logradouro"), end.get("logradouro")) if p),
"numero": end.get("numero") or "",
"complemento": end.get("complemento") or "",
"bairro": end.get("bairro") or "",
"cep": f"{cep[:5]}-{cep[5:]}" if len(cep) == 8 else cep,
"cidade": municipio.get("nome") or "",
"uf": end.get("uf") or "",
"codigo_ibge": municipio.get("codigo_ibge") or "",
"telefone": f"({tel['ddd']}) {tel['numero'][:-4]}-{tel['numero'][-4:]}" if tel else "",
"email": (e.get("email") or "").lower(),
"situacao": e["situacao_cadastral"]["descricao"],
}
_cache = {} # cnpj -> (expira, resposta)
_trava = threading.Lock()
def consultar(cnpj):
with _trava:
guardado = _cache.get(cnpj)
if guardado and guardado[0] > time.time():
return guardado[1]
pedido = urllib.request.Request(
f"{API}/v1/cnpj/{cnpj}?formato=completo", headers={"Authorization": f"Bearer {CHAVE}"}
)
try:
with urllib.request.urlopen(pedido, timeout=8) as r:
resposta = (200, para_formulario(json.load(r)), None)
except urllib.error.HTTPError as e:
if e.code == 404:
resposta = (404, {"erro": "CNPJ não encontrado na base da Receita Federal"}, None)
elif e.code == 429:
return (429, {"erro": "Limite de consultas atingido; tente de novo em instantes"}, e.headers.get("Retry-After"))
elif e.code == 400:
return (400, {"erro": "CNPJ inválido"}, None)
else:
raise # 401 (chave), 5xx...
# Encontrado: 30 dias (a base é mensal). Não encontrado: 1 dia, para não segurar uma empresa recém-aberta.
with _trava:
if len(_cache) >= 10_000:
_cache.pop(next(iter(_cache)))
_cache[cnpj] = (time.time() + (30 * DIA if resposta[0] == 200 else DIA), resposta)
return resposta
ESTATICOS = {
"/": ("cadastro.html", "text/html; charset=utf-8"),
"/cadastro.js": ("cadastro.js", "text/javascript; charset=utf-8"),
}
class Rotas(BaseHTTPRequestHandler):
def do_GET(self):
caminho = self.path.split("?")[0]
if caminho.startswith("/api/cnpj/"):
cnpj = re.sub(r"[.-]", "", caminho[len("/api/cnpj/"):]).upper()
if not cnpj_valido(cnpj):
return self.enviar(400, {"erro": "CNPJ inválido"}) # nem chega à API
try:
status, corpo, retry_after = consultar(cnpj)
except Exception as e:
self.log_error("apipj: %s", e)
return self.enviar(502, {"erro": "Consulta de CNPJ indisponível"})
return self.enviar(status, corpo, retry_after)
if caminho in ESTATICOS:
arquivo, tipo = ESTATICOS[caminho]
return self.enviar(200, (PUBLICO / arquivo).read_bytes(), tipo=tipo)
self.enviar(404, {"erro": "Rota não encontrada"})
def enviar(self, status, corpo, retry_after=None, tipo="application/json; charset=utf-8"):
dados = corpo if isinstance(corpo, bytes) else json.dumps(corpo, ensure_ascii=False).encode()
self.send_response(status)
self.send_header("Content-Type", tipo)
self.send_header("Content-Length", str(len(dados)))
if retry_after:
self.send_header("Retry-After", retry_after)
self.end_headers()
self.wfile.write(dados)
if __name__ == "__main__":
print(f"Formulário em http://localhost:{PORTA}")
ThreadingHTTPServer(("", PORTA), Rotas).serve_forever()Para rodar, com a chave na variável de ambiente (nunca no código):
export APIPJ_KEY="apipj_live_sua_chave"
node servidor.mjs # ou: python servidor.pyO que a rota devolve ao navegador:
| Situação | HTTP da sua rota | Corpo |
|---|---|---|
| Encontrado | 200 | Os 14 campos do formulário |
| CNPJ mal formado ou com dígito errado | 400 | {"erro":"CNPJ inválido"}, sem chamar a API |
| CNPJ válido, fora da base da Receita | 404 | {"erro":"CNPJ não encontrado na base da Receita Federal"} |
| Limite por minuto ou cota do mês | 429 | Repassa o Retry-After da API, quando ele vem |
| Chave errada, API fora do ar, tempo esgotado | 502 | {"erro":"Consulta de CNPJ indisponível"}; o motivo vai para o log do servidor |
Detalhes que importam:
- Validação local antes de tudo. O dígito verificador é conferido no servidor também (o navegador não é confiável). CNPJ errado não gasta cota, e o código aceita o CNPJ alfanumérico: cada caractere vale o código ASCII menos 48, com pesos 6, 5, 4, 3, 2, 9, 8, 7, 6, 5, 4, 3, 2 e módulo 11.
- Cache de 30 dias para quem existe, 1 dia para o 404. A base é mensal, então repetir a consulta no mesmo mês só gasta cota. O 404 também conta na cota, mas fica pouco tempo guardado para não esconder uma empresa que entre na carga seguinte.
- 429 não é cacheado. É um estado passageiro: o
Retry-Aftervai para o navegador, que mostra quanto esperar. APIPJ_BASE_URLexiste só para apontar a rota para uma API falsa nos testes (foi assim que os caminhos 429 e 502 foram exercitados).- Cache em memória serve para um processo. Com várias instâncias, troque o
Map(ou odict) por Redis ou por uma tabela, com o mesmo prazo.
Testando a rota com curl, contra a API real, em 13/10/2026:
for c in 34028316000103 11378117000121 99999999000191; do
curl -s -w " -> HTTP %{http_code}
" localhost:3000/api/cnpj/$c
done{"cnpj":"34028316000103","razao_social":"EMPRESA BRASILEIRA DE CORREIOS E TELEGRAFOS","nome_fantasia":"CORREIOS SEDE","logradouro":"SETOR SBN QUADRA 1 BLOCO A","numero":"S/N","complemento":"","bairro":"ASA NORTE","cep":"70002-900","cidade":"BRASILIA","uf":"DF","codigo_ibge":"5300108","telefone":"(61) 3214-4316","email":"acgtescnpj@correios.com.br","situacao":"ATIVA"} -> HTTP 200
{"erro":"CNPJ inválido"} -> HTTP 400
{"erro":"CNPJ não encontrado na base da Receita Federal"} -> HTTP 404O segundo tem o último dígito errado (o certo é 11378117000120) e nem chega à API. O terceiro é válido, mas não existe na base da Receita. A versão em Python devolve os mesmos campos e os mesmos códigos.
O front-end: formulário em JavaScript puro
O HTML é um formulário comum. O que faz diferença: o aviso fica num elemento com role="status" e aria-live="polite", ligado ao campo pelo aria-describedby, para que leitores de tela anunciem "preenchido", "não encontrado" ou o alerta de situação; os campos têm autocomplete; e o campo do CNPJ não usa inputmode="numeric", porque o CNPJ alfanumérico tem letras.
<!doctype html>
<html lang="pt-BR">
<head>
<meta charset="utf-8">
<meta name="viewport" content="width=device-width, initial-scale=1">
<title>Cadastro de cliente</title>
</head>
<body>
<form id="cadastro" method="post" action="/clientes">
<label for="cnpj">CNPJ</label>
<input id="cnpj" name="cnpj" required maxlength="18" autocomplete="off" spellcheck="false"
autocapitalize="characters" aria-describedby="cnpj-aviso">
<p id="cnpj-aviso" role="status" aria-live="polite"></p>
<label for="razao_social">Razão social</label>
<input id="razao_social" name="razao_social" required autocomplete="organization">
<label for="nome_fantasia">Nome fantasia</label>
<input id="nome_fantasia" name="nome_fantasia">
<label for="situacao">Situação na Receita Federal</label>
<input id="situacao" name="situacao" readonly>
<label for="cep">CEP</label>
<input id="cep" name="cep" autocomplete="postal-code" inputmode="numeric">
<label for="logradouro">Logradouro</label>
<input id="logradouro" name="logradouro" autocomplete="address-line1">
<label for="numero">Número</label>
<input id="numero" name="numero">
<label for="complemento">Complemento</label>
<input id="complemento" name="complemento" autocomplete="address-line2">
<label for="bairro">Bairro</label>
<input id="bairro" name="bairro">
<label for="cidade">Cidade</label>
<input id="cidade" name="cidade" autocomplete="address-level2">
<label for="uf">UF</label>
<input id="uf" name="uf" maxlength="2" autocomplete="address-level1">
<input type="hidden" name="codigo_ibge">
<label for="telefone">Telefone</label>
<input id="telefone" name="telefone" type="tel" autocomplete="tel">
<label for="email">E-mail</label>
<input id="email" name="email" type="email" autocomplete="email">
<button type="submit">Salvar</button>
</form>
<script src="/cadastro.js" defer></script>
</body>
</html>O script valida o dígito verificador, espera o usuário parar de digitar, consulta a rota e preenche os campos:
const PESOS = [6, 5, 4, 3, 2, 9, 8, 7, 6, 5, 4, 3, 2];
function cnpjValido(cnpj) {
if (!/^[A-Z0-9]{12}[0-9]{2}$/.test(cnpj) || /^0+$/.test(cnpj)) return false;
const v = [...cnpj].map((ch) => ch.charCodeAt(0) - 48);
const dv = (n) => {
let soma = 0;
for (let i = 0; i < n; i++) soma += v[i] * PESOS[i + 13 - n];
const resto = soma % 11;
return resto < 2 ? 0 : 11 - resto;
};
return v[12] === dv(12) && v[13] === dv(13);
}
const CAMPOS = ['razao_social', 'nome_fantasia', 'situacao', 'cep', 'logradouro', 'numero', 'complemento',
'bairro', 'cidade', 'uf', 'codigo_ibge', 'telefone', 'email'];
const FALHOU = 'Não foi possível buscar os dados agora. Preencha à mão; o cadastro funciona do mesmo jeito.';
const form = document.getElementById('cadastro');
const campoCnpj = form.elements.cnpj;
const aviso = document.getElementById('cnpj-aviso');
const respostas = new Map(); // cnpj -> dados, ou null (não encontrado): o mesmo CNPJ não é consultado duas vezes
let ultimo = '';
let preenchidoCom = '';
let espera;
const normalizar = (valor) => valor.replace(/[^0-9A-Za-z]/g, '').toUpperCase();
const avisar = (texto, tipo = 'info') => { aviso.textContent = texto; aviso.dataset.tipo = tipo; };
function limpar() {
for (const nome of CAMPOS) form.elements[nome].value = '';
preenchidoCom = '';
}
function mostrar(cnpj, dados) {
if (!dados) {
if (preenchidoCom) limpar();
return avisar('CNPJ não encontrado na base da Receita Federal. Preencha os dados à mão.', 'erro');
}
for (const nome of CAMPOS) form.elements[nome].value = dados[nome] ?? '';
preenchidoCom = cnpj;
if (dados.situacao !== 'ATIVA') avisar(`Atenção: a situação deste CNPJ na Receita Federal é ${dados.situacao}.`, 'alerta');
else avisar(`Dados de ${dados.razao_social} preenchidos. Confira antes de salvar.`);
}
async function buscar(cnpj) {
if (respostas.has(cnpj)) return mostrar(cnpj, respostas.get(cnpj));
if (preenchidoCom) limpar(); // tira os dados de outro CNPJ
avisar('Buscando os dados do CNPJ…');
form.setAttribute('aria-busy', 'true');
try {
const r = await fetch(`/api/cnpj/${cnpj}`);
if (r.ok || r.status === 404) {
respostas.set(cnpj, r.ok ? await r.json() : null);
if (normalizar(campoCnpj.value) === cnpj) mostrar(cnpj, respostas.get(cnpj)); // senão, já digitaram outro
return;
}
ultimo = ''; // erro passageiro: deixa tentar de novo
if (r.status === 400) avisar('CNPJ inválido. Confira os dígitos.', 'erro');
else if (r.status === 429) avisar(`Muitas consultas seguidas. Tente de novo em ${r.headers.get('Retry-After') || 60} segundos ou preencha à mão.`, 'erro');
else avisar(FALHOU, 'erro');
} catch {
ultimo = '';
avisar(FALHOU, 'erro');
} finally {
form.removeAttribute('aria-busy');
}
}
campoCnpj.addEventListener('input', () => {
clearTimeout(espera);
const cnpj = normalizar(campoCnpj.value);
if (cnpj.length < 14) { ultimo = ''; return avisar(''); }
if (cnpj === ultimo) return;
if (!cnpjValido(cnpj)) return avisar('CNPJ inválido. Confira os dígitos.', 'erro');
espera = setTimeout(() => { ultimo = cnpj; buscar(cnpj); }, 400); // debounce: espera o usuário parar de digitar
});Como ele se comporta:
- Debounce de 400 ms. Digitando caractere a caractere, sai uma chamada só, quando o CNPJ fica completo.
- O mesmo CNPJ não é consultado duas vezes. O
Mapguarda as respostas da página, inclusive o "não encontrado"; voltar a um CNPJ já visto preenche sem rede. - Resposta atrasada não sobrescreve. Se o usuário já trocou o CNPJ quando a resposta chega, ela é guardada e não preenche a tela.
- Situação diferente de ATIVA vira alerta, como
Atenção: a situação deste CNPJ na Receita Federal é BAIXADA.O cadastro não é bloqueado: decidir se aceita uma empresa baixada ou inapta é regra do seu negócio. - Falha não trava o cadastro. Em 429, 502 ou erro de rede, o aviso pede para preencher à mão e os campos continuam editáveis. Um novo
inputno campo tenta de novo. - Funciona com máscara e com minúsculas:
34.028.316/0001-03e00.000.000/e08g-12viram34028316000103e00000000E08G12antes da validação.
O teste automatizado abriu a página servida pelo próprio back-end (Node.js e Python) no jsdom, digitou os CNPJs e conferiu os campos. Com 34.028.316/0001-03, os 13 campos ficaram assim (o codigo_ibge vai num campo oculto):
razao_social EMPRESA BRASILEIRA DE CORREIOS E TELEGRAFOS cep 70002-900
nome_fantasia CORREIOS SEDE logradouro SETOR SBN QUADRA 1 BLOCO A
situacao ATIVA numero S/N
bairro ASA NORTE complemento (vazio)
cidade BRASILIA uf DF
codigo_ibge 5300108 telefone (61) 3214-4316
email acgtescnpj@correios.com.brE os avisos, na ordem do teste:
34.028.316/0001-03 Dados de EMPRESA BRASILEIRA DE CORREIOS E TELEGRAFOS preenchidos. Confira antes de salvar.
33066408000115 Atenção: a situação deste CNPJ na Receita Federal é BAIXADA.
99999999000191 CNPJ não encontrado na base da Receita Federal. Preencha os dados à mão.
11378117000121 CNPJ inválido. Confira os dígitos.
00.000.000/e08g-12 Dados de BANCO DO BRASIL SA preenchidos. Confira antes de salvar.Boas práticas
- Cache de até 30 dias. Os dados abertos do CNPJ mudam uma vez por mês; o campo
atualizado_emda resposta diz de qual carga eles vêm. Um cache de 30 dias no back-end corta a maior parte das consultas repetidas. - Respeite o limite por minuto do plano. O gratuito tem 10 consultas por minuto e 50.000 por mês; o Dev, R$ 12,90 por mês, tem 30 por minuto. Um formulário digitado por pessoas raramente chega perto, mas uma importação em lote chega: nesse caso, controle o ritmo no servidor.
- Proteja a sua rota. Sem proteção,
/api/cnpj/vira uma consulta de CNPJ grátis para qualquer um, paga pela sua cota. Exija sessão de usuário logado ou ponha um limite por IP. - Não bloqueie o cadastro se a API falhar. O preenchimento é conveniência: tempo esgotado, 429 ou 502 devem deixar o usuário digitar à mão e salvar.
- Peça confirmação. O endereço da Receita é o do estabelecimento naquele CNPJ, que pode não ser o de entrega ou de cobrança. Deixe os campos editáveis.
- LGPD: guarde só o que o cadastro usa. A resposta completa traz o quadro de sócios, com nomes e CPF mascarado; a rota deste guia nem repassa esses dados ao navegador. Em empresário individual e MEI, a razão social costuma ser o nome da pessoa e o endereço pode ser a casa dela: são dados pessoais, mesmo vindos de uma base pública. Salve os campos do formulário, não o JSON inteiro, com a data da consulta se precisar provar a origem.
Perguntas frequentes
Posso chamar a API direto do navegador?
Tecnicamente sim, o CORS é liberado. Mas a chave ficaria exposta para qualquer visitante. Do navegador, chame só GET /v1/valida/{cnpj}, que é aberto, não pede chave e não consome cota, se quiser validar o CNPJ sem escrever o algoritmo. A consulta dos dados fica no back-end.
Dá para preencher só pelo CEP?
O CEP sozinho não identifica a empresa; o que este guia preenche vem do CNPJ. Se o usuário corrigir o CEP depois, valide com o serviço de CEP que você já usa.
Funciona com o CNPJ alfanumérico?
Sim. A validação do dígito nos três trechos de código (Node.js, Python e navegador) aceita letras nas 12 primeiras posições, e a consulta é a mesma. O teste usou 00000000E08G12, o primeiro CNPJ alfanumérico emitido pela Receita. O guia do CNPJ alfanumérico tem as regras.
Quantas consultas um formulário gasta?
Uma por CNPJ distinto por mês, com o cache do back-end. CNPJ com dígito errado não gasta nada, porque é barrado antes. O CNPJ válido que não existe na base gasta uma consulta, que o cache segura por um dia.
Próximos passos
- Crie uma conta grátis para ter a chave: o plano gratuito não pede cartão.
- Documentação: todos os campos da resposta, os erros e os cabeçalhos de limite.
- API de CNPJ: o que a API devolve e o que ela não substitui.
- Validar fornecedor pelo CNPJ: depois de preencher, as regras para aprovar, revisar ou bloquear o cadastro.