> ## Documentation Index
> Fetch the complete documentation index at: https://docs.consentfly.com/llms.txt
> Use this file to discover all available pages before exploring further.

# Erros da API

> Formato padrão de erro, códigos HTTP, CSRF, rate limits e limites de corpo

Todos os endpoints da API retornam erros no mesmo formato JSON. Esta página documenta o formato, os códigos usados, a proteção CSRF e os headers de cota.

## Formato padrão

Toda resposta de erro segue o schema **APIErrorResponse** — dois campos, sempre presentes:

```json theme={null}
{
  "error": "quota_exceeded",
  "message": "You have reached your API request limit for this billing period. Upgrade to continue."
}
```

* **`error`** — código estável, feito para tratamento programático. Ramifique a sua lógica por ele, nunca pelo texto.
* **`message`** — descrição legível, sujeita a mudança sem aviso.

Em erros `5xx` a mensagem é **sempre genérica** ("An internal error occurred. Please try again later.") — detalhes internos nunca vazam no corpo da resposta.

## Códigos de erro

| `error`               | Significado                                                               |
| --------------------- | ------------------------------------------------------------------------- |
| `invalid_request`     | Corpo, parâmetro ou query inválidos — inclui corpo grande demais (`413`). |
| `unauthorized`        | Sessão/API Key/token de site ausente, inválido ou revogado.               |
| `forbidden`           | Autenticado, mas sem permissão — inclui falha de CSRF e conta inativa.    |
| `email_not_verified`  | O e-mail da conta ainda não foi confirmado.                               |
| `plan_required`       | Assinatura inexistente, cancelada ou expirada.                            |
| `plan_limit_exceeded` | Recurso fora do plano ou limite do plano atingido (ex.: número de sites). |
| `quota_exceeded`      | Cota mensal de requisições da API esgotada.                               |
| `too_many_requests`   | Rate limit por janela de tempo em endpoint sensível.                      |
| `not_found`           | Recurso inexistente ou não pertencente à sua conta.                       |
| `conflict`            | Estado incompatível com a operação (ex.: export ainda não pronto).        |
| `subscription_exists` | Já existe assinatura ativa; use a troca de plano.                         |
| `already_on_plan`     | Você já está no plano solicitado.                                         |
| `internal_error`      | Falha interna. Tente novamente; se persistir, contate o suporte.          |

## Status HTTP

| Status | Quando ocorre                                                                                          | Códigos típicos                                          |
| ------ | ------------------------------------------------------------------------------------------------------ | -------------------------------------------------------- |
| `400`  | Corpo/query malformados, validação de campos, cursor inválido.                                         | `invalid_request`                                        |
| `401`  | Sem autenticação válida: API Key revogada, sessão expirada, `X-Site-Token` inválido no ingest público. | `unauthorized`                                           |
| `402`  | Assinatura não está ativa.                                                                             | `plan_required`                                          |
| `403`  | CSRF ausente/inválido, e-mail não verificado, conta inativa, recurso fora do plano.                    | `forbidden`, `email_not_verified`, `plan_limit_exceeded` |
| `404`  | Recurso não existe **ou não é seu** — a API não distingue os dois casos.                               | `not_found`                                              |
| `409`  | Conflito de estado (assinatura duplicada, export não finalizado).                                      | `conflict`, `subscription_exists`                        |
| `413`  | Corpo maior que o limite da rota (ver [Limites de corpo](#limites-de-corpo-413)).                      | `invalid_request`                                        |
| `429`  | Rate limit por janela **ou** cota mensal esgotada — distinga pelo código.                              | `too_many_requests`, `quota_exceeded`                    |
| `5xx`  | Falha interna; mensagem sempre genérica.                                                               | `internal_error`                                         |

## CSRF (`403`)

Requisições de escrita (`POST`/`PUT`/`PATCH`/`DELETE`) autenticadas por **cookie de sessão** exigem o padrão *double-submit cookie*:

1. O backend entrega um cookie `csrf_token` legível por JavaScript (deliberadamente **não** HttpOnly).
2. O cliente ecoa o valor do cookie no header **`X-CSRF-Token`** em toda mutação.
3. Header ausente ou diferente do cookie → `403` com `{"error": "forbidden", "message": "CSRF token missing or invalid"}`.

Métodos de leitura (`GET`/`HEAD`/`OPTIONS`) nunca passam pela verificação.

<Note>
  **Chamadas com API Key são isentas de CSRF.** Tráfego server-to-server não tem contexto de navegador nem cookies ambientes — a autenticação já é um header `Authorization` explícito que um atacante precisaria possuir de qualquer forma. Se você integra apenas via `Bearer sk-...`, pode ignorar esta seção.
</Note>

Se o seu backend recebe `403` de CSRF, você provavelmente está autenticando com cookie sem querer — use API Key para integrações server-to-server.

## Headers `X-RateLimit-*`

Respostas de rotas **medidas por cota** (chamadas com API Key aos grupos `/consents`, `/policies` e `/exports`) trazem três headers, na convenção GitHub/Stripe:

| Header                  | Semântica                                                                                                                  |
| ----------------------- | -------------------------------------------------------------------------------------------------------------------------- |
| `X-RateLimit-Limit`     | Cota de requisições do período de cobrança. **`0` significa ilimitado** (plano sem teto).                                  |
| `X-RateLimit-Used`      | Requisições já consumidas no período.                                                                                      |
| `X-RateLimit-Remaining` | Restante, nunca negativo. Quando `Limit` é `0` (ilimitado), vem `0` — **ignore-o nesse caso** e use `Limit: 0` como sinal. |

Use-os para se auto-regular sem precisar consultar o estado do plano. Chamadas do dashboard (cookie) não consomem cota e não recebem esses headers. Quando a cota esgota, a API responde `429` com `quota_exceeded`.

Os rate limits por janela de tempo (`too_many_requests`) são independentes da cota mensal: endpoints sensíveis (login, recuperação de senha, contato) têm limites por IP e por e-mail, e o ingest público do banner aceita **30 req/min por cliente** e **600 req/min por site**. Esses limites **não** emitem headers `X-RateLimit-*` — apenas o `429`.

## Limites de corpo (`413`)

Cada rota limita o tamanho do corpo antes de qualquer processamento:

| Rotas                                                                                 | Limite     |
| ------------------------------------------------------------------------------------- | ---------- |
| `POST /api/v1/public/consent` (ingest público, sem autenticação)                      | **64 KiB** |
| Rotas autenticadas em geral (sites, políticas, exports, webhooks, ...)                | **1 MiB**  |
| Grupo `/api/v1/consents` — dimensionado para `POST /consents/batch` (até 1000 linhas) | **8 MiB**  |

Corpo com `Content-Length` acima do teto é rejeitado de imediato com `413` e código `invalid_request`. Corpos sem `Content-Length` (chunked) que estourem o teto durante a leitura podem aparecer como `400` de bind — trate ambos como "reduza o payload".

Esses tetos protegem o processo, não o negócio: limites por item (tamanho de campos, linhas do batch) são validados no DTO e retornam `400` com mensagem específica.

<p align="center">
  Dúvidas? <a href="mailto:suporte@consentfly.com.br">[suporte@consentfly.com.br](mailto:suporte@consentfly.com.br)</a>
</p>
