A mesma base que serve as fichas do site, em JSON: cadastro da Receita, endereço normalizado, coordenadas e código do IBGE do município. Feita para as ferramentas da casa consumirem.
Contrato OpenAPI 3.1: https://pj.bio/openapi.yaml
.env do seu projeto, nunca no código versionado.GET /api/v1/status antes de escrever qualquer integração.Base atual: ainda não importada nesta instalação. A Receita publica uma vez por mês e a base inteira é substituída na importação.
Toda rota exige chave. Mande no cabeçalho Authorization:
curl -H "Authorization: Bearer pjbio_7f3a91c0_9d2e…" \
"https://pj.bio/api/v1/status"
Se o seu proxy não deixa mexer no Authorization, use
X-API-Key com o mesmo valor. Não há outra forma —
chave em query string ficaria gravada em log de servidor e em histórico de proxy.
A chave carrega só o que foi concedido a ela. Campo fora do escopo
não vem — e a resposta não avisa, para não virar um mapa do que existe.
Confira o que a sua chave tem em /api/v1/status.
| Escopo | Libera | Observação |
|---|---|---|
cnpj |
Consultar CNPJ | Ficha completa de um CNPJ: cadastro, endereço, coordenadas e atividades. |
busca |
Buscar e listar empresas | Listagem por nome, cidade, estado e atividade, com paginação. |
socios |
Ver o quadro societário | Nome do sócio é dado pessoal. Só conceda a quem precisa de fato. |
contato |
Ver telefone e e-mail | No site esses campos exigem conta. Conceder aqui contorna esse portão. |
geo |
Gerar coordenadas de endereços | Os 111 milhões de endereços do CNEFE. Não expõe empresa nem pessoa: só CEP, rua e coordenada. |
selo |
Ler o selo "verificado pelo PJ.bio" | Se o dono provou que responde pelo CNPJ (reivindicação aprovada). É o que as outras plataformas exibem como "verificado". |
Sucesso sempre em dados; listagem traz meta junto:
{
"dados": { … },
"meta": { "fonte": "Receita Federal", "competencia": "2026-08-01" }
}
Erro sempre em erro, com um código estável para você testar:
{
"erro": { "codigo": "nao_encontrado", "mensagem": "…" }
}
Teste erro.codigo, nunca o texto de erro.mensagem —
a mensagem é para humano em log e pode mudar de redação sem aviso.
/api/v1/cnpj/{cnpj}escopo cnpj
A ficha completa de um estabelecimento. Aceita com ou sem máscara
(08430449000100 ou 08.430.449/0001-00), em qualquer caixa.
Aqui não há recorte por situação: baixada, suspensa e inapta respondem igual.
CNPJ alfanumérico. Desde julho de 2026 a Receita emite CNPJ com
letras nas 12 primeiras posições (00.000.000/E08G-12); só os dois
dígitos verificadores continuam numéricos. A API aceita e devolve os dois formatos.
Se a sua ferramenta valida CNPJ com \d{14}, ela vai rejeitar
empresas que existem.
Tamanho errado (inclusive 13 dígitos — o zero à esquerda que caiu) e dígito
verificador que não confere respondem 422 cnpj_invalido, com a mensagem
dizendo qual dos dois. 404 nao_encontrado é só para CNPJ bem formado que
não está na competência importada.
curl -H "Authorization: Bearer $PJ.BIO_TOKEN" \
"https://pj.bio/api/v1/cnpj/08430449000100"
{
"dados": {
"cnpj": "08430449000100",
"cnpj_formatado": "08.430.449/0001-00",
"tipo": "matriz",
"razao_social": "PADARIA E CONFEITARIA REIS MAGOS LTDA",
"nome_fantasia": "PADARIA REIS MAGOS",
"situacao": { "codigo": "02", "descricao": "ATIVA", "data": "2006-11-13", "motivo": null },
"abertura": { "data": "2006-11-13", "anos": 19 },
"porte": { "codigo": "01", "descricao": "ME" },
"natureza_juridica": { "codigo": "2062", "descricao": "Sociedade Empresária Limitada" },
"capital_social": "50000.00",
"simples": { "optante": true, "mei": false },
"atividade_principal": { "codigo": "4721102", "descricao": "Padaria e confeitaria…" },
"atividades_secundarias": [ … ],
"endereco": {
"logradouro": "AV ENGENHEIRO ROBERTO FREIRE",
"numero": "1912",
"bairro": "CAPIM MACIO",
"cep": "59082095",
"municipio": "NATAL",
"uf": "RN",
"municipio_codigo_rfb": "1761",
"municipio_codigo_ibge": "2408102",
"completo": "Av Engenheiro Roberto Freire, 1912 — Capim Macio — Natal / RN"
},
"localizacao": {
"latitude": -5.8712,
"longitude": -35.1998,
"precisao": "numero",
"fonte": "cnefe",
"atualizada_em": "2026-09-01T12:00:00-03:00"
},
"unidades": { "total": 3 },
"ficha": { "url": "https://pj.bio/padaria…", "reivindicada": true }
}
}
localizacao pode ser null.
92% das empresas ativas têm coordenada; o resto não tem, e a API prefere dizer
"não sei" a devolver o centro da cidade como se fosse a porta.
/api/v1/empresasescopo busca
Listagem filtrada. Só empresas ativas — não é opção: os índices do recorte são parciais nessa condição, e afrouxá-la faria cada consulta varrer dezenas de milhões de linhas.
| Parâmetro | Aceita | Para quê |
|---|---|---|
q | texto, até 120 | Busca no nome e no fantasia (índice de texto completo). |
uf | 2 letras | Recorte por estado. |
municipio | código RFB (4 díg.) ou slug | 1761 ou natal-rn. Implica a UF. |
cnae | 7 dígitos | Atividade principal. |
porte | 01, 03, 05 | ME, EPP, demais. |
mei | booleano | Optante pelo MEI. |
simples | booleano | Optante pelo Simples. |
pagina | inteiro ≥ 1 | Padrão 1. Para chegar a uma página específica. |
depois_de | o meta.proximo anterior | Cursor. Para percorrer o recorte inteiro, sem teto. Não combina com pagina. |
por_pagina | 1 a 100 | Padrão 30. |
curl -H "Authorization: Bearer $PJ.BIO_TOKEN" \
"https://pj.bio/api/v1/empresas?municipio=natal-rn&cnae=4721102&por_pagina=50"
{
"dados": [
{
"cnpj": "08430449000100",
"razao_social": "PADARIA E CONFEITARIA REIS MAGOS LTDA",
"nome_fantasia": "PADARIA REIS MAGOS",
"situacao": "ATIVA",
"municipio": "NATAL",
"uf": "RN",
"atividade_principal": "4721102",
"abertura": "2006-11-13",
"ficha": "https://pj.bio/padaria…"
}
],
"meta": {
"pagina": 1, "por_pagina": 50,
"proximo": "02205724000369",
"total": 10000, "total_exato": false,
"somente_ativas": true
}
}
Passe o meta.proximo de cada resposta em depois_de na
seguinte. Quando vier null, acabou. Cada página custa o mesmo que a
primeira, não importa a profundidade — é o modo certo para enriquecer um cadastro:
GET /api/v1/empresas?uf=RN&cnae=4721102&por_pagina=100
GET /api/v1/empresas?uf=RN&cnae=4721102&por_pagina=100&depois_de=02205724000369
GET /api/v1/empresas?uf=RN&cnae=4721102&por_pagina=100&depois_de=08430449000100
… até "proximo": null
total_exato: false quer dizer "pelo menos isso".
A contagem para em 10.000:
contar de verdade um recorte de milhões custa a leitura do índice inteiro
para produzir um número que ninguém pagina até o fim.
Pelo mesmo motivo a paginação por número para em
10.000 resultados
(paginacao_profunda) — e, mesmo antes do teto, página funda custa mais,
porque o banco lê e descarta todas as anteriores. Para varrer, use
depois_de; para chegar a um ponto específico, estreite por
uf e municipio.
A listagem nunca traz sócio, telefone, e-mail ou coordenada, mesmo
que a chave tenha esses escopos. Cem linhas por página com dado pessoal dentro
deixa de ser paginação e vira exportação de base. Para isso, use
/cnpj/{cnpj} um a um.
escopo geo
Os 111 milhões de endereços do CNEFE (IBGE, Censo 2022) que já estão neste banco. Não há chamada a serviço de fora, não há custo por consulta e não há teto diário — o que no Google seria fatura, aqui é índice.
Toda resposta traz precisao, e ela decide o que o número vale:
numero é a porta; logradouro é a rua certa com ponto
aproximado; cep é algum ponto da via. Se você vai medir distância ou
desenhar raio de atendimento, trate os dois últimos como erro de dezenas a centenas de
metros.
/api/v1/geo/cep/{cep}A via daquele CEP. Precisão cep por definição — sem número não há imóvel.
/api/v1/geo/endereco
Parâmetros: cep (obrigatório), numero, logradouro.
Com número tenta a porta; sem achar, cai para a via e diz que caiu.
curl -H "Authorization: Bearer $PJ.BIO_GEO" \
"https://pj.bio/api/v1/geo/endereco?cep=59025580&numero=588"
{
"dados": {
"latitude": -5.78493,
"longitude": -35.20833,
"precisao": "numero",
"fonte": "cnefe",
"endereco": {
"cep": "59025580",
"logradouro": "PRACA ANDRE DE ALBUQUERQUE",
"numero": "588",
"bairro": "CIDADE ALTA",
"municipio_ibge": "2408102",
"uf": "RN"
}
},
"meta": { "fonte": "CNEFE — IBGE, Censo 2022" }
}
No degrau cep o campo numero vem null.
A linha que casou só pelo CEP é de um vizinho qualquer da mesma via: o logradouro e o
bairro valem, o número não. Devolvê-lo seria afirmar um endereço errado.
/api/v1/geo/loteAté 100 endereços numa consulta só — é o caso de uso real: enriquecer um cadastro inteiro de uma vez.
{
"enderecos": [
{ "cep": "59025580", "numero": "588" },
{ "cep": "01310100", "logradouro": "Avenida Paulista" }
]
}
A saída tem a mesma ordem e o mesmo tamanho da entrada, com
null onde não achou. Case por posição. Se devolvesse só os encontrados,
o alinhamento quebraria em silêncio e a coordenada de uma linha viraria a de outra —
erro que não estoura e contamina o cadastro inteiro.
escopo selo (vem por padrão em toda chave nova)
O selo afirma uma coisa só: o dono provou que responde por este CNPJ — a reivindicação com documento e selfie, revisada por uma pessoa. Não é "cadastro confere com a Receita": isso vence a cada competência e não diz nada sobre quem está do outro lado. É o que a sua plataforma exibe ao lado de um cadastro como dono verificado.
A verdade mora aqui, não na sua plataforma. O selo nasce e morre dentro
do PJ.bio: cai quando a reivindicação é revertida, quando a conta do dono é bloqueada
e quando o CNPJ some da Receita. Guarde uma cópia só para renderizar
(pjbio_verificado_em) e renove pelo lote, de madrugada. Não há cache:
o dono aprovado às 15h aparece verificado às 15h01.
/api/v1/selo/{cnpj}{
"dados": {
"cnpj": "61708158000105",
"existe": true,
"verificado": true,
"desde": "2026-09-01T15:03:10-03:00",
"situacao": { "codigo": "02", "descricao": "ATIVA" },
"ficha": "https://pj.bio/padaria-puro-pao-61708158000105"
},
"meta": { "selo": "O dono provou, com documento e selfie revisados por uma pessoa, que responde por este CNPJ." }
}
existe: false não é erro: é o seu cadastro tendo um CNPJ que a Receita não tem. Os demais campos vêm nulos.desde só vem quando o selo vale — uma data em selo caído convidaria a exibi-la.situacao vai por cortesia: o selo é sobre a pessoa e não vence com a Receita, mas "dono verificado" numa empresa baixada é você quem decide se mostra.ficha é a prova pública, para o selo linkar./api/v1/selo/lote
Até 100 CNPJs em {"cnpjs": [...]} — para reconciliar um cadastro inteiro
(134 mil CNPJs são 1.341 chamadas). A saída tem a mesma ordem e o mesmo
tamanho da entrada; um CNPJ malformado derruba o lote inteiro com
422 cnpj_invalido, porque em reconciliação automatizada silenciar uma
posição é o que faz o desalinhamento passar despercebido. meta.pedidos e
meta.verificados resumem o lote.
configurado por plataforma em /painel/plataformas · exige uma chave viva com o escopo selo
O lote de madrugada garante consistência; o webhook garante o "na hora": o dono
aprovado às 15h aparece verificado no SuperTur às 15h01. O aviso é magro
— diz que o selo do CNPJ mudou e por quê, e a sua plataforma re-consulta
/api/v1/selo/{cnpj}. Assim o payload nunca vira verdade, a ordem de entrega
não importa e uma entrega atrasada não sobrescreve uma mais nova.
Dispara em: reivindicação aprovada, dono bloqueado ou desbloqueado, CNPJ que sumiu da Receita na importação ou voltou.
POST {sua URL}
Content-Type: application/json
X-PJ.bio-Signature: sha256=9f2c… ← HMAC-SHA256 do corpo, com o segredo do painel
X-PJ.bio-Evento: selo.mudou
X-PJ.bio-Entrega: 3f8e2c1a-7b4d-4e0f-9a6c-2d5b8e1f0a7c
{"cnpj":"61708158000105","evento":"selo.mudou","id":"3f8e2c1a-7b4d-4e0f-9a6c-2d5b8e1f0a7c","motivo":"aprovacao","ocorrido_em":"2026-09-14T15:03:10-03:00"}
id repetido. Reenvio (automático ou pelo painel) repete o mesmo id, o mesmo corpo e a mesma assinatura.410 Gone diz "esse endereço morreu" e para na hora.// Laravel, na sua plataforma
$corpo = $request->getContent();
$esperada = 'sha256=' . hash_hmac('sha256', $corpo, config('cnpjbio.webhook_segredo'));
abort_unless(hash_equals($esperada, (string) $request->header('X-PJ.bio-Signature')), 401);
$evento = json_decode($corpo, true);
if (Cache::add('cnpjbio:entrega:' . $evento['id'], 1, now()->addDays(7))) {
ReconsultarSelo::dispatch($evento['cnpj']); // GET /api/v1/selo/{cnpj} e grava pjbio_verificado_em
}
return response()->noContent(200);
O botão "Enviar evento de teste" do painel manda um selo.teste
com os mesmos cabeçalhos — é o jeito de conferir URL e segredo antes de qualquer
reivindicação real. As últimas 20 entregas de cada chave ficam visíveis no painel, com
status HTTP, tentativas e a resposta que a sua plataforma devolveu.
/api/v1/statusqualquer chave válida
Diz quem é a chave, o que ela pode e de que competência é a base. Serve para conferir a credencial num deploy sem gastar consulta — e é a primeira coisa a checar quando um campo esperado não veio (quase sempre: escopo que a chave não tem).
{
"dados": {
"chave": {
"nome": "SuperTur — produção",
"prefixo": "pjbio_7f3a91c0",
"escopos": ["cnpj", "busca"],
"limite_por_minuto": 60
},
"base": { "fonte": "Receita Federal", "competencia": "2026-08-01" }
}
}
| HTTP | Código | O que fazer |
|---|---|---|
| 401 | sem_credencial | Faltou o cabeçalho. Não repita a chamada. |
| 401 | credencial_invalida | Chave errada ou truncada. Não repita. |
| 403 | credencial_revogada | A chave foi desligada. Pare de tentar e peça outra. |
| 403 | plataforma_desativada | A empresa dona da chave foi desativada — todas as chaves dela param juntas. Não adianta trocar de chave; fale com quem opera o PJ.bio. |
| 403 | escopo_ausente | A chave não tem o escopo da rota. Peça a ampliação. |
| 404 | nao_encontrado | CNPJ não existe na competência importada. |
| 422 | cnpj_invalido | Não são 14 dígitos. |
| 422 | cep_invalido | Não são 8 dígitos. |
| 422 | parametros_invalidos | Confira a tabela de parâmetros. |
| 422 | municipio_desconhecido | Use o código RFB de 4 dígitos ou o slug de /cidade. |
| 422 | paginacao_profunda | Estreite o filtro; a paginação não vai tão fundo. |
| 429 | limite_excedido | Respeite o Retry-After do cabeçalho. |
| 500 | erro_interno | Registrado do nosso lado. Repita com recuo exponencial. |
O teto é por chave, não por IP: as ferramentas rodam em servidor e várias podem sair do mesmo endereço — limitar por IP puniria a chave comportada por causa da vizinha. Toda resposta traz o estado atual:
X-RateLimit-Limit: 60
X-RateLimit-Remaining: 57
No 429 vem também Retry-After, em segundos. Respeitá-lo é mais rápido
que insistir: cada tentativa recusada consome a mesma janela.
localizacao.precisao — leia antes de plotarA coordenada vem do cruzamento com o CNEFE do IBGE (Censo 2022), sem chamada a serviço externo. Ela nunca vem sozinha:
numero — casou CEP e número: é a porta do imóvel.logradouro — casou CEP e nome da rua: rua certa, ponto aproximado.cep — só o CEP: algum ponto da via, não do imóvel.
Se você vai medir distância ou desenhar raio de atendimento, trate
cep e logradouro como erro de dezenas a centenas de metros.
Ignorar esse campo é o jeito mais fácil de produzir um relatório errado com
aparência de exato.
municipio_codigo_rfb tem 4 dígitos e é o do cadastro fiscal.
municipio_codigo_ibge tem 7 e é o que cruza com censo, malha territorial
e qualquer base pública de fora. São números diferentes para o mesmo município:
trocar um pelo outro não dá erro, dá resultado errado calado.
capital_social é string
Decimal com duas casas, em texto ("50000.00"). Em ponto flutuante o
valor perde centavo na ida e na volta — e quem consome capital social costuma
estar somando.
YYYY-MM-DD nos campos de data; ISO 8601 com fuso
(-03:00) nos de instante.
Com o escopo socios, a ficha traz socios (o quadro
inteiro, até 1.000 — o site mostra 30) e socios_total. Cada sócio vem com
nome, documento, qualificacao,
data_entrada, tipo (fisica,
juridica ou estrangeiro), faixa_etaria,
pais e, quando existe, representante_legal
(quem assina pelo sócio PJ, menor ou incapaz).
MEI, empresário individual e produtor rural não têm sócio — são 45
milhões de CNPJs, e a Receita não publica linha nenhuma para eles no arquivo de
sócios. Nesses casos socios vem vazio e responsavel traz o
titular (nome, papel, natureza), extraído da razão social.
documento é o que a Receita publica: CPF mascarado
(***038787**, só os seis dígitos do meio) para pessoa física, e só a
raiz do CNPJ (8 dígitos) para pessoa jurídica. Não dá para
reconstruir o CPF a partir daqui — e não é para dar.
Nome de sócio é dado pessoal. Quem pede remoção pelo portal de privacidade sai da base na importação seguinte — e some da API junto, porque a API lê a mesma tabela que o site. Se você copiar sócios para o seu banco, reconcilie: cópia que ninguém atualiza transforma um pedido atendido aqui em dado vivo aí.
Fale com quem administra o PJ.bio. Diga qual ferramenta vai consumir, com que frequência, e se precisa de sócios ou contato — os dois escopos sensíveis só são concedidos quando há motivo declarado.