Erros, Retry-After e retentativas
O envelope de erro da API, os doze 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 ou um valor fora do enum do contrato. | Confira o nome e o valor do filtro na referência da operação e reenvie. | GET /v1/cnpjsPOST /v1/filters/generate |
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}GET /v1/cnpjsPOST /v1/filters/generateGET /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}GET /v1/cnpjsPOST /v1/filters/generateGET /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}GET /v1/cnpjsPOST /v1/filters/generate |
insufficient_plan |
403 |
não | A operação não faz parte do plano da conta. | Troque de plano: repetir a chamada não muda a resposta. | GET /v1/cnpjs/{cnpj}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}GET /v1/cnpjs |
not_found |
404 |
não | O CNPJ é válido, mas não está na base servida, ou foi removido a pedido do titular. | Não repita: a resposta só muda com a próxima base ou com o fim da supressão. | GET /v1/cnpjs/{cnpj}GET /v1/demo |
rate_limited |
429 |
sim | A conta passou do limite de requisições por minuto; todas as chaves da conta somam no mesmo contador. | Espere o intervalo do header Retry-After e repita com backoff. |
GET /v1/cnpjs/{cnpj}GET /v1/cnpjsPOST /v1/filters/generateGET /v1/demo |
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/cnpjs |
upstream_timeout |
504 |
sim | A consulta ao armazenamento estourou o tempo limite. | Repita com backoff; nada é cobrado. | GET /v1/cnpjs/{cnpj}GET /v1/cnpjs |
internal_error |
500 |
a confirmar | Falha não prevista no servidor. | Guarde o request_id e acione o suporte; o contrato ainda não declara se a chamada é retentável. |
— |
- A confirmar —
internal_error: O doc 12 §2.3 fixa o status 500 e deixa a coluna ‘quando’ vazia; não declararetryable, e nenhuma operação do openapi.json declara a resposta 500. O valor deretryablefica em aberto até o CTO ratificar.
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.Retry-Afterdiz quantos segundos esperar. Reduza a concorrência em vez de só esperar; o limite é por conta e todas as chaves somam.503 maintenance: a base está sendo atualizada.Retry-Afterdiz quando voltar. A janela é mensal e anunciada em status.cnpj.ia.br.504 upstream_timeout: a consulta estourou o tempo limite. Nada foi cobrado; repita com recuo exponencial.500 internal_error: falha nossa. O contrato ainda não declara essa resposta nem o seuretryable; registre 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");
}
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. - 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.