Referência rápida do integrador

Uma página com as regras obrigatórias, os endpoints, os erros e os estados — no formato que um humano consulta com o conector aberto e que um agente de código usa como especificação. Tudo aqui foi conferido no código do Dracma em 2026-09-27; o detalhe está nas páginas linkadas e as diferenças para o contrato em divergencias.md.

1. Regras do integrador (MUST / NEVER)

Palavras no sentido da RFC 2119. Cada regra tem um id estável.

Chamadas ao Dracma

  • R01 MUST chamar o Dracma só do servidor do conector. NEVER do navegador, do app do jogador ou de código com segredo em bundle (NEXT_PUBLIC_*, VITE_*).
  • R02 MUST assinar toda requisição: pedido canônico de 6 linhas sem \n final, path com /v1 e query exatamente como enviada, sha256 do corpo cru, HMAC-SHA256 com o segredo da chave (autenticacao.md).
  • R03 MUST gerar nonce de 32 hex minúsculos por CSPRNG, novo em toda requisição, inclusive em retry. NEVER Math.random()/math/rand.
  • R04 MUST mandar Dracma-Timestamp em segundos e manter o relógio por NTP (desvio > 5 s = alerta).
  • R05 MUST serializar o corpo uma vez e enviar exatamente os bytes assinados.
  • R06 MUST reproduzir os vetores de exemplos/vetores-assinatura.json byte a byte no CI, inclusive recusar o negativo.
  • R07 MUST usar timeout de cliente de 10 s.

