docs · base agosto/2026Atualizado em

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ódigoHTTPRetentávelQuando aconteceO que fazerOperaçõ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/generate
GET /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/generate
GET /v1/cnpjs
GET /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/generate
GET /v1/cnpjs
GET /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/generate
GET /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/generate
GET /v1/cnpjs
GET /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/demo
GET /openapi.json
GET /robots.txt
GET /llms.txt
GET /.well-known/api-catalog
POST /v1/filters/generate
GET /v1/cnpjs
GET /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/demo
POST /v1/filters/generate
GET /v1/cnpjs
GET /v1/status
GET /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/demo
POST /v1/filters/generate
GET /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/demo
POST /v1/filters/generate
GET /v1/cnpjs
GET /v1/status
GET /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/demo
GET /openapi.json
GET /robots.txt
GET /llms.txt
GET /.well-known/api-catalog
POST /v1/filters/generate
GET /v1/cnpjs
GET /v1/status
GET /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-After diz 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-After diz quando voltar. A janela é mensal e anunciada em cnpjia.statuspage.io; GET /v1/usage també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 o request_id e 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_found como 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 gera 404: a resposta é 200 com meta.suppressed: true.
  • Guarde o request_id nos seus logs. É o que permite ao suporte achar a chamada.
  • Leia X-RateLimit-Remaining para desacelerar antes do 429.
  • Em lotes grandes, um 402 quota_exceeded no meio do processo é previsível: consulte GET /v1/usage antes de começar e compare com o custo estimado.

Relacionados

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.