# Erros

> Formato dos erros da API do Couryo, tipos, status HTTP e todos os códigos com causa e correção.

Fonte: https://couryo.com/docs/errors

Todo erro tem o mesmo formato. O `code` é estável: pode usar no seu código sem medo de mudar. Cada `code` tem sempre o mesmo `type` e o mesmo status HTTP.

```json title="Erro"
{
  "error": {
    "type": "invalid_request",
    "code": "domain_not_verified",
    "message": "O domínio exemplo.com.br ainda não foi verificado neste projeto.",
    "param": "from",
    "doc_url": "https://couryo.com/docs/errors#domain_not_verified",
    "request_id": "req_6f2a9c1e7b0k3m4n"
  }
}
```

| Campo | O que é |
|---|---|
| `type` | a categoria do erro (tabela abaixo) |
| `code` | o motivo exato, estável |
| `message` | explicação para pessoas, em português ou inglês conforme o `Accept-Language` (o padrão é português) |
| `param` | o campo com problema, quando houver (por exemplo, `to[1]` ou `emails[2].from`) |
| `doc_url` | o link para a explicação nesta página |
| `request_id` | o ID da chamada, igual ao cabeçalho `Request-Id`; mande ao suporte se precisar de ajuda |

## Tipos

| `type` | HTTP | Quando |
|---|---|---|
| `invalid_request` | 400 ou 422 | o pedido tem algum problema que você pode corrigir |
| `authentication` | 401 | chave ausente ou inválida |
| `permission` | 403 | a chave ou o plano não permite isso |
| `not_found` | 404 | o recurso ou a rota não existe |
| `conflict` | 409 | conflito, como uma chave de idempotência reaproveitada |
| `rate_limited` | 429 | muitas chamadas em pouco tempo |
| `tier_limit` | 429 | limite do seu nível, do seu plano ou do seu limite de gasto |
| `api_error` | 500 ou 503 | problema do nosso lado; pode tentar de novo |

## Códigos

