# Lidar com erros

## Estratégia geral

Classifique o erro antes de decidir a ação:

| Classe | Status | Ação |
|  --- | --- | --- |
| Erro do cliente | 4xx (exceto 429) | Corrigir a requisição; não retentar sem mudança |
| Rate limit | 429 | Aguardar reset e retentar |
| Erro de servidor | 5xx | Retentar com backoff exponencial |
| Conflito de estado | 409 | Verificar estado atual antes de retentar |


## Tratar 4xx

Erros 4xx indicam problema na sua requisição. Não faça retry sem corrigir a causa.


```typescript
async function chamarApi(url: string, options: RequestInit) {
  const res = await fetch(url, options);

  if (res.status >= 400 && res.status < 500 && res.status !== 429) {
    const erro = await res.json();
    // Logar código de erro para diagnóstico
    console.error(`Erro ${res.status}: ${erro.erro} — ${erro.mensagem}`);
    throw new Error(`Erro de cliente: ${erro.erro}`);
  }

  return res;
}
```

## Tratar 429 com backoff


```typescript
async function chamarComBackoff(
  fn: () => Promise<Response>,
  maxTentativas = 4
): Promise<Response> {
  for (let i = 0; i < maxTentativas; i++) {
    const res = await fn();

    if (res.status !== 429) return res;

    const resetHeader = res.headers.get('X-RateLimit-Reset');
    const aguardar = resetHeader
      ? Math.max(Number(resetHeader) * 1000 - Date.now(), 0)
      : Math.pow(2, i) * 1000;

    console.warn(`Rate limit atingido. Aguardando ${aguardar}ms...`);
    await new Promise(r => setTimeout(r, aguardar));
  }
  throw new Error('Rate limit excedido após todas as tentativas');
}
```

## Tratar 5xx com retry exponencial


```typescript
async function chamarComRetry(
  fn: () => Promise<Response>,
  maxTentativas = 3
): Promise<Response> {
  const intervalos = [1000, 5000, 30000];

  for (let i = 0; i < maxTentativas; i++) {
    try {
      const res = await fn();
      if (res.status < 500) return res;
      console.warn(`Erro ${res.status} na tentativa ${i + 1}`);
    } catch (e) {
      console.warn(`Falha de rede na tentativa ${i + 1}:`, e);
    }

    if (i < maxTentativas - 1) {
      await new Promise(r => setTimeout(r, intervalos[i]));
    }
  }
  throw new Error('Serviço indisponível após retries');
}
```

Idempotência no retry
Erros 5xx não consomem a `idempotencyKey`. Reenvie a mesma chave no retry — é seguro e evita duplicatas.

## Tratar 409 em pedidos

Um `409 Conflict` em confirmação indica que o pedido já mudou de estado. Verifique o estado atual antes de retentar:


```typescript
async function confirmarComVerificacao(pedidoId: string, key: string) {
  const res = await fetch(`${BASE_URL}/pedidos/${pedidoId}/confirmar`, {
    method: 'POST',
    headers: headers,
    body: JSON.stringify({ idempotencyKey: key }),
  });

  if (res.status === 409) {
    // Verificar estado atual
    const estadoRes = await fetch(`${BASE_URL}/pedidos/${pedidoId}`, { headers });
    const pedido = await estadoRes.json();
    console.log(`Pedido já em estado: ${pedido.status}`);
    // Se já confirmado, operação foi bem-sucedida — não é erro
    if (pedido.status === 'confirmado') return pedido;
    throw new Error(`Conflito inesperado: ${pedido.status}`);
  }

  return res.json();
}
```

## Checklist de robustez

- [ ] Todos os erros logam o campo `erro` (código de aplicação), não só o status HTTP.
- [ ] Retry implementado com backoff exponencial para 5xx e 429.
- [ ] `idempotencyKey` reutilizada nos retries de 5xx.
- [ ] Erros 4xx lançam exceção sem retry (exceto 429).
- [ ] Conflitos 409 verificam estado atual antes de agir.
- [ ] Timeout de conexão configurado (recomendado: 10s).


## Próximos passos

- [Guia de erros](/parceiros/guides/erros) — catálogo completo de códigos
- [Guia de idempotência](/parceiros/guides/idempotencia)
- [Guia de rate limits](/parceiros/guides/rate-limits)