Início rápido da API de consulta de CNPJ
Da chave de teste ao primeiro resultado: autenticação, criação da consulta, webhook assinado, sandbox, erros e idempotência, com exemplos em cURL e Python.
Visão geral
O ProCNPJ é uma API REST. Você envia o CNPJ, numérico ou alfanumérico, e recebe o desfecho da consulta e, quando o CNPJ é encontrado, razão social, nome fantasia, situação cadastral, datas, natureza jurídica, atividades econômicas (CNAE), capital social e endereço.
A consulta é assíncrona: a criação responde na hora com a consulta na fila, e o desfecho chega por webhook ou por consulta pelo id.
https://api.procnpj.com.br/v1
| Endpoint | Para que serve |
|---|---|
POST/v1/consultations | Cria uma consulta e a coloca na fila. |
GET/v1/consultations/{id} | Devolve o status e o resultado de uma consulta. |
GET/v1/consultations | Lista as consultas da conta. |
GET/v1/usage | Mostra o consumo e a cota do mês. |
POST/v1/webhooks | Cadastra um destino de webhook. |
GET/v1/webhooks | Lista os destinos cadastrados. |
POST/v1/webhooks/deliveries/{id}/resend | Reenvia uma entrega de webhook. |
Autenticação
Toda chamada leva a chave de API no cabeçalho Authorization. O prefixo da chave define o ambiente:
pcnpj_test_...: sandbox. Usa um simulador, não custa nada e não consome cota.pcnpj_live_...: produção. Consulta de verdade, conta na cota.
Authorization: Bearer pcnpj_test_...
As chaves são criadas no console. O valor completo aparece uma única vez, na criação: o ProCNPJ guarda só o hash. Se a chave vazar, revogue no console e gere outra. Use a chave apenas no seu servidor, nunca no navegador ou no aplicativo.
Criar uma consulta
POST/v1/consultations
- cnpj
- Obrigatório. CNPJ numérico ou alfanumérico, com ou sem máscara. Os dígitos verificadores são validados antes de a consulta entrar na fila, pela regra que cobre os dois formatos.
- external_reference
- Opcional. Um identificador seu (até 120 caracteres), que volta na resposta e no webhook.
- Idempotency-Key
- Cabeçalho opcional. Veja Idempotência.
cURL
curl -X POST https://api.procnpj.com.br/v1/consultations \
-H "Authorization: Bearer pcnpj_test_..." \
-H "Content-Type: application/json" \
-H "Idempotency-Key: fornecedor-8841" \
-d '{"cnpj": "11.222.333/0006-96",
"external_reference": "fornecedor-8841"}'
Python
import requests
API = "https://api.procnpj.com.br/v1"
HEADERS = {"Authorization": "Bearer pcnpj_test_..."}
response = requests.post(
f"{API}/consultations",
headers={**HEADERS, "Idempotency-Key": "fornecedor-8841"},
json={"cnpj": "11.222.333/0006-96", "external_reference": "fornecedor-8841"},
timeout=10,
)
response.raise_for_status()
consultation = response.json()
print(consultation["id"], consultation["status"]) # status: pending
Resposta
A API responde 202 Accepted com a consulta na fila. Guarde o id.
{
"id": "3f0e6a52-9c1b-4d7e-8a35-2b6f1c9d4e70",
"status": "pending",
"outcome": null,
"cnpj": "11.222.333/0006-96",
"external_reference": "fornecedor-8841",
"sandbox": true,
"result": null,
"error": null,
"personal_data_purged": false,
"created_at": "2026-09-30T17:03:11.482913+00:00",
"finished_at": null
}
Buscar o resultado
GET/v1/consultations/{id}
Enquanto a consulta está em andamento, o status é pending ou processing. Ela termina em done (concluída) ou failed (falhou). Você pode esperar o webhook ou consultar o id de tempos em tempos.
cURL
ID=3f0e6a52-9c1b-4d7e-8a35-2b6f1c9d4e70
curl https://api.procnpj.com.br/v1/consultations/$ID \
-H "Authorization: Bearer pcnpj_test_..."
Python
import time
def wait_for_result(consultation_id, attempts=20, interval=2):
"""Consulta o id até a consulta sair de pending ou processing."""
for _ in range(attempts):
response = requests.get(
f"{API}/consultations/{consultation_id}", headers=HEADERS, timeout=10
)
response.raise_for_status()
consultation = response.json()
if consultation["status"] in ("done", "failed"):
return consultation
time.sleep(interval)
raise TimeoutError("Consulta ainda em andamento")
consultation = wait_for_result(consultation["id"])
if consultation["status"] == "failed":
print("Falhou:", consultation["error"]["code"]) # não foi cobrada
elif consultation["outcome"] == "found":
result = consultation["result"]
print(result["legal_name"], result["registration_status"])
else:
print("Desfecho:", consultation["outcome"]) # not_found
Resposta de uma consulta concluída
{
"id": "3f0e6a52-9c1b-4d7e-8a35-2b6f1c9d4e70",
"status": "done",
"outcome": "found",
"cnpj": "11.222.333/0006-96",
"external_reference": "fornecedor-8841",
"sandbox": true,
"result": {
"legal_name": "EMPRESA DE EXEMPLO LTDA",
"trade_name": "EXEMPLO",
"registration_status": "ativa",
"registration_status_label": "Ativa",
"registration_status_date": "2010-05-03",
"opening_date": "2010-05-03",
"head_office": true,
"legal_nature": "206-2 - Sociedade Empresária Limitada",
"main_activity": "62.01-5-01 - Desenvolvimento de programas de computador sob encomenda",
"secondary_activities": [
"62.02-3-00 - Desenvolvimento e licenciamento de programas customizáveis"
],
"share_capital": "100000.00",
"address": {
"street": "AVENIDA DE EXEMPLO",
"number": "1000",
"complement": "SALA 1",
"district": "CENTRO",
"city": "SAO PAULO",
"state": "SP",
"zip_code": "01000-000"
}
},
"error": null,
"personal_data_purged": false,
"created_at": "2026-09-30T17:03:11.482913+00:00",
"finished_at": "2026-09-30T17:03:15.127406+00:00"
}
Campos da consulta
- id
- Identificador da consulta (UUID).
- status
- pending (na fila), processing (consultando), done (concluída) ou failed (falhou).
- outcome
- Desfecho da consulta concluída: found ou not_found.
- cnpj
- CNPJ consultado, formatado como 00.000.000/0000-00.
- external_reference
- A referência que você enviou na criação.
- sandbox
- true quando a consulta foi criada com chave de teste.
- result
- Dados cadastrais da empresa. Só vem preenchido quando o desfecho é found.
- error
- Código e mensagem da falha. Só vem preenchido quando o status é failed.
- personal_data_purged
- true depois que os dados da consulta foram apagados pelo prazo de retenção. A partir daí, result volta nulo.
- created_at
- Data e hora da criação.
- finished_at
- Data e hora do desfecho.
Desfechos
- found
- CNPJ encontrado. O CNPJ existe no cadastro. O resultado vem completo, com a situação cadastral.
- not_found
- CNPJ não encontrado. O número passa na conta dos dígitos verificadores, mas não existe no cadastro.
Campos de result
- legal_name
- Razão social.
- trade_name
- Nome fantasia, quando houver.
- registration_status
- Situação cadastral em código: ativa, baixada, suspensa, inapta, nula ou desconhecida.
- registration_status_label
- Situação cadastral por extenso, pronta para exibir.
- registration_status_date
- Data da situação cadastral, em AAAA-MM-DD.
- opening_date
- Data de abertura, em AAAA-MM-DD.
- head_office
- true para matriz, false para filial e null quando a fonte não informa.
- legal_nature
- Natureza jurídica.
- main_activity
- Atividade econômica principal (CNAE), com código e descrição.
- secondary_activities
- Lista das atividades econômicas secundárias (CNAE).
- share_capital
- Capital social, em texto.
- address
- Endereço do estabelecimento: street, number, complement, district, city, state e zip_code.
Quando a consulta falha
Se a primeira fonte não responde, o ProCNPJ tenta a próxima sozinho. A consulta só termina como failed quando nenhuma fonte respondeu ou quando a fonte recusou os dados. Nos dois casos ela não é cobrada e não consome cota. O campo error traz o motivo:
- ENGINES_UNAVAILABLE
- Nenhuma fonte respondeu. A consulta não é cobrada nem consome cota. Tente de novo.
- INVALID_INPUT
- A fonte recusou os dados informados. A consulta não é cobrada nem consome cota.
Webhooks
Com um destino cadastrado, o ProCNPJ avisa o seu servidor quando a consulta termina. São dois eventos: consultation.completed (consulta concluída) e consultation.failed (consulta que falhou).
O webhook não leva dado cadastral. Ele leva o id e o status, e o seu servidor busca o resultado por GET /v1/consultations/{id}, autenticado. Assim, os dados de um CNPJ nunca ficam em log de requisição recebida.
Cadastrar um destino
Pelo console ou pela API. O destino precisa ser um endereço público: endereços internos são recusados. O segredo de assinatura aparece uma única vez, na resposta da criação.
curl -X POST https://api.procnpj.com.br/v1/webhooks \
-H "Authorization: Bearer pcnpj_test_..." \
-H "Content-Type: application/json" \
-d '{"url": "https://seusite.com.br/webhooks/procnpj",
"description": "Cadastro de fornecedores"}'
# 201 Created: guarde o campo "secret", ele não volta a ser exibido
O que chega no seu servidor
Content-Type: application/json
X-ProCNPJ-Signature: sha256=9c1f...
X-ProCNPJ-Timestamp: 1790787795
{
"event": "consultation.completed",
"id": "3f0e6a52-9c1b-4d7e-8a35-2b6f1c9d4e70",
"status": "done",
"outcome": "found",
"external_reference": "fornecedor-8841",
"sandbox": true,
"created_at": "2026-09-30T17:03:11.482913+00:00",
"finished_at": "2026-09-30T17:03:15.127406+00:00"
}
Verificar a assinatura
A assinatura é um HMAC-SHA256, em hexadecimal, do texto {timestamp}.{corpo}, calculado com o segredo do destino. Ela chega em X-ProCNPJ-Signature com o prefixo sha256=, e o carimbo de tempo (segundos desde 1970) chega em X-ProCNPJ-Timestamp. Calcule sobre o corpo bruto da requisição, antes de interpretar o JSON, e compare em tempo constante.
import hashlib
import hmac
import time
WEBHOOK_SECRET = "segredo-exibido-na-criacao"
TOLERANCE_SECONDS = 300 # exemplo: recuse avisos antigos, contra repetição
def is_valid_signature(raw_body: bytes, signature: str, timestamp: str) -> bool:
"""Confere a assinatura do ProCNPJ sobre "{timestamp}.{corpo}"."""
if not signature or not timestamp.isdigit():
return False
if abs(time.time() - int(timestamp)) > TOLERANCE_SECONDS:
return False
signed_payload = timestamp.encode() + b"." + raw_body
expected = hmac.new(WEBHOOK_SECRET.encode(), signed_payload, hashlib.sha256).hexdigest()
return hmac.compare_digest(f"sha256={expected}", signature)
# No seu endpoint: leia os cabeçalhos X-ProCNPJ-Signature e X-ProCNPJ-Timestamp,
# valide com o corpo bruto e só então busque o resultado pelo id.
Entrega e novas tentativas
- Qualquer resposta 2xx do seu servidor confirma a entrega. Responda rápido e processe depois.
- Resposta fora de 2xx, erro de rede ou demora contam como falha: o ProCNPJ tenta de novo, com intervalo crescente, até um limite de tentativas.
- Redirecionamentos não são seguidos. Cadastre a URL final.
- Esgotadas as tentativas, a entrega fica marcada como falha e pode ser reenviada pelo console ou por
POST /v1/webhooks/deliveries/{id}/resend. - Trate o aviso de forma idempotente, pela dupla
ideevent: uma nova tentativa pode chegar depois de o seu servidor já ter processado a anterior.
Sandbox
Toda chamada feita com uma chave pcnpj_test_ roda no sandbox: mesmo fluxo da produção (fila, webhook, resultado), com um simulador no lugar da fonte. É grátis e não consome cota. O décimo segundo caractere do CNPJ, o último antes dos dígitos verificadores, escolhe o cenário. Vale para o CNPJ numérico e para o alfanumérico: uma letra nessa posição cai no cenário de situação ativa.
| 12º caractere | Cenário simulado | CNPJ de exemplo |
|---|---|---|
| 0 | CNPJ não encontrado | 11.222.333/0000-09 |
| 2 | Situação baixada | 11.222.333/0002-62 |
| 3 | Situação suspensa | 11.222.333/0003-43 |
| 4 | Situação inapta | 11.222.333/0004-24 |
| 5 | Situação nula | 11.222.333/0005-05 |
| 9 | Falha simulada (a consulta termina como failed) | 11.222.333/0009-39 |
| Qualquer outro | Situação ativa | 11.222.333/0006-96 |
Os CNPJs de exemplo são sintéticos, com dígitos verificadores válidos. Para o formato alfanumérico, use 12.ABC.345/01DE-35, que volta como situação ativa. No sandbox, a razão social devolvida é sempre EMPRESA DE EXEMPLO LTDA, com endereço e atividades fictícios.
Erros
Use o status HTTP como primeira informação. Todo erro da API segue um formato único, com um code estável para o seu sistema e uma message para leitura humana. Quando há contexto, ele vem em details.
{
"error": {
"code": "QUOTA_EXCEEDED",
"message": "Cota mensal de consultas esgotada.",
"details": {"monthly_limit": 100, "period": "2026-09"}
}
}
| Status | Código | Quando acontece | O que fazer |
|---|---|---|---|
| 400 | INVALID_REQUEST | Dados inválidos: CNPJ com dígitos verificadores errados ou campo obrigatório ausente. | Corrija o campo apontado em details. |
| 401 | UNAUTHENTICATED | Chave de API ausente, inválida ou revogada. | Confira o cabeçalho Authorization e o ambiente da chave. |
| 402 | TRIAL_EXPIRED | Período de teste encerrado e conta sem plano. | Fale com a equipe para seguir em produção. O sandbox continua disponível. |
| 403 | REGISTRATION_INCOMPLETE | Cadastro incompleto: falta aceitar os termos de uso no console. | Um administrador da conta conclui o cadastro no console. |
| 403 | FORBIDDEN | Chave somente leitura tentando criar consulta ou webhook. | Use uma chave com permissão de escrita. |
| 429 | QUOTA_EXCEEDED | Cota mensal de consultas esgotada. | Espere a virada do mês (cabeçalho Retry-After) ou peça aumento de cota. |
| 429 | RATE_LIMITED | Limite de requisições por minuto da chave excedido. | Espere o tempo do cabeçalho Retry-After e tente de novo. |
O período de teste dura 14 dias e a cota padrão é de 100 consultas por mês. Os bloqueios 402 e 429 acontecem antes de a consulta ser criada: nada é cobrado. Em 400, details aponta o campo com problema; em 401, message traz o motivo da recusa.
Idempotência
Redes falham, e repetir um POST sem cuidado criaria duas consultas. Para evitar isso, envie o cabeçalho Idempotency-Key com um valor único por operação (até 80 caracteres), como o id do seu cadastro.
- Primeira chamada com a chave: cria a consulta e responde 202.
- Chamada repetida com a mesma chave: devolve a consulta que já existe, com status 200, sem novo custo e sem consumir cota de novo.
- A chave vale por conta: a mesma chave em outra conta não conflita.
Listagem e consumo
GET/v1/consultations lista as consultas da conta, da mais recente para a mais antiga, em páginas (count, next, previous e results). Filtros: status e external_reference.
curl "https://api.procnpj.com.br/v1/consultations?status=done&external_reference=fornecedor-8841" \
-H "Authorization: Bearer pcnpj_test_..."
GET/v1/usage devolve o consumo da conta: o período atual (period), as consultas usadas (used_in_period), a cota (monthly_limit), o fim do período de teste (trial_ends_at) e o histórico por mês, por status e por desfecho. Só a consulta concluída conta como uso: sandbox e consulta que falhou ficam de fora.
Referência OpenAPI
A referência completa da v1 é gerada do próprio código e fica sempre atualizada:
- Referência interativa: todos os endpoints, parâmetros e respostas.
- Esquema OpenAPI: a especificação para leitura por máquina e geração de cliente.
Dúvida de integração? Escreva para [email protected] com o id da consulta.