| `code` | `type` | HTTP | Resumo |
|---|---|---|---|
| [`missing_field`](#missing_field) | `invalid_request` | 400 | falta um campo obrigatório |
| [`invalid_field`](#invalid_field) | `invalid_request` | 400 | um campo tem valor inválido |
| [`invalid_json`](#invalid_json) | `invalid_request` | 400 | o corpo não é JSON |
| [`batch_too_large`](#batch_too_large) | `invalid_request` | 400 | mais de 100 e-mails no lote |
| [`invalid_cloudflare_token`](#invalid_cloudflare_token) | `invalid_request` | 400 | token da Cloudflare recusado |
| [`cloudflare_zone_not_found`](#cloudflare_zone_not_found) | `invalid_request` | 400 | o token não alcança a zona do domínio |
| [`domain_not_verified`](#domain_not_verified) | `invalid_request` | 422 | o domínio do `from` não está verificado |
| [`recipient_suppressed`](#recipient_suppressed) | `invalid_request` | 422 | todos os destinatários estão suprimidos |
| [`missing_api_key`](#missing_api_key) | `authentication` | 401 | sem chave |
| [`invalid_api_key`](#invalid_api_key) | `authentication` | 401 | chave inválida |
| [`not_authenticated`](#not_authenticated) | `authentication` | 401 | painel sem sessão |
| [`insufficient_scope`](#insufficient_scope) | `permission` | 403 | escopo da chave insuficiente |
| [`ip_not_allowed`](#ip_not_allowed) | `permission` | 403 | IP fora da lista da chave |
| [`sandbox_recipient`](#sandbox_recipient) | `permission` | 403 | conta no nível 0 enviando para fora |
| [`domain_limit_reached`](#domain_limit_reached) | `permission` | 403 | a conta já tem os domínios do plano |
| [`forbidden_origin`](#forbidden_origin) | `permission` | 403 | escrita no painel vinda de outro site |
| [`resource_not_found`](#resource_not_found) | `not_found` | 404 | ID inexistente |
| [`route_not_found`](#route_not_found) | `not_found` | 404 | rota inexistente |
| [`idempotency_conflict`](#idempotency_conflict) | `conflict` | 409 | mesma chave de idempotência, corpo diferente |
| [`idempotency_in_progress`](#idempotency_in_progress) | `conflict` | 409 | o mesmo pedido ainda está em andamento |
| [`domain_exists`](#domain_exists) | `conflict` | 409 | domínio já cadastrado |
| [`already_suppressed`](#already_suppressed) | `conflict` | 409 | endereço já está na lista |
| [`last_project`](#last_project) | `conflict` | 409 | tentativa de apagar o único projeto |
| [`rate_limited`](#rate_limited) | `rate_limited` | 429 | chamadas demais por segundo |
| [`daily_limit_reached`](#daily_limit_reached) | `tier_limit` | 429 | limite diário atingido |
| [`monthly_limit_reached`](#monthly_limit_reached) | `tier_limit` | 429 | cota do mês atingida |
| [`spend_limit_reached`](#spend_limit_reached) | `tier_limit` | 429 | o excedente chegou ao seu limite de gasto |
| [`sending_paused`](#sending_paused) | `tier_limit` | 429 | envio pausado, com motivo |
| [`internal_error`](#internal_error) | `api_error` | 500 | erro do nosso lado |
| [`billing_unavailable`](#billing_unavailable) | `api_error` | 503 | cobrança indisponível agora |

### missing_field

Falta um campo obrigatório, indicado em `param`. Para enviar, são obrigatórios `from`, `to`, `subject` e um entre `html` ou `text` (sem nenhum dos dois, o `param` vem como `html`).

### invalid_field

O campo indicado em `param` tem um valor que não aceitamos: um endereço mal formado, mais de 50 destinatários somando `to`, `cc` e `bcc`, um `scheduled_at` no passado ou a mais de 30 dias, uma URL de webhook sem `https://`. A `message` diz o que está errado. Em lote, o `param` vem com o índice: `emails[2].to[0]`.

### invalid_json

O corpo não é um JSON válido. Confira o `Content-Type: application/json` e as aspas.

### batch_too_large

O lote tem mais de 100 e-mails. Divida em chamadas menores.

### invalid_cloudflare_token

O token da Cloudflare não foi aceito. Ele precisa ter a permissão **Zona > DNS > Editar** para o domínio. Veja [Domínios e DNS](https://couryo.com/docs/domains.md#conectar-a-cloudflare).

### cloudflare_zone_not_found

O token é válido, mas não dá acesso à zona do domínio na Cloudflare. Crie o token incluindo essa zona.

### domain_not_verified

O domínio do `from` não está verificado no projeto da chave. Verifique em [Domínios e DNS](https://couryo.com/docs/domains.md) e tente de novo. Chaves `ck_test_` não exigem domínio verificado.

### recipient_suppressed

**Todos** os destinatários estão na [lista de supressão](https://couryo.com/docs/suppressions.md), por devolução permanente, reclamação, descadastro ou inclusão manual. O `param` indica o primeiro (`to[n]`). Se só alguns estiverem suprimidos, o e-mail sai para os outros e a linha do tempo ganha um evento `suppressed` para cada endereço pulado.

### missing_api_key

Faltou o cabeçalho `Authorization: Bearer ck_...`. Veja [Autenticação](https://couryo.com/docs/authentication.md).

### invalid_api_key

A chave não existe, foi apagada ou foi copiada pela metade. Crie uma nova no painel se precisar.

### not_authenticated

Chamada ao painel (`/api`) sem sessão. Entre de novo em [app.couryo.com](https://app.couryo.com). Não acontece na API pública (`/v1`).

### insufficient_scope

A chave não tem permissão para esta operação. Por exemplo, uma chave `send` tentando ler e-mails, que exige `read`. A `message` diz qual escopo falta.

### ip_not_allowed

A chave tem uma lista de IPs permitidos, e a chamada veio de outro endereço. Ajuste a lista no painel.

### sandbox_recipient

A conta está no nível 0 (sandbox), ou o remetente é `teste@sandbox.couryo.com`, e esse caminho só envia para os e-mails da própria conta. Verifique um domínio para subir ao nível 1. Veja [Limites e níveis](https://couryo.com/docs/limits.md).

### domain_limit_reached

A conta já tem o número de domínios que o plano permite: Grátis 1, Pro 10, Escala 50, Empresa sem limite (somando todos os projetos). Apague um domínio que não usa ou mude de plano. A `message` diz o limite atual e o do plano seguinte.

### forbidden_origin

Uma escrita no painel veio de outro site (proteção contra CSRF). Não acontece na API pública.

### resource_not_found

O ID informado não existe neste projeto, ou pertence a outro projeto. A `message` diz qual recurso não foi encontrado.

### route_not_found

O caminho não existe. Confira o método e o endereço, por exemplo `POST /v1/emails`.

### idempotency_conflict

A mesma `Idempotency-Key` foi usada nas últimas 24 horas com um corpo diferente. Use uma chave nova para um pedido novo. Veja [Idempotência](https://couryo.com/docs/sending.md#idempotencia).

### idempotency_in_progress

Um pedido com a mesma `Idempotency-Key` ainda está sendo processado. Espere o `Retry-After` (1 segundo) e repita: você recebe a resposta do primeiro.

### domain_exists

Esse domínio já está cadastrado no Couryo, nesta ou em outra conta. Se ele é seu e está em outra conta, apague de lá primeiro.

### already_suppressed

O endereço já está na lista de supressão desse fluxo.

### last_project

A conta precisa de pelo menos um projeto. Crie outro antes de apagar este.

### rate_limited

Chamadas demais em pouco tempo (o padrão é 10 por segundo por chave). Espere os segundos do cabeçalho `Retry-After` e tente de novo com a mesma `Idempotency-Key`. Os cabeçalhos `RateLimit-Limit`, `RateLimit-Remaining` e `RateLimit-Reset` mostram sua folga.

### daily_limit_reached

Você atingiu o limite diário do seu nível de confiança, ou o do plano Grátis (100 por dia). A `message` diz o limite, quanto já foi enviado e o que falta para subir de nível. O contador zera à meia-noite no fuso da sua conta.

### monthly_limit_reached

Você atingiu a cota do mês: 3 mil no plano Grátis e também no nível 1 de qualquer plano. O envio volta no mês seguinte (no plano pago, no próximo período). No nível 1, a cota sobe quando a conta chega ao nível 2; no Grátis, ao assinar o Pro ou o Escala.

### spend_limit_reached

O excedente do mês chegou ao limite de gasto que você definiu em **Cobrança**. **Todo** o envio para (transacional e marketing) até você aumentar o limite ou o período virar. Nada é cobrado acima do teto.

### sending_paused

O envio está pausado. Na pausa comum, o marketing para e o transacional continua com até 30 por dia; na pausa total, tudo para. A `message` traz o motivo, os números e como corrigir, e o painel tem o botão **Pedir revisão**. Veja [Limites e níveis](https://couryo.com/docs/limits.md#pausas).

### internal_error

Um problema do nosso lado. O pedido **não** foi processado. Tente de novo com a mesma `Idempotency-Key`: não há risco de duplicar.

### billing_unavailable

A cobrança está indisponível agora (checkout ou portal de pagamento). Tente de novo em alguns minutos; o envio de e-mails não é afetado.
