Erros

Para: Dev — Conferido em 2026-10-18.

Todo erro da API /v1 é um objeto application/problem+json com um code estável. Esta página lista o formato do erro, os 30 code que a API emite hoje, os 22 motivos de validação (errors[].reason), as respostas da borda que não têm corpo e o que acontece quando uma dependência do Dracma está fora.

O que é

O corpo do erro segue a RFC 9457. O campo que o seu código testa é o code.

{
  "type": "https://docs.dracma.test/errors/insufficient_scope",
  "title": "insufficient_scope",
  "status": 403,
  "code": "insufficient_scope",
  "trace_id": "4bf92f3577b34da6a3ce929d0e0e4736",
  "retryable": false
}
Campo Presença Uso
code sempre Estável. É o que o seu código testa. Códigos novos só se acrescentam.
status sempre O mesmo status HTTP da resposta.
retryable sempre true se vale repetir com a mesma Idempotency-Key.
trace_id sempre Também no cabeçalho Dracma-Trace-Id. Guarde no log; é o que o suporte pede.
type sempre Identificador opaco (https://docs.dracma.test/errors/<code>). Não é uma página: o domínio não resolve.
title sempre Igual ao code.
detail omitido quando vazio Aparece só em poucos erros (internal_error, day_open, ação de sandbox desconhecida). Nunca faça parse dele.
errors[] em validation_failed Itens com field, message e reason (o reason é omitido se vazio).
existing em 409 duplicate_external_id O depósito ou saque que já existe com esse external_id.

O Dracma nunca devolve rastro de pilha, SQL, segredo, a assinatura esperada nem o pedido canônico calculado.

Quando usar

Use o code para decidir o fluxo do conector e o retryable (junto da classe HTTP) para decidir se repete. Use o trace_id para falar com o suporte. Use errors[].field e errors[].reason para corrigir o seu pedido, e nunca para mostrar texto ao jogador: message não é contrato.

Como funciona

O que fazer com cada resposta

Resposta Significado Ação
2xx Feito (ou já estava feito) Grave o resultado.
4xx, exceto 409 idempotency_in_progress, 409 day_open e 429 O Dracma decidiu recusar Não repita igual. Corrija a causa ou encerre o fluxo.
409 idempotency_in_progress, 409 day_open, 429, 5xx Sem efeito conclusivo, ou ainda cedo Espere o Retry-After e repita com a mesma Idempotency-Key.
timeout, conexão caiu Você não sabe se houve efeito Repita com a mesma chave; ao desistir, consulte por external_id (Idempotência e repetição).
code que você não conhece Código novo (a lista só cresce) Trate pela classe HTTP e pelo retryable.

Catálogo de code emitidos

São 30 códigos. Repetir = repetir com a mesma Idempotency-Key (retryable: true).

HTTP code Repetir Quando O que fazer
400 validation_failed não Corpo, parâmetro ou cabeçalho inválido (errors[]); Idempotency-Key ausente; 4xx genérico do servidor HTTP Corrija o pedido. É erro do seu lado.
400 currency_not_enabled não Moeda não habilitada na operação Ajuste no onboarding; confira GET /currencies.
400 method_not_enabled não Método não habilitado para a moeda (hoje só pix em BRL) Não ofereça o método.
400 rail_not_enabled não POST /sandbox/bank-transfers/incoming: nenhum trilho bancário está habilitado Não use a rota.
401 invalid_signature não Assinatura não confere, chave inexistente, nonce mal formado, cabeçalho ausente Autenticação HMAC.
401 timestamp_out_of_window não Fora de ±300 s, ou timestamp fora da forma canônica Corrija o relógio; segundos.
401 replayed_nonce não Nonce já usado com essa chave Gere nonce novo a cada tentativa.
401 unknown_key não key_id mal formado, de outro tipo de operação, ou chave revogada Confira a chave e o host.
403 ip_not_allowed não IP de origem fora da lista da operação A lista não é aplicada hoje; você não deve ver este código.
403 insufficient_scope não A chave não tem o escopo da rota, ou o método não existe Use a chave certa.
403 operation_suspended não Operação suspensa pelo Dracma Perguntas frequentes.
404 not_found não O objeto não existe nesta operação (inclusive de outra operação, nunca 403); rota inexistente; GET em rota só POST Confira o id e a chave.
409 idempotency_key_reused não Mesma chave com pedido diferente Bug do seu lado.
409 idempotency_in_progress sim A mesma chave ainda executa (Retry-After: 1) Espere e repita.
409 duplicate_external_id não external_id já usado com outra chave Leia existing e concilie.
409 invalid_state_transition não Ação que o estado atual não permite (cancelar saque já enviado, cancelar depósito pago, recall de saque PIX) Consulte o estado atual.
409 day_open sim GET /reconciliation de um dia que ainda não fechou; detail traz o instante de abertura; Retry-After em segundos Agende após o instante; não repita em laço.
412 etag_mismatch não If-Match desatualizado em POST /rules/change-requests Releia GET /rules e reenvie.
413 validation_failed não Corpo acima de 64 KiB (errors[0] = {field: "body", reason: "too_large"}) Reduza o corpo.
422 amount_below_minimum, amount_above_maximum não Limite por transação Mostre o limite ao jogador.
422 document_invalid não Documento do jogador inválido (CPF/CNPJ, dígitos verificadores) Revise o cadastro do jogador.
422 document_mismatch não Documento diferente do já fixado para esse external_ref Revise o cadastro do jogador.
422 player_blocked, destination_blocked não Listas de bloqueio (mensagem genérica de propósito) Não revele o motivo ao jogador.
422 velocity_exceeded não Limite de frequência ou volume Mostre "tente mais tarde".
422 insufficient_operation_balance não Só saque: sem caixa livre na operação Alerte o financeiro; nova tentativa é operação nova (external_id novo).
422 fx_not_enabled não POST /fx/quotes: câmbio desligado em toda operação Não use câmbio.
429 rate_limited sim Limite de taxa (Retry-After) Limites e quotas.
500 internal_error sim Erro interno (detail: "erro interno; informe o trace_id") Repita com a mesma chave; se persistir, abra chamado com o trace_id.
503 service_unavailable sim Dependência do Dracma fora, ou queda simulada em operação Teste Espere o Retry-After e repita.

Os motivos exatos dos 422 de dinheiro (limites, listas, frequência) dependem das regras da operação: consulte GET /v1/rules e Decisão, limites e regras.

Reservado no contrato

Sete códigos do contrato 1.1.0 existem no catálogo mas a API não os emite hoje: psp_unavailable, outside_operating_hours, fx_quote_expired, fx_quote_mismatch, corridor_not_allowed, beneficiary_bank_unreachable e recall_window_expired. Quando um PSP está fora você recebe service_unavailable. Trate-os como qualquer code desconhecido: pela classe HTTP e pelo retryable.

Motivos de validação: errors[].reason

Em 400 validation_failed, cada item de errors[] traz o field (por exemplo player.document.number; para cabeçalhos, o nome do cabeçalho; para o corpo inteiro, body) e um reason. A lista é aberta: trate um reason desconhecido como erro de validação genérico. São 22 motivos emitidos hoje:

reason Quando
unknown_field Campo fora do schema (inclusive status, version, paid_amount, decision, operation_id aninhado)
duplicate_key Chave JSON repetida em qualquer nível, inclusive variando a caixa
invalid_utf8 Corpo com UTF-8 inválido
syntax JSON malformado
trailing_data Conteúdo depois do objeto JSON
type Tipo errado (número onde vai string, por exemplo)
invalid Corpo inválido por outro motivo (por exemplo amount fora do formato de Money)
invalid_json Corpo de POST /fx/quotes que não é JSON válido
required Campo obrigatório ausente; também Idempotency-Key ausente ou fora do formato
format Formato inválido: external_id, external_ref, documento, país, IP, e-mail, updated_since, cursor, after
enum Valor fora do enum: method, player.document.type, destination.pix.key_type, status de saque
range Faixa: expires_in_seconds, metadata com mais de 20 chaves, limit fora de 1 a 200, name/device_id longos no saque
one_of destination com zero ou mais de um método
method_mismatch O método do destination difere de method
currency_mismatch paid_amount.currency diferente da moeda do depósito em /sandbox/deposits/{id}/pay
unknown after de GET /events que não é um evento da sua operação
too_large Corpo acima de 64 KiB (status 413)
required_by_operation Campo que a operação exige e o contrato deixa opcional (por exemplo player.email)
max_length player.email com mais de 254 caracteres, player.name com mais de 120 no depósito; justification do recall com mais de 1000
reserved_domain_required Em operação Teste, player.email que não é de domínio reservado (.test, .invalid, .example)
rail_not_enabled Também aparece como motivo em POST /sandbox/bank-transfers/incoming
length justification de POST /rules/change-requests fora de 1 a 1000 caracteres

status com valor desconhecido e limit fora da faixa aparecem como format em GET /deposits e como enum e range em GET /withdrawals; não dependa da diferença. A resposta de validação de GET /events, /reconciliation, /balances, /rules e /players/{ref}/limits traz message fixa ("parâmetro inválido").

Exemplo de campo desconhecido no corpo (o servidor recusa additionalProperties):

{
  "type": "https://docs.dracma.test/errors/validation_failed",
  "title": "validation_failed",
  "status": 400,
  "code": "validation_failed",
  "trace_id": "4bf92f3577b34da6a3ce929d0e0e4736",
  "retryable": false,
  "errors": [
    { "field": "paid_amount", "message": "campo não permitido", "reason": "unknown_field" }
  ]
}

Respostas da borda (sem problem+json)

Antes de a requisição chegar à API, a entrada pública do Dracma pode responder sozinha. Essas respostas não têm corpo problem+json:

Resposta Quando
404 sem corpo Qualquer caminho fora de /v1/* (/health, /metrics, /); /v1/sandbox/* em operação Real (nunca existe)
413 sem corpo Corpo acima de 1 MB (a API só chega a avaliar até 64 KiB)

Falhas de TLS ou de rede também não têm corpo. O conector deve tratar uma resposta que não é JSON como falha de transporte: repita com a mesma Idempotency-Key.

Rotas, métodos e erros genéricos

  • Rota inexistente em /v1 com credencial válida: 404 not_found. A autenticação vem antes: com assinatura inválida a resposta é 401.
  • Método errado. Um GET em uma rota que só aceita POST volta 404 not_found. Qualquer método além de GET, HEAD e POST, ou um POST em rota que não existe, volta 403 insufficient_scope, porque o escopo é conferido antes da rota.
  • Erro genérico do servidor HTTP (caminho malformado, cabeçalho inválido, 4xx que não é do Dracma) vira 400 validation_failed; o corpo não traz errors[].
  • Erro inesperado vira 500 internal_error, sempre opaco. O motivo real fica no log do Dracma, ligado ao trace_id.

Modo degradado

O Dracma não "aceita sem checar": se uma dependência está fora, a rota recusa ou desvia para revisão. A tabela mostra o que você vê.

Dependência fora POST /deposits POST /withdrawals Leituras
Motor de regras 503 service_unavailable, nenhum depósito criado 201 com o saque em under_review e RULES_UNAVAILABLE em decision.reasons (revisão manual) GET /rules e GET /players/{ref}/limits: 503
Livro de saldos segue segue; o motor pode registrar BALANCE_UNAVAILABLE e mandar para revisão, nunca liberar GET /balances e GET /reconciliation: 503
Serviço de webhooks segue; os eventos esperam e são entregues depois segue GET /reconciliation, GET /sandbox/webhooks e redeliver: 503; GET /events continua
Executor de pagamentos 202 com o depósito em created até a cobrança sair; acompanhe por webhook ou GET /deposits/{id} 201; o envio ao banco é retomado depois ações de sandbox: 503 com Retry-After: 5
Armazenamento de nonces, cofre de segredos, limitador, idempotência 503 com Retry-After: 1, antes de qualquer efeito idem idem

Em todos os 503 a regra é a mesma: nenhum efeito parcial; espere o Retry-After e repita com a mesma Idempotency-Key. Um saque em under_review por RULES_UNAVAILABLE não é erro: veja Decisão, limites e regras.

Exemplos

Recusa por limite de taxa. Repare em retryable: true e no Retry-After:

HTTP/1.1 429 Too Many Requests
Content-Type: application/problem+json
Retry-After: 2
Dracma-Trace-Id: 4bf92f3577b34da6a3ce929d0e0e4736
{
  "type": "https://docs.dracma.test/errors/rate_limited",
  "title": "rate_limited",
  "status": 429,
  "code": "rate_limited",
  "trace_id": "4bf92f3577b34da6a3ce929d0e0e4736",
  "retryable": true
}

Um tratador mínimo, em pseudocódigo:

resposta = enviar(pedido)
se resposta.status é 2xx:               gravar e seguir
se resposta.json.retryable ou status é 5xx ou 429 ou erro de rede:
                                        esperar Retry-After (ou backoff com jitter) e repetir com a MESMA Idempotency-Key
senão:                                  registrar code, errors[] e trace_id; corrigir ou encerrar o fluxo

Erros e casos de borda

  • Recusa de negócio não cria objeto. Um 4xx em POST /deposits ou POST /withdrawals não cria depósito nem saque. No saque, devolva a reserva ao jogador ao receber um 4xx conclusivo, e só então. A mesma chave devolve a mesma recusa por 30 dias; uma nova tentativa é uma operação nova, com external_id e chave novos.
  • Erro de validação também fica gravado. Um 400 por corpo inválido é guardado com a chave; corrigir o corpo exige uma chave nova (mesmo corpo com a chave antiga devolve o mesmo 400).
  • 409 duplicate_external_id traz o objeto existente em existing: use-o para conciliar em vez de criar outro.
  • Motivos de revisão não são erros. Quando um saque é aceito mas vai para under_review, os motivos vêm em decision.reasons, não em errors[].

Veja também