Erros, Retry-After e retentativas
O envelope de erro da API, os catorze códigos estáveis, qual status HTTP cada um usa, quais podem ser repetidos e como respeitar Retry-After. Nenhum erro consome crédito.
Todo erro da API vem no mesmo envelope: um código estável em error.code, uma mensagem em português, o request_id para citar ao suporte, retryable dizendo se vale repetir, e docs_url apontando para a seção desta página. O status HTTP diz a categoria; o código diz a causa. Nenhum erro consome crédito: só respostas 200 são cobradas.
O envelope
Campos de error, como o contrato os declara:
| Campo | Tipo | Conteúdo |
|---|---|---|
code |
string, enum | Um dos códigos estáveis abaixo |
message |
string | Explicação em português, para pessoas |
request_id |
string | Identificador da chamada, o mesmo do header X-Request-Id |
retryable |
boolean | Se repetir a mesma chamada pode dar certo |
docs_url |
string (uri) | Link para a seção deste código nesta página |
Programe contra error.code, não contra message: o texto pode mudar, o código não. Os códigos são um enum fechado do contrato; um código novo só entra com aviso no changelog.
Os códigos
| Código | HTTP | Retentável | Quando acontece | O que fazer | Operações |
|---|---|---|---|---|---|
invalid_cnpj |
400 |
não | O CNPJ enviado não tem formato válido ou o dígito verificador não confere. | Corrija o CNPJ antes de repetir: a mesma entrada devolve sempre a mesma resposta. | GET /v1/cnpjs/{cnpj} |
invalid_filter |
400 |
não | A chamada trouxe um filtro desconhecido, um valor fora do enum do contrato, um cursor inválido, um profile diferente de basic e full, uma busca sem nenhum filtro além de cursor, ou uma descricao ausente, vazia ou com mais de 500 caracteres no gerador de filtros. |
Confira o nome e o valor do filtro na referência da operação e reenvie. | GET /v1/cnpjs/{cnpj}POST /v1/filters/generateGET /v1/cnpjs |
invalid_api_key |
401 |
não | A chave não foi enviada, não é conhecida ou foi revogada. | Confira o header Authorization: Bearer e use uma chave ativa da conta. |
GET /v1/cnpjs/{cnpj}POST /v1/filters/generateGET /v1/cnpjsGET /v1/usage |
key_expired |
401 |
não | A chave enviada passou da data de validade. | Crie uma chave nova no portal e troque no cliente. | GET /v1/cnpjs/{cnpj}POST /v1/filters/generateGET /v1/cnpjsGET /v1/usage |
quota_exceeded |
402 |
não | A franquia do mês e os pacotes da conta acabaram. | Aguarde a data do header X-Credits-Reset, troque de plano ou compre um pacote. |
GET /v1/cnpjs/{cnpj}POST /v1/filters/generateGET /v1/cnpjs |
insufficient_plan |
403 |
não | A operação não faz parte do plano da conta: busca em plano sem busca, ou busca ainda fechada na liberação gradual (mesmo código, mensagem distinta). | Fora do plano: troque de plano, repetir a chamada não muda a resposta. Busca fechada na liberação gradual: aguarde a abertura, anunciada no changelog. | GET /v1/cnpjs |
filter_not_in_plan |
403 |
não | A busca recebeu um filtro de nível acima do plano da conta. A mensagem nomeia o primeiro filtro recusado e o plano mínimo que libera todos os recusados. Nada é consultado nem cobrado. | Retire o filtro ou troque para o plano citado na mensagem; repetir a chamada não muda a resposta. | GET /v1/cnpjs |
payment_required |
403 |
não | A conta está com pagamento em aberto há mais de sete dias. | Regularize a cobrança no portal. | GET /v1/cnpjs/{cnpj}POST /v1/filters/generateGET /v1/cnpjsGET /v1/usage |
account_suspended |
403 |
não | A conta foi suspensa pelo suporte, por motivo que não é pagamento. Vale para toda chamada com chave, inclusive GET /v1/usage, e vem antes de payment_required. Nada é consultado nem cobrado. |
Fale com o suporte; repetir a chamada não muda a resposta. | — |
not_found |
404 |
não | O CNPJ é válido, mas não está na base servida; ou o CNPJ está fora da lista da demonstração, o caminho não existe, ou a API está desligada. | CNPJ ausente: não repita, a resposta só muda com a próxima base. Demonstração: use um CNPJ da lista. Caminho inexistente: confira a URL. API desligada: acompanhe a página de status. Pedido de remoção do titular não gera 404: a resposta é 200 com meta.suppressed: true e os campos de contato omitidos. |
GET /v1/cnpjs/{cnpj}GET /v1/demoGET /openapi.jsonGET /robots.txtGET /llms.txtGET /.well-known/api-catalogPOST /v1/filters/generateGET /v1/cnpjsGET /v1/usage |
rate_limited |
429 |
sim | Passou do limite de requisições por minuto da conta (todas as chaves somam no mesmo contador), do limite por endereço IP dos endpoints sem chave, ou do teto diário do gerador de filtros. | Espere o intervalo do header Retry-After e repita com backoff. |
GET /v1/cnpjs/{cnpj}GET /v1/demoPOST /v1/filters/generateGET /v1/cnpjsGET /v1/statusGET /v1/usage |
maintenance |
503 |
sim | A base está na janela mensal de atualização. | Espere o intervalo do header Retry-After e repita. |
GET /v1/cnpjs/{cnpj}GET /v1/demoPOST /v1/filters/generateGET /v1/cnpjs |
upstream_timeout |
504 |
sim | A consulta ao banco ou ao modelo de IA estourou o tempo limite. | Repita com backoff; nada é cobrado. | GET /v1/cnpjs/{cnpj}GET /v1/demoPOST /v1/filters/generateGET /v1/cnpjsGET /v1/statusGET /v1/usage |
internal_error |
500 |
sim | Falha não prevista no servidor. Nada é cobrado; se houve débito, o estorno é tentado. | Repita com recuo exponencial. Se persistir, guarde o request_id e acione o suporte. |
GET /v1/cnpjs/{cnpj}GET /v1/demoGET /openapi.jsonGET /robots.txtGET /llms.txtGET /.well-known/api-catalogPOST /v1/filters/generateGET /v1/cnpjsGET /v1/statusGET /v1/usage |
Quando repetir
Repita só quando retryable for true, e sempre respeitando Retry-After quando ele vier:
429 rate_limited: você passou do limite de requisições por minuto da conta, do limite por IP dos endpoints sem chave, ou do teto diário do gerador de filtros.Retry-Afterdiz quantos segundos esperar. Reduza a concorrência em vez de só esperar; o limite da conta soma todas as chaves.503 maintenance: a base está sendo atualizada.Retry-Afterdiz quando voltar. A janela é mensal e anunciada em cnpjia.statuspage.io;GET /v1/usagetambém fica indisponível nela.504 upstream_timeout: a consulta ao banco ou ao modelo de IA estourou o tempo limite. Nada foi cobrado; repita com recuo exponencial.500 internal_error: falha nossa. Nada é cobrado e, se houve débito, o estorno é tentado. Repita com recuo exponencial; se persistir, guarde orequest_ide cite ao suporte.
Não repita erros 4xx de validação, autenticação ou cobrança: a mesma chamada vai falhar de novo. Corrija o CNPJ, a chave ou o plano e chame outra vez.
Python
import os, time, requests
HEADERS = {"Authorization": f"Bearer {os.environ['CNPJIA_KEY']}"}
def get(url, params=None, tentativas=4):
for i in range(tentativas):
r = requests.get(url, params=params, headers=HEADERS, timeout=10)
if r.status_code == 200:
return r.json()
erro = r.json().get("error", {})
if not erro.get("retryable"):
raise RuntimeError(f"{r.status_code} {erro.get('code')}: {erro.get('message')} ({erro.get('request_id')})")
espera = int(r.headers.get("Retry-After", 2 ** i))
time.sleep(espera)
raise RuntimeError("desistiu após retentativas")
Node
const headers = { Authorization: `Bearer ${process.env.CNPJIA_KEY}` };
async function get(url, tentativas = 4) {
for (let i = 0; i < tentativas; i++) {
const res = await fetch(url, { headers });
if (res.ok) return res.json();
const { error } = await res.json();
if (!error?.retryable) throw new Error(`${res.status} ${error?.code}: ${error?.message} (${error?.request_id})`);
const espera = Number(res.headers.get("Retry-After") ?? 2 ** i);
await new Promise((r) => setTimeout(r, espera * 1000));
}
throw new Error("desistiu após retentativas");
}
Headers em toda resposta
| Header | Quando vem |
|---|---|
X-Request-Id |
Sempre, inclusive antes da autenticação; é o mesmo valor de error.request_id |
X-Client-Request-Id |
Eco do X-Request-Id que você enviou, quando ele tem 8 a 64 caracteres de A-Z a-z 0-9 _ -; ausente se não enviou ou o valor é inválido |
X-RateLimit-Limit, X-RateLimit-Remaining |
Nas respostas que passaram pelo controle de requisições por conta, inclusive 4xx posteriores; ausentes nas falhas anteriores à autenticação e no 429 que o limite por IP devolve a uma chamada sem chave às operações autenticadas. Em GET /v1/status e GET /v1/demo eles refletem o limite por IP, inclusive no 429. |
X-Credits-Charged, X-Credits-Remaining, X-Credits-Reset |
Nas respostas que passaram pela verificação de saldo, mesmo sem débito: 200, página vazia e 5xx após estorno, com 0 |
Retry-After |
Em 429 e 503: segundos até tentar de novo |
Access-Control-Allow-Origin |
Só em GET /v1/status e GET /v1/demo, quando a resposta é produzida pela própria operação |
Boas práticas
- Trate
not_foundcomo resultado, não como falha: CNPJ com formato válido que não existe na base é uma resposta legítima. Pedido de remoção do titular não gera404: a resposta é200commeta.suppressed: true. - Guarde o
request_idnos seus logs. É o que permite ao suporte achar a chamada. - Leia
X-RateLimit-Remainingpara desacelerar antes do429. - Em lotes grandes, um
402 quota_exceededno meio do processo é previsível: consulteGET /v1/usageantes de começar e compare com o custo estimado.
Relacionados
- Autenticação: os erros
401e403em detalhe. - Créditos, franquia e rate limit:
402,429e os headers de saldo. - Dados e frescor: o que acontece na janela de manutenção.
Perguntas frequentes
Um erro consome crédito?
Não. Só respostas 200 são cobradas. Se uma chamada foi debitada e depois falhou por tempo limite, o crédito é estornado e o saldo aparece correto em GET /v1/usage.
Devo programar contra o status HTTP ou contra `error.code`?
Contra error.code. O status dá a categoria e serve para clientes HTTP genéricos; o código é estável, específico e é o que diferencia, por exemplo, invalid_api_key de key_expired dentro do mesmo 401.
O que fazer com `retryable: true`?
Esperar o tempo de Retry-After, quando o header vier, ou aplicar recuo exponencial, e repetir a mesma chamada. Vale para 429, 503 e 504. Erros de validação, autenticação e cobrança não são repetíveis: a chamada vai falhar igual até você corrigir a causa.
A lista de códigos pode crescer?
Sim, mas só com aviso prévio no changelog, e um código novo nunca muda o significado dos existentes. Trate código desconhecido como erro genérico e registre o request_id.