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

# Erros

> Formato padrão de erro, códigos HTTP e como tratar falhas de forma robusta.

Quando algo dá errado, a API retorna um status HTTP de erro (`4xx`/`5xx`) e um corpo JSON no formato
padrão abaixo.

## Formato do erro

```json theme={null}
{
  "error": {
    "code": "CONFLICT",
    "message": "E-mail já cadastrado",
    "key": "errors.conflict.duplicate_email",
    "params": { "email": "joao@example.com" }
  }
}
```

| Campo     | Sempre presente | Descrição                                                                                                 |
| --------- | --------------- | --------------------------------------------------------------------------------------------------------- |
| `code`    | Sim             | Código estável e legível por máquina (ex.: `NOT_FOUND`, `VALIDATION_ERROR`).                              |
| `message` | Sim             | Mensagem legível (fallback em pt-BR).                                                                     |
| `issues`  | Não             | Lista dos campos que falharam na validação. Veja [Erros de validação](#erros-de-validação-400).           |
| `key`     | Não             | Identifica o caso específico do erro, de forma estável. Veja [Erros de domínio](#erros-de-domínio-a-key). |
| `params`  | Não             | Parâmetros de interpolação da mensagem (ex.: `{ "email": "..." }`).                                       |
| `details` | Não             | Detalhes adicionais, quando houver.                                                                       |

<Tip>
  Programe sua lógica em cima do **`code`** (e do status HTTP), nunca do texto de `message` — o texto
  pode mudar ou vir traduzido.
</Tip>

## Códigos HTTP

| Status                | Código típico      | Significado                                                         | O que fazer                                                                                                          |
| --------------------- | ------------------ | ------------------------------------------------------------------- | -------------------------------------------------------------------------------------------------------------------- |
| `400`                 | `VALIDATION_ERROR` | Corpo ou parâmetros inválidos.                                      | Corrigir o payload. Ver `issues`.                                                                                    |
| `401`                 | — não tem `code`   | API key ausente ou inválida.                                        | Verificar o header `x-api-key`.                                                                                      |
| `403`                 | — não tem `code`   | Sem permissão para a operação.                                      | Verificar o tipo/escopo da chave.                                                                                    |
| `404`                 | `NOT_FOUND`        | Recurso não existe (ou é de outra conta).                           | Conferir o ID.                                                                                                       |
| `409`                 | `CONFLICT`         | Conflito de estado (duplicado, transição inválida).                 | Tratar o caso específico.                                                                                            |
| `422`                 | — não tem `code`   | Mesma `Idempotency-Key` reenviada com corpo diferente.              | Usar uma chave nova para uma requisição nova. Veja [Convenções · Idempotência](/pt-BR/convencoes#idempot%C3%AAncia). |
| `429`                 | — não tem `code`   | Limite de requisições excedido. Veja [Rate limit](#rate-limit-429). | Aguardar `Retry-After` e repetir com backoff.                                                                        |
| `500` / `502` / `503` | `INTERNAL_ERROR`   | Erro no servidor.                                                   | Repetir com backoff; se persistir, contatar o suporte.                                                               |

<Note>
  **Quatro erros não seguem esse formato.** O `401` e o `403` (autenticação), o `429` (limite de
  requisições) e os conflitos de idempotência — `422` para a mesma `Idempotency-Key` com corpo
  diferente, `409` para duas requisições simultâneas com a mesma chave — são interrompidos antes de
  chegar à aplicação. Neles, `error` vem como **texto**, não como objeto:

  ```json Formato normal theme={null}
  { "error": { "code": "NOT_FOUND", "message": "Transaction not found" } }
  ```

  ```json 401, 403, 422 e 429 theme={null}
  { "error": "Unauthorized" }
  ```

  Na prática: `error.code` e `error.message` são `undefined` nesses casos. Um tratador genérico que
  leia `error.code` quebra — e quebra dentro do próprio tratamento de erro, escondendo o problema
  original. Para esses status, ramifique pelo **status HTTP** e leia os headers (`Retry-After` no
  `429`). Veja [Convenções · Idempotência](/pt-BR/convencoes#idempot%C3%AAncia).
</Note>

## Rate limit (`429`)

Requisições podem ser limitadas para proteger a plataforma. Os tetos variam por endpoint e podem
mudar sem aviso, então **trate o `429` em qualquer chamada** em vez de assumir onde ele ocorre.

Como nos outros erros de infraestrutura, `error` vem como **texto**, não como objeto:

```json theme={null}
{
  "error": "Too Many Requests",
  "message": "Rate limit exceeded. Try again in 60s.",
  "retryAfter": 60
}
```

Headers relevantes:

| Header                  | Descrição                                                    |
| ----------------------- | ------------------------------------------------------------ |
| `Retry-After`           | Segundos até a janela do limite liberar (presente no `429`). |
| `X-RateLimit-Limit`     | Limite de requisições da janela.                             |
| `X-RateLimit-Remaining` | Requisições restantes na janela atual.                       |

## Erros de validação (`400`)

Quando um ou mais campos são recusados, o corpo traz **`issues`** — uma lista com todos os problemas
encontrados de uma vez, não só o primeiro:

```json theme={null}
{
  "error": {
    "code": "VALIDATION_ERROR",
    "message": "Validation failed",
    "issues": [
      { "path": "items", "message": "É necessário pelo menos 1 item" },
      { "path": "", "message": "customer ou customerId é obrigatório" }
    ]
  }
}
```

| Campo de `issues[]` | Descrição                                                                                                                                                         |
| ------------------- | ----------------------------------------------------------------------------------------------------------------------------------------------------------------- |
| `path`              | Caminho do campo, com `.` separando níveis e o índice para arrays: `items.0.amount`, `payments.0.creditCard.token`. Veja a observação abaixo sobre o valor vazio. |
| `message`           | Descrição do problema, já em texto legível.                                                                                                                       |
| `key`               | Chave de i18n, quando disponível. Opcional.                                                                                                                       |

<Warning>
  **A `message` do topo é genérica** (`"Validation failed"`) e não descreve o erro. O que interessa
  está em `issues` — não exiba a mensagem de fora para o usuário final achando que ela explica algo.
</Warning>

<Note>
  **`path` vazio (`""`) significa erro que não pertence a um campo só.** É o caso das regras que
  cruzam campos — por exemplo `customer ou customerId é obrigatório`, que depende dos dois. Ao
  montar a exibição do erro no seu sistema, trate o `path` vazio como um erro do formulário inteiro,
  e não de um campo específico.
</Note>

<Tip>
  **Toda validação responde `400`**, venha ela do formato do payload ou de uma regra de negócio, e
  sempre neste mesmo formato. Um único tratamento cobre os dois casos.
</Tip>

## Erros de domínio: a `key`

Alguns erros trazem um terceiro campo além de `code` e `message`: uma **`key`** estável, que
identifica o caso específico.

```json theme={null}
{
  "error": {
    "code": "CONFLICT",
    "message": "Já existe um checkout com o slug \"minha-loja\"",
    "key": "errors.checkout.slug_taken"
  }
}
```

Os três campos respondem perguntas diferentes:

| Campo     | Para quê                                                                                                                                                     |
| --------- | ------------------------------------------------------------------------------------------------------------------------------------------------------------ |
| `code`    | **A categoria.** `CONFLICT`, `VALIDATION_ERROR`, `NOT_FOUND`… É por aqui que você decide o fluxo: repetir, corrigir o payload, avisar o usuário.             |
| `key`     | **O caso exato.** Use quando quiser substituir a nossa mensagem pela sua naquele cenário — `errors.checkout.slug_taken` é sempre esse conflito, e não outro. |
| `message` | **O texto pronto**, com fallback em pt-BR. Exiba se não quiser escrever o seu. Pode mudar sem aviso: não use em condicional.                                 |

<Warning>
  **A `key` não vai em `code`.** Um erro de slug duplicado tem `code: "CONFLICT"` e
  `key: "errors.checkout.slug_taken"`. Comparar `error.code === "slug_taken"` nunca casa — e falha
  em silêncio, porque o campo existe e simplesmente tem outro valor.
</Warning>

Cada página de endpoint lista os erros que aquela rota produz, com a `key` quando houver. Não há
catálogo central: erros de domínio nascem e mudam com as regras de negócio, e uma lista mantida à mão
desatualiza sem ninguém perceber.

## Tratamento recomendado

<CardGroup cols={2}>
  <Card title="Retry com backoff" icon="rotate-cw">
    Em `429` e `5xx`, repita com backoff exponencial. Combine com `Idempotency-Key` para não
    duplicar a operação.
  </Card>

  <Card title="Não faça retry em 4xx" icon="ban">
    `400`, `409` e `422` não se resolvem repetindo a mesma requisição — corrija o payload ou trate o
    conflito.
  </Card>

  <Card title="Logue o code e o corpo" icon="bug">
    Guarde `code`, status HTTP e o corpo do erro para diagnóstico — em `400`, guarde `issues`
    inteiro. Nunca logue a API key.
  </Card>

  <Card title="Idempotência sempre" icon="shield">
    Em escrita, use `Idempotency-Key` para que retries sejam seguros. Ver
    [Convenções](/pt-BR/convencoes).
  </Card>
</CardGroup>
