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

# Erros e códigos HTTP

> O formato das respostas de erro da NyxPag API, o catálogo completo de error.code, o que fazer em cada status e como repetir com segurança.

Toda resposta de erro tem o mesmo formato: `success: false`, um objeto `error` e um `requestId`. Decida pelo **`error.code`**, que é estável. A `error.message` é para humanos e pode mudar.

```json theme={"dark"}
{
  "success": false,
  "error": {
    "code": "invalid_request",
    "message": "Informe um CPF válido para o pagador"
  },
  "requestId": "req_01H..."
}
```

| Campo | Descrição |
| - | - |
| `success` | Sempre `false` |
| `error.code` | Código curto, legível por máquina |
| `error.message` | Explicação em português |
| `requestId` | Protocolo da requisição. Mande ao suporte |

<Tip>
  Registre o `requestId` de **toda** resposta nos seus logs, não só nas de erro. É o que o suporte pede para localizar a requisição.
</Tip>

## Por status HTTP

| HTTP | Significado | Repetir? |
| - | - | - |
| `400` | Dados inválidos | Não. Corrija |
| `401` | Chave ausente, inválida ou revogada | Não. Corrija a chave |
| `403` | Sem permissão, IP bloqueado ou conta sem verificação | Não |
| `404` | Recurso não encontrado | Não |
| `409` | Conflito de idempotência, ou saque já em andamento | Não com corpo novo. Veja abaixo |
| `422` | Operação recusada por regra de negócio | Não. Não é falha de rede |
| `423` | Saques bloqueados pela administração | Não |
| `429` | Limite de requisições | Sim, depois do `Retry-After` |
| `5xx` | Erro do nosso lado | Sim, com a **mesma** chave |

## Catálogo de `error.code`

### Autenticação e conta

| Código | HTTP | Quando |
| - | - | - |
| `invalid_api_key` | 401 | Chave ausente, mal formada, inexistente ou revogada |
| `permission_denied` | 403 | A chave não tem a permissão do endpoint. A mensagem diz qual |
| `ip_not_allowed` | 403 | A chave está restrita a IPs e o seu não está na lista |
| `kyc_required` | 403 | A conta não concluiu a verificação de identidade |
| `account_unavailable` | 403 | Conta bloqueada ou indisponível |

### Requisição

| Código | HTTP | Quando |
| - | - | - |
| `invalid_request` | 400 (e 423) | Dado inválido. A `message` diz qual |
| `invalid_status` | 400 | `status` fora de `pending`, `approved`, `failed`, `cancelled`, `refunded` |
| `invalid_type` | 400 | `type` fora de `payment` e `withdrawal` |
| `invalid_med_status` | 400 | `status` de MED fora da lista |
| `not_found` | 404 | Transação ou MED não encontrado |

### Idempotência

| Código | HTTP | Quando |
| - | - | - |
| `idempotency_key_required` | 400 | Faltou `Idempotency-Key` ou `externalId` |
| `invalid_idempotency_key` | 400 | Mais de 100 caracteres, ou caracteres fora de `. _ : / -` e alfanuméricos |
| `idempotency_conflict` | 409 | Mesma chave com corpo diferente, ou `Idempotency-Key` diferente do `externalId` |

### Operação financeira

| Código | HTTP | Quando |
| - | - | - |
| `operation_refused` | 422 | Regra de negócio: saldo insuficiente, recurso desabilitado, split que não cabe |
| `WITHDRAWAL_IN_PROGRESS` | 409 | Já existe uma transferência `pending` na conta |
| `internal_error` | 500, 503 | Falha interna ou rota de processamento indisponível |

### Split

| Código | HTTP | Quando |
| - | - | - |
| `SPLIT_INVALID` | 400 | Item sem `amount` nem `percent`, ou com os dois |
| `SPLIT_RECIPIENT_INVALID` | 400 | `recipient` inválido |
| `SPLIT_TOO_MANY_RECIPIENTS` | 400 | Mais de 10 recebedores |
| `SPLIT_RECIPIENT_NOT_FOUND` | 404 | Conta do recebedor não existe ou está bloqueada |
| `SPLIT_RECIPIENT_SELF` | 409 | O recebedor é a própria conta |
| `SPLIT_RECIPIENT_DUPLICATED` | 409 | Recebedor repetido |
| `SPLIT_EXCEEDS_SALE` | 422 | O split não cabe na venda |

### Limites

| Código | HTTP | Quando |
| - | - | - |
| `rate_limit_exceeded` | 429 | Mais de 100 requisições em 60 segundos |

<Note>
  Em produção, erros `5xx` trazem uma mensagem genérica ("Não foi possível processar a solicitação."). Quem diagnostica é o `requestId`, com o suporte.
</Note>

## 429: limite de requisições

