success: false, um objeto error e um requestId. Decida pelo error.code, que é estável. A error.message é para humanos e pode mudar.
{
"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 |
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.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 |
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.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 devolver429.
| 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 |
{
"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..."
}
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 |
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.Repetição com backoff e jitter
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));
}
}
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
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)));
}
}
Idempotência
Repetição segura nas operações financeiras.
Troubleshooting
Sintomas comuns e como resolver.