Idempotência e repetição

  • R08 MUST mandar Idempotency-Key em todo POST (inclusive cancel e /sandbox/*).
  • R09 MUST usar Idempotency-Key = external_id em POST /deposits e POST /withdrawals, e gravar os dois no banco antes da chamada (outbox).
  • R10 NEVER gerar Idempotency-Key nova para repetir um POST de dinheiro. Repetição = mesma chave, mesmos bytes, timestamp/nonce/assinatura novos.
  • R11 MUST repetir 5xx, 429, 409 idempotency_in_progress, timeout e erro de rede com a mesma chave, respeitando Retry-After (backoff exponencial com jitter).
  • R12 NEVER repetir um 4xx (é decisão). Tentar de novo depois de uma recusa = operação nova com external_id novo.
  • R13 MUST tratar code desconhecido pela classe HTTP (4xx não repete; 5xx/429 repete).

Webhooks e fetch-back

  • R14 MUST verificar a assinatura do webhook sobre o corpo cru, antes de interpretar o JSON, aceitando qualquer v1 com qualquer segredo ativo (lista de 1 ou 2), em tempo constante; janela de 300 s; Dracma-Timestamp = t=.
  • R15 MUST responder 400 se Dracma-Event-Id ≠ id do corpo e deduplicar por Dracma-Event-Id (≥ 7 dias): duplicata → 200 sem efeito.
  • R16 MUST fazer fetch-back (GET /deposits/{id} ou GET /withdrawals/{id} com a própria credencial) antes de mover dinheiro. NEVER creditar, debitar ou devolver reserva pelo corpo do webhook.
  • R17 MUST conferir no fetch-back: external_id é seu; amount.amount, amount.currency e player_ref idênticos ao registrado. Divergência → não move dinheiro, revisão, alerta.
  • R18 MUST ignorar estado com version ≤ a última aplicada (responde 200).
  • R19 MUST responder ao webhook em ≤ 10 s; 503 quando o fetch-back falhar por rede/5xx (o Dracma reentrega).
  • R20 MUST rodar um job de GET /events?after=<último evt aplicado> a cada 5 minutos, com cursor persistido, passando cada evento pelo mesmo caminho do webhook.

Dinheiro

  • R21 MUST creditar depósito só em paid, uma vez (idempotente por external_id), pelo valor que você registrou na criação.
  • R22 NEVER mover dinheiro em estado, tipo de evento, method, kind ou motivo desconhecido: trate como não-terminal, responda 200, confira depois (Contrato §6.0).
  • R23 MUST reservar (debitar) o saldo do jogador antes de POST /withdrawals, na mesma transação que grava o saque local.
  • R24 MUST devolver a reserva só em rejected, failed, cancelled (lidos por fetch-back) ou em 4xx conclusivo na criação. NEVER em timeout, 5xx ou 429.
  • R25 NEVER debitar/recreditar automaticamente em refunded, chargeback, returned, recalled, paid_partial ou pagamento depois de expirar: needs_decision + alerta, decisão humana.
  • R26 MUST representar dinheiro como string de inteiro na menor unidade ("5000" = R$ 50,00). NEVER float.

Dados do jogador

  • R27 MUST usar player.external_ref opaco e imutável. NEVER e-mail, telefone ou documento como id.
  • R28 MUST mandar player.ip (IP do jogador) em todo saque; sem ele o saque nunca é automático.
  • R29 MUST ler as exigências por operação em GET /currencies (method_details[].deposit_requirements) e mandar os campos exigidos (ex.: player.name + player.email com Mercado Pago; player.name com MaximaxPay), vindos do cadastro — NEVER inventados.
  • R30 NEVER usar, fora de produção, e-mail ou documento de pessoa real (inclusive o seu). E-mail de teste só em .test, .invalid ou .example; CPF de teste gerado com dígito válido.
  • R31 NEVER mostrar ao jogador o motivo de bloqueio ou de revisão (listas, PAYER_MISMATCH…).

Segredos, rede e operação

  • R32 MUST guardar segredo de chamada, segredo de webhook e chave privada mTLS em cofre/arquivo 0400 do usuário do serviço. NEVER em repositório, imagem, log, ticket ou variável de front.
  • R33 MUST cadastrar todos os IPs de saída na allowlist antes de o tráfego mudar e usar mTLS em produção.
  • R34 MUST registrar Dracma-Trace-Id em todo log de chamada. NEVER logar Dracma-Signature inteiro, segredo, documento completo ou chave PIX em claro.
  • R35 MUST reconciliar diariamente (GET /reconciliation, depois das 02:00 UTC), conferir digest e assinatura e tratar divergência como incidente — NEVER "consertar" o livro para bater.
  • R36 MUST apontar homologação para o sandbox (key_test_) e produção para produção (key_live_).

2. Endpoints (código atual)

Base: https://<host>/v1 (hosts reais no onboarding). Todos exigem a assinatura §3; todo POST exige Idempotency-Key.

Método e path Escopo Sucesso Erros típicos Página
GET /ping qualquer chave 200 {ok, operation_id, environment, server_time} 401, 403 autenticacao.md
GET /currencies read 200 {data: [Moeda + method_details]} 503 depositos.md
POST /deposits deposits:write 201 (pending, ou failed) / 202 (created) 400, 409, 422, 429, 503 depositos.md
GET /deposits read (listagem 5/s) 200 {data, next_cursor} 400 depositos.md
GET /deposits/{id} read 200 Deposito 404 depositos.md
POST /deposits/{id}/cancel deposits:write 200 Deposito (cancelled) 404, 409 depositos.md
POST /withdrawals withdrawals:write 201 Saque (approved, under_review, requested) 400, 409, 422, 503 saques.md
GET /withdrawals read (listagem 5/s) 200 {data, next_cursor} 400 saques.md
GET /withdrawals/{id} read 200 Saque 404 saques.md
POST /withdrawals/{id}/cancel withdrawals:write 200 Saque (cancelled) 404, 409 saques.md
GET /players/{external_ref}/limits?currency= read 200 LimitesJogador 400, 404, 503 saques.md
GET /balances read 200 {data: [{available, pending_in, pending_out, as_of}]} 503 saques.md
GET /events?after=&object_id=&limit= read (listagem 5/s) 200 {data: [Evento], next_cursor} 400 webhooks.md
GET /reconciliation?date=&currency=&cursor=&limit= read 200 (última página com totals, digest e header Dracma-Signature) 400, 409 day_open, 503 reconciliacao.md
GET /rules read 200 conjunto vigente + ETag 404, 503 saques.md
POST /rules/change-requests rules:propose 202 {id, status: pending_approval} 412 etag_mismatch autenticacao.md
POST /fx/quotes deposits:write ou withdrawals:write — 422 fx_not_enabled sempre erros-e-idempotencia.md
GET /fx/quotes/{id} read — 404 sempre (nenhuma cotação é emitida) referencia/openapi.md
GET /receipts?status=&updated_since=&limit= read (listagem 5/s) 200 {data: [], next_cursor: null} 400 referencia/openapi.md
GET /receipts/{id} read — 404 sempre (sem trilho bancário) referencia/openapi.md
POST /withdrawals/{id}/recall withdrawals:write — 400, 404, 409 invalid_state_transition (saque PIX não tem recall) saques.md
POST /sandbox/deposits/{id}/pay deposits:write 200 Deposito 400, 404, 409, 503 sandbox.md
POST /sandbox/deposits/{id}/expire deposits:write 200 Deposito (expired) 404, 409 sandbox.md
POST /sandbox/withdrawals/{id}/{approve,reject,pay,fail,return} withdrawals:write 200 Saque (estado atual) 400, 404, 409, 503 sandbox.md
POST /sandbox/bank-transfers/incoming deposits:write — 400 rail_not_enabled (corpo válido), 400 validation_failed referencia/openapi.md
POST /sandbox/outage qualquer …:write 200 {down} 400 sandbox.md
GET /sandbox/webhooks read 200 log de entregas 503 sandbox.md
POST /sandbox/webhooks/redeliver qualquer …:write 200 {event_id, scheduled} 400, 404, 503 sandbox.md

As rotas wire/SWIFT/câmbio do contrato 1.1.0 existem no recorte PIX (linhas acima): nenhuma move dinheiro nem cria objeto. Em produção, todo /sandbox/* responde 404.

3. Erros (code estável) e o que fazer

HTTP code Repetir? Ação
400 validation_failed não corrija o pedido (errors[].field/reason)
400 currency_not_enabled / method_not_enabled não configuração da operação
401 invalid_signature / timestamp_out_of_window / replayed_nonce / unknown_key não assinatura, relógio, nonce, chave/ambiente
403 ip_not_allowed / insufficient_scope / operation_suspended não allowlist, escopo, suporte
404 not_found não id ou operação errados (objeto alheio também é 404)
409 idempotency_key_reused não bug: chave reusada com outro corpo
409 duplicate_external_id não use o objeto em existing
409 invalid_state_transition não consulte o estado atual
409 idempotency_in_progress sim mesma chave depois do Retry-After
409 day_open sim, depois agende para depois do Retry-After
412 etag_mismatch não releia GET /rules
413 validation_failed (too_large) não corpo ≤ 64 KiB
422 amount_below_minimum / amount_above_maximum / document_invalid / document_mismatch / player_blocked / destination_blocked / velocity_exceeded / insufficient_operation_balance / fx_not_enabled não recusa de negócio; sem objeto; saque: devolva a reserva
429 rate_limited sim mesma chave depois do Retry-After
500 internal_error sim mesma chave; chamado com o trace_id se persistir
503 service_unavailable (e, no futuro, psp_unavailable / outside_operating_hours) sim mesma chave depois do Retry-After

Motivos de validação (errors[].reason) e o catálogo completo: erros-e-idempotencia.md.

4. Estados e efeito no livro do integrador

Depósito

Estado Terminal? Efeito
created não nenhum
pending não nenhum (mostre o PIX)
under_review não nenhum
paid para o crédito, sim credita uma vez
expired / cancelled / failed / returned sim nenhum (mesmo com paid_amount preenchido)
refunded / chargeback sim débito por política: needs_decision
pending_compliance / frozen não nenhum
paid_partial sim nenhum automático: needs_decision
desconhecido tratar como não nenhum

Saque (reserva feita antes do POST)

Estado Terminal? Efeito na reserva
requested / under_review / approved / processing não mantém
paid sim (pode ir a returned) finaliza
rejected / failed / cancelled sim devolve
returned / recalled sim nenhum automático: needs_decision
pending_compliance / sent / in_transit / recall_requested não mantém
desconhecido tratar como não mantém

Eventos emitidos hoje

deposit.pending, deposit.under_review, deposit.paid, deposit.expired, deposit.cancelled, deposit.failed, deposit.returned; withdrawal.under_review, withdrawal.approved, withdrawal.processing, withdrawal.paid, withdrawal.rejected, withdrawal.failed, withdrawal.cancelled, withdrawal.returned. Só no sandbox (ações refund/chargeback, D-D3): deposit.refunded, deposit.chargeback. Do contrato, ainda sem gatilho: v1.1.0 (wire/SWIFT e screening): deposit.pending_compliance, deposit.frozen, deposit.paid_partial, withdrawal.pending_compliance, withdrawal.sent, withdrawal.in_transit, withdrawal.recall_requested, withdrawal.recall_rejected, withdrawal.recalled, receipt.*.