# Tratamento de erros

Erros da API vêm sempre em JSON, com uma mensagem legível e um código interno:

```json
{
  "message": "Token expirado",
  "error_code": "API_AUT_03",
  "status_code": 401
}
```

O campo `message` muda com o tempo e serve para humanos — **não faça lógica em cima
dele**. Ramifique pelo **status HTTP** e, quando precisar distinguir causas dentro do mesmo
status, pelo `error_code`.

:::tip
Decida pelo **status HTTP**. Ele é o contrato: é o que todo cliente, proxy e biblioteca de
retentativa entende, e é o único sinal que existe quando não há corpo — um `502` de proxy,
um `504` de gateway ou um timeout não trazem JSON algum para ler.

O `error_code` do corpo refina o diagnóstico dentro de um status; o `status_code` repete o
HTTP e serve para log. Nenhum dos dois substitui o status da resposta.
:::


## Códigos HTTP

| Código | Significado | O que fazer |
| --- | --- | --- |
| `200` | Sucesso. | — |
| `201` | Recurso criado. | Guarde o `id` retornado. |
| `204` | Sucesso, sem conteúdo. Retornado pelas remoções de telefone e e-mail. | Não tente ler o corpo da resposta. |
| `400` | Requisição inválida — por exemplo, remover um telefone que não pertence ao cliente informado. | Corrija a requisição. Repetir não resolve. |
| `401` | Falha de autenticação. | Veja os códigos `API_AUT_*` em [Autenticação](/fundamentos/autenticacao#erros-de-autenticação). |
| `403` | Token válido, sem a permissão necessária. | Ajuste as permissões do token no painel. |
| `404` | Recurso não encontrado, ou fora da sua conta. | Confira o `id`. Lembre que o token só enxerga a própria unidade. |
| `422` | Falha de validação nos dados enviados. | Corrija os campos e reenvie. |
| `429` | Limite de requisições excedido. | Espere e tente de novo, com backoff exponencial. |
| `5xx` | Erro no lado da Quyta. | Tente novamente com backoff. Se persistir, fale com o suporte. |

## Erros de validação (422)

Ocorrem quando os dados enviados não passam nas regras do endpoint: campo obrigatório
ausente, documento inválido, data mal formatada, texto além do limite de caracteres.

São **erros de programação ou de dado de origem** — nunca se resolvem sozinhos. Não
recoloque a requisição na fila de retentativas: registre o payload, alerte alguém e
corrija a origem.

As causas mais comuns ao cadastrar clientes:

| Campo | O que reprova |
| --- | --- |
| `document` | CPF ou CNPJ com dígito verificador errado, com todos os dígitos iguais, ou com tamanho diferente de 11/14 dígitos. Também reprova se o documento já existe na sua conta. |
| `phones.*` | Número que não existe de verdade depois de normalizado — DDD inexistente, celular sem o nono dígito, DDI inválido. Veja [Telefones: E.164](/conceitos#telefones-e164). |
| `emails.*` | E-mail malformado, ou repetido dentro do mesmo array. |
| `addr_zipcode` | CEP que não tenha exatamente 8 dígitos. |

Os campos exigidos por cada endpoint estão na [Referência de API](/api).

## O que dá para tentar de novo

Nem todo erro merece retentativa. A distinção evita tanto perda de dados quanto laços
infinitos:

| Situação | Retentar? |
| --- | --- |
| `429`, `500`, `502`, `503`, `504`, timeout de rede | **Sim**, com backoff exponencial. |
| `400`, `401`, `403`, `404`, `422` | **Não.** A requisição precisa mudar antes. |

:::warning
Cuidado ao retentar `POST /debts` e `POST /customers` depois de um timeout: pode ser que a
requisição original tenha chegado. Como a API não é idempotente, você corre o risco de
duplicar o registro. Antes de reenviar, consulte
`GET /debts?external_id=...` ou `GET /customers?document=...` para conferir se o recurso
já existe.
:::

## Sugestão de tratamento

```javascript
async function chamarQuyta(caminho, opcoes = {}, tentativas = 4) {
  for (let i = 0; i < tentativas; i++) {
    const resposta = await fetch(`${process.env.QUYTA_API}${caminho}`, {
      ...opcoes,
      headers: {
        Authorization: `Bearer ${process.env.QUYTA_TOKEN}`,
        "Content-Type": "application/json",
        ...opcoes.headers,
      },
    });

    if (resposta.ok) {
      return resposta.status === 204 ? null : resposta.json();
    }

    // Decida pelo status HTTP: ele existe mesmo quando não há corpo.
    const transitorio = resposta.status === 429 || resposta.status >= 500;

    if (!transitorio) {
      // 400, 401, 403, 404, 422: interrompa e registre — retentar não muda nada.
      // O corpo entra só para detalhar o diagnóstico.
      const erro = await resposta.json().catch(() => ({}));
      throw new Error(
        `Quyta ${resposta.status} ${erro.error_code ?? ""}: ${erro.message ?? "erro desconhecido"}`,
      );
    }

    await new Promise((r) => setTimeout(r, 2 ** i * 1000));
  }

  throw new Error(`Quyta indisponível após ${tentativas} tentativas`);
}
```

## Registre o suficiente para investigar

Quando algo falhar, você vai querer reconstruir o que aconteceu. Guarde, no mínimo:

- método e caminho da requisição;
- `status_code` e `error_code` da resposta;
- o `external_id` ou `document` envolvido, para localizar o registro na origem;
- o horário da chamada.

Evite registrar o token, ainda que parcialmente.

## Suporte

Persistindo o problema, escreva para **suporte@quyta.com.br** com os dados acima. Quanto
mais preciso o relato — horário, `error_code`, identificador do registro —, mais rápida a
resposta.
