Erros
Toda resposta 4xx/5xx carrega o mesmo envelope JSON:
{
"code": 400,
"message": "invalid_input",
"details": [
{ "field": "items[0].unit_price", "message": "must be a positive decimal string" }
]
}code— o status HTTP numérico (400,401,404, …). Útil para tratamento genérico na camada HTTP, mas para lógica de ramificação faça switch emmessageem vez disso —codenão vai desambiguar entre, digamos,invalid_inputepayment_method_not_supported(ambos 400).message— o nome sentinela em lower-snake-case (por exemplo,INVALID_INPUTvira"invalid_input"). Estável entre releases — faça switch nisso.details— preenchido em erros de validação. Array de objetos{ field, message }para que o cliente possa amarrar erros a inputs. Omitido caso contrário.
Os corpos de erro não incluem trace ID nem timestamp. Para reportar
um problema, envie ao suporte o header Date da resposta, os headers
X-RateLimit-*, o ID do seu lojista, o endpoint e o horário
aproximado da requisição.
Falhas de autenticação usam um formato diferente. O envelope
acima é o que a API retorna na maioria dos erros. Requisições
rejeitadas antes de serem processadas (um X-Signature
ausente/inválido, um X-Client-ID desconhecido ou um X-Timestamp
velho numa chamada /b2b/v1/*) voltam como
{ "error": "...", "message": "..." }, onde error é um slug
grosseiro (unauthorized / bad_request / service_unavailable)
e message carrega as especificidades. Não há code numérico
nem details. Ramifique primeiro pelo status HTTP, depois leia
message. Exemplo (401):
{ "error": "unauthorized", "message": "invalid signature" }Status HTTP → códigos típicos
| HTTP | Valores típicos de code | O que significa |
|---|---|---|
| 400 | INVALID_INPUT, MISSING_REQUIRED, INVALID_FORMAT, INVALID_LENGTH, INVALID_VALUE, PAYMENT_METHOD_NOT_SUPPORTED, AMOUNT_BELOW_MINIMUM | Requisição ruim — olhe details |
| 401 | INVALID_CREDENTIALS, INVALID_TOKEN, TOKEN_EXPIRED, INVALID_SIGNATURE (B2B); SESSION_EXPIRED (só dashboard) | Auth falhou — chave ruim, timestamp expirado, assinatura errada. Os códigos de login do dashboard não são relevantes para integrações B2B. |
| 402 | INSUFFICIENT_CREDIT | Saldo pré-pago do lojista acabou — recarregue antes de retentar |
| 403 | FORBIDDEN, IP_BLOCKED | Chave é válida, mas não tem o escopo/permissão de IP para esta chamada |
| 404 | NOT_FOUND, RECORD_NOT_FOUND | Recurso não existe (ou não existe para este lojista) |
| 409 | ALREADY_EXISTS | Replay de idempotência com body diferente, ou a máquina de estados recusou a transição |
| 429 | TOO_MANY_REQUESTS, TOO_MANY_ATTEMPTS | Rate limit batido; backoff e retente |
| 500 | INTERNAL_SERVER_ERROR, EXTERNAL_SERVICE_ERROR | Erro nosso; seguro retentar com backoff. |
| 503 | SERVICE_UNAVAILABLE | Uma dependência downstream caiu. Retente com backoff |
Referência completa de códigos
Os valores de code que você pode ver:
Autenticação (401)
INVALID_CREDENTIALS— combinação de chaves de API rejeitadaINVALID_TOKEN— token não-parseável ou adulteradoTOKEN_EXPIRED— token expiradoSESSION_EXPIRED— sessão do dashboard expirouINVALID_SIGNATURE— assinatura HMAC não bate em chamadas B2B / webhook
Autorização (403)
FORBIDDEN— autenticado, mas você não tem permissão para realizar esta açãoIP_BLOCKED— requisições deste endereço IP estão bloqueadas
Não encontrado (404)
NOT_FOUND— genéricoRECORD_NOT_FOUND— linha ausente para o ID dado
Conflito (409)
ALREADY_EXISTS— genérico
Validação (400)
INVALID_INPUT— genérico; chequedetailsMISSING_REQUIRED— um campo obrigatório estava ausenteINVALID_FORMAT— valor não combinou com o formato esperado (por exemplo, UUID, URL, e-mail)INVALID_LENGTH— valor muito curto ou muito longoINVALID_VALUE— valor fora do enum/range permitidoPAYMENT_METHOD_NOT_SUPPORTED— combinação provider/ativo não está habilitada para o lojistaAMOUNT_BELOW_MINIMUM— valor do pedido abaixo do piso por rede ou por ambiente. A resposta trazdetails.floor_usdcom o piso configurado (em USD), então você pode exibi-lo diretamente; o texto demessagetambém o informa.INSUFFICIENT_BALANCE— a carteira do comprador não tem saldo suficiente do ativo de pagamento para cobrir a transferência.INSUFFICIENT_GAS— a carteira do comprador não tem gas nativo suficiente para transmitir a transferência.
Pagamento / faturamento (402)
INSUFFICIENT_CREDIT— saldo pré-pago do lojista não consegue cobrir a taxa de rede (gas) / taxa da plataforma. Recarregue pelo dashboard, depois retente
Rate limiting (429)
TOO_MANY_REQUESTS— rate limit de IP excedidoTOO_MANY_ATTEMPTS— tentativas falhadas repetidas no mesmo recurso (por exemplo, OTP) dispararam um throttle
Erros de servidor (500)
EXTERNAL_SERVICE_ERROR— um provedor terceiro falhouINTERNAL_SERVER_ERROR— erro inesperado; fale com o suporte informando o headerDateda resposta e os headersX-RateLimit-*
Disponibilidade (503)
SERVICE_UNAVAILABLE— o serviço está temporariamente indisponível; retente com backoff
Erros de validação (400)
Quando o problema é um body de requisição malformado, details é
um array para que você possa mapear erros de volta para campos:
{
"code": 400,
"message": "invalid_input",
"details": [
{ "field": "items[0].unit_price", "message": "must be a positive decimal string" },
{ "field": "success_url", "message": "must be a valid https URL" }
]
}Erros de auth (401)
INVALID_SIGNATURE tem três causas comuns:
- Chave secreta errada (re-cheque a env var)
- Defasagem de timestamp > 5 min (sincronize NTP)
- String canônica errada (mais comum: esqueceu os separadores
\nou assinou um body parseado-e-re-stringificado que difere do que você mandou)
Veja Autenticação para o algoritmo exato de assinatura.
Rate limits (429)
| Superfície | Limite |
|---|---|
Todas as rotas da API (incl. /b2b/v1/*) | Por endereço IP — 500 req/min, bucket compartilhado. Não por lojista. |
Checkout público (/checkout/*) | Sub-bucket mais estrito — 20 req/min por IP |
X-RateLimit-Limit e X-RateLimit-Remaining são incluídos em
toda resposta com rate limit, não só nas de sucesso. Trate como o orçamento vivo para o seu IP.
Respostas com rate-limit incluem um header Retry-After (segundos
até a janela resetar) — honre-o. Como fallback, backoff com jitter —
1s base + exponencial até 30s.
Os rate limits podem mudar. Se você os atinge com tráfego legítimo (por exemplo, reconciliando uma faixa histórica grande), fale com o suporte.
Crédito insuficiente (402)
INSUFFICIENT_CREDIT (HTTP 402 Payment Required) significa que
o seu saldo pré-pago não consegue cobrir a taxa de rede (gas) e a
taxa da plataforma para a operação que você tentou, tipicamente
liquidar um pagamento cripto ou uma ação on-chain cuja taxa de rede
é patrocinada. Recarregue pelo dashboard do lojista
(Billing → Add credit), depois retente a operação.
Operações já em andamento continuam automaticamente depois que o
saldo é recarregado.
Erros de servidor (5xx)
Um 500 significa que a gente não conseguiu processar a requisição.
Retente com backoff — a sua idempotency_key garante que você não
vai cobrar em dobro se a requisição original parcialmente teve
sucesso.
Se as retentativas não recuperam em um minuto, exiba um genérico
“pagamento temporariamente indisponível” para o comprador e fale
com o suporte com o endpoint que falhou, o ID do seu lojista, o
header Date da resposta e os valores X-RateLimit-*, e o horário
aproximado da requisição — isso é o suficiente para a gente
localizar a requisição.
Erros de entrega de webhook
Entregas de webhook são um canal de falha separado — não aparecem como erros de API porque não é o seu servidor que está chamando. Quando uma entrega retorna não-2xx (ou dá timeout), ela é retentada com backoff exponencial em 0s, 1min, 5min, 15min, 1h, 6h (seis tentativas no total, o mesmo cronograma de Webhooks → Visão geral). O histórico completo por entrega aparece em Developers → Webhooks → [endpoint] → Delivery log no dashboard. Depois da sexta tentativa a entrega é marcada como “Failed” no dashboard, e você pode reenviá-la manualmente assim que o seu servidor estiver saudável.