Todo dev brasileiro que já precisou consultar CNPJ conhece o dilema: ou você
usa uma API que faz proxy da Receita (3 a 10 segundos por consulta, quando não
cai), ou baixa o dump de dados abertos e monta a própria base — e descobre que
"baixar um CSV" era a parte fácil.
Eu montei a própria base. Este post é o diário honesto do que funcionou, do
que quebrou e dos números reais — 217 milhões de linhas servidas em ~10ms de
p50 dentro do datacenter, num Postgres de 1 vCPU.
A arquitetura em uma frase
Não consulte a Receita em tempo real. Ingira o dump mensal e sirva da sua
infra. O resto é decorrência.
Receita (dump mensal, ~6GB zip) ──▶ ingestão Go (COPY) ──▶ Postgres ──▶ API (chi)
CGU (CEIS/CNEP, zip diário) ──▶ job diário ──┘
O dump da Receita: as pegadinhas que ninguém documenta
O layout oficial existe, mas o que quebra parser de verdade é o que está fora dele:
-
Encoding latin1 (ISO-8859-1) — acento vira lixo se você ler como UTF-8.
Em Go:
charmap.ISO8859_1.NewDecoder()numtransform.Readerstreaming. -
Decimal com vírgula (
"1000000,00") e datasYYYYMMDDonde0e00000000significam nulo. - CNPJ quebrado em 3 colunas (básico 8 + ordem 4 + DV 2). A chave de junção entre empresas, estabelecimentos e sócios é o básico — errar isso custa um dia.
- As partições 0–9 não se alinham entre arquivos. O estabelecimento da partição 3 pode ser de uma empresa da partição 7. Foreign key rígida entre as tabelas = COPY quebrando no meio da carga. A solução: sem FK; a integridade vem da fonte.
-
Bytes NUL (
0x00) no meio dos dados. O Postgres rejeita NUL emtext. Umstrings.ReplaceAll(s, "\x00", "")no parser economizou três recargas. - Desde jan/2026 o repositório é um Nextcloud do SERPRO+ com WebDAV
público — dá pra listar meses com
PROPFINDe baixar com o token do share como usuário. Adeus, scraping.
COPY ou morte
A diferença entre INSERT em lote e o protocolo COPY não é incremental — é
outra categoria. Com pgx.CopyFrom e lotes de 50k:
- 28,1 milhões de empresas em 1m28s (~320k linhas/s) num MacBook
- A base completa (68,6M empresas + 71,9M estabelecimentos + 27,8M sócios + 49M de registros do Simples) entra em cerca de uma hora numa VM modesta
O truque é não materializar nada: latin1→UTF-8→parse→COPY, tudo streaming,
memória constante.
O índice que paga o aluguel
A feature mais pedida por quem faz compliance é a busca reversa: de um nome
de pessoa para todas as empresas em que ela é sócia. Com 27,8 milhões de sócios:
CREATE INDEX idx_socios_nome_trgm ON socios USING gin (nome_socio gin_trgm_ops);
Lição aprendida: ORDER BY similarity(...) avalia todos os matches antes
do LIMIT. Com threshold 0.4 num nome comum ("JOSE..."), timeout; com 0.6,
~1s. O operador % respeita o set_limit() da sessão — alinhe o threshold do
índice com o do filtro ou o plano de query mente pra você.
Cruzar sanções com o QSA (o pulo do gato)
O CEIS/CNEP (empresas e pessoas punidas, CGU) publica CPF completo. O QSA
da Receita publica CPF mascarado: ***XXXXXX** — os 6 dígitos do meio.
Então o match sócio-sancionado é: substring(cpf_mascarado, 4, 6) = + similaridade trigram de nome ≥ 0.5. Não é
substring(cpf_completo, 4, 6)
prova — é sinal forte, e em compliance sinal forte com explicação vale mais que
caixa-preta. Numa base real: empresa limpa, sócio proibido de contratar com o
poder público até 2030. É esse cruzamento que consulta nenhuma te dá de graça.
Números de produção (Cloud Run + Cloud SQL 1 vCPU)
| Operação | Latência |
|---|---|
| Ficha completa (3 queries + JOINs de tradução) | ~8ms no datacenter, ~134ms na minha casa |
| Checagem de sanções (empresa + sócios) | 14–34ms |
| Score com sinais | ~96ms |
| Busca reversa em 27,8M de nomes | ~1s |
Custo total da infra: ~R$ 300/mês. O concorrente que faz proxy da Receita
responde em 3.000–10.000ms e cai junto com ela.
Coisas que quebraram e viraram conhecimento
- O Google Frontend reserva
/healthznos domínios*.run.app— o request nem chega no container. Use/health. - Cloud Run com scale-to-zero: primeiro request paga ~6s de cold start (pool
do Postgres). Aceitável para o meu caso;
min-instances=1resolve por ~R$ 30/mês. -
gcloud sql connectnão funciona se seu ISP te dá IPv6.cloud-sql-proxyautentica via IAM e ignora o problema. - Payment Links do Stripe suportam assinatura de R$ 0 sem cartão
(
payment_method_collection: if_required) — free tier com identidade real, zero código de checkout.
O resultado
A API está no ar em fides.api.br — tem playground sem
cadastro na landing e um free tier de 50 consultas/mês que pede só email. Se
você só precisa de razão social e endereço, a BrasilAPI é grátis e resolve;
eu mantenho uma comparação honesta que
inclui quando não usar o meu produto.
O código do parser de latin1 e a estratégia de COPY descritos aqui são
genéricos — servem pra qualquer dataset dos dados abertos brasileiros.
Dúvidas sobre o pipeline? Comenta que eu respondo — a parte de infra rendeu
mais história do que coube aqui.
Top comments (0)