---
title: "Erros, Retry-After e retentativas"
description: "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."
canonical: "https://cnpj.ia.br/docs/erros-e-retentativas"
date_modified: 2026-09-02
source: "cnpj.ia.br"
---

docs · base agosto/2026 Atualizado em 02/09/2026

# 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](https://cnpj.ia.br/openapi/openapi.json); um código novo só entra com aviso no [changelog](https://cnpj.ia.br/docs/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/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](https://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

```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

```javascript
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

- [Autenticação](https://cnpj.ia.br/docs/autenticacao): os erros `401` e `403` em detalhe.
- [Créditos, franquia e rate limit](https://cnpj.ia.br/docs/creditos-e-limites): `402`, `429` e os headers de saldo.
- [Dados e frescor](https://cnpj.ia.br/docs/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`.
