docs · base agosto/2026Atualizado em

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ó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 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/cnpjs
POST /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/cnpjs
POST /v1/filters/generate
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}
GET /v1/cnpjs
POST /v1/filters/generate
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}
GET /v1/cnpjs
POST /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/cnpjs
POST /v1/filters/generate
GET /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 declara retryable, e nenhuma operação do openapi.json declara a resposta 500. O valor de retryable fica 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-After diz 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-After diz 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 seu retryable; registre 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");
}

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.
  • 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.