A NyxPag aplica **100 requisições por janela de 60 segundos, por API Key**, somando todas as rotas e o MCP. Proteções adicionais por IP também podem devolver `429`.

| Cabeçalho | Unidade | Descrição |
| - | - | - |
| `Retry-After` | segundos | Quanto esperar antes de tentar de novo |
| `RateLimit-Limit` | requisições | Limite da regra aplicada (`100` na cota por chave) |
| `RateLimit-Remaining` | requisições | Quantas restam na janela |
| `RateLimit-Reset` | segundos | Tempo até a janela reiniciar |

```json theme={"dark"}
{
  "success": false,
  "code": "rate_limit_exceeded",
  "error": {
    "code": "rate_limit_exceeded",
    "message": "Muitas solicitações. Aguarde antes de tentar novamente."
  },
  "retryAfter": 60,
  "requestId": "req_01H..."
}
```

Veja [Limites](/guides/confiabilidade/limites) para as regras completas da cota.

## O que repetir e o que não repetir

| Situação | Estratégia |
| - | - |
| `400`, `401`, `403`, `404`, `422`, `423` | **Não repita.** Corrija a causa. Repetir igual dá o mesmo erro |
| `409 idempotency_conflict` | Não troque a chave para "passar". Descubra o que mudou no corpo ou consulte a original |
| `409 WITHDRAWAL_IN_PROGRESS` | Espere a transferência pendente terminar e envie a próxima |
| `429` | Espere o `Retry-After`, com jitter |
| `5xx` e timeout | Backoff exponencial, **mesma chave de idempotência** |

<Warning>
  Ao repetir `POST /transactions` ou `POST /transfers`, use a **mesma** `Idempotency-Key` ou `externalId`. Uma chave nova cria uma operação nova. Veja [Idempotência](/guides/idempotencia).
</Warning>

## Repetição com backoff e jitter

<CodeGroup>
  ```javascript JavaScript theme={"dark"}
  async function request(url, options, maxAttempts = 5) {
    for (let attempt = 1; attempt <= maxAttempts; attempt++) {
      let res;
      try {
        res = await fetch(url, options);
      } catch {
        res = null; // erro de rede
      }

      if (res && res.status < 500 && res.status !== 429) return res;
      if (attempt === maxAttempts) return res;

      const retryAfter = res?.status === 429 ? Number(res.headers.get("Retry-After") ?? 1) : 0;
      const backoff = Math.min(2 ** attempt * 1000, 30_000);
      const jitter = Math.random() * 500;
      await new Promise((r) => setTimeout(r, Math.max(retryAfter * 1000, backoff) + jitter));
    }
  }
  ```

  ```python Python theme={"dark"}
  import random
  import time
  import requests

  def request(method, url, max_attempts=5, **kwargs):
      for attempt in range(1, max_attempts + 1):
          try:
              res = requests.request(method, url, timeout=15, **kwargs)
          except requests.RequestException:
              res = None  # erro de rede

          if res is not None and res.status_code < 500 and res.status_code != 429:
              return res
          if attempt == max_attempts:
              return res

          retry_after = int(res.headers.get("Retry-After", "1")) if res is not None and res.status_code == 429 else 0
          backoff = min(2 ** attempt, 30)
          time.sleep(max(retry_after, backoff) + random.random() * 0.5)
  ```

  ```php PHP theme={"dark"}
  <?php
  function request(string $url, array $options, int $maxAttempts = 5): array
  {
      for ($attempt = 1; $attempt <= $maxAttempts; $attempt++) {
          $headers = [];
          $ch = curl_init($url);
          curl_setopt_array($ch, $options + [
              CURLOPT_RETURNTRANSFER => true,
              CURLOPT_TIMEOUT => 15,
              CURLOPT_HEADERFUNCTION => function ($c, $line) use (&$headers) {
                  $parts = explode(':', $line, 2);
                  if (count($parts) === 2) $headers[strtolower(trim($parts[0]))] = trim($parts[1]);
                  return strlen($line);
              },
          ]);
          $body = curl_exec($ch);
          $status = curl_getinfo($ch, CURLINFO_HTTP_CODE);

          if ($body !== false && $status < 500 && $status !== 429) return [$status, $body];
          if ($attempt === $maxAttempts) return [$status, $body];

          $retryAfter = $status === 429 ? (int) ($headers['retry-after'] ?? 1) : 0;
          sleep(max($retryAfter, min(2 ** $attempt, 30)));
      }
  }
  ```
</CodeGroup>

<CardGroup cols={2}>
  <Card title="Idempotência" icon="repeat" href="/guides/idempotencia">
    Repetição segura nas operações financeiras.
  </Card>

  <Card title="Troubleshooting" icon="wrench" href="/guides/ajuda/troubleshooting">
    Sintomas comuns e como resolver.
  </Card>
</CardGroup>


This documentation is built and hosted on [Mintlify](https://mintlify.com), a developer documentation platform.