openapi: 3.1.0 info: title: Dracma — API de pagamentos para operadores (v1) version: 1.1.0 summary: Gateway oficial multitenant de cash-in/cash-out (PIX, cartão, cripto, wire, SWIFT) com motor de regras. description: | Contrato NORMATIVO entre um operador (ex.: a bet "Nero") e o Dracma, o gateway oficial que intermedeia todos os pagamentos. A semântica que o YAML não expressa — algoritmo de assinatura, janela anti-replay, idempotência, máquinas de estado, ordem de webhooks, fetch-back — está em `CONTRATO.md`, que é parte deste contrato. Convenções que valem para TODO o documento: - Dinheiro é SEMPRE `Money`: `amount` é string de inteiro na MENOR unidade da moeda (centavo, satoshi, wei) e `currency` é o código do registro de moedas (`GET /v1/currencies`). Nunca float, nunca decimal com vírgula. - Tempo é RFC 3339 em UTC (`2026-09-26T15:04:05Z`). - IDs do Dracma são opacos com prefixo (`dep_`, `wdr_`, `evt_`, `rcn_`, `chg_`; v1.1: `rcv_` recebimento bancário, `fxq_` cotação de câmbio). O operador nunca deriva nada do formato. - Cotação/taxa de câmbio é decimal em STRING (`"5.4321"`), nunca float. Data de liquidação é `date` (AAAA-MM-DD) no calendário do país do trilho. - LEITOR TOLERANTE (v1.1.0, CONTRATO.md §6.0): o operador DEVE aceitar valor desconhecido de `status`, `type` de evento, `Metodo`, `rail`, `kind` e campo desconhecido; estado/evento desconhecido NUNCA move dinheiro no operador — é não-terminal e se confere depois pelo fetch-back. - O TENANT e a OPERAÇÃO vêm da credencial (certificado mTLS + chave de API), NUNCA do corpo. Um campo `operation_id` no corpo é ignorado se existir. - Erro é `application/problem+json` (RFC 9457) com `code` estável. - Rotas `/sandbox/*` só existem fora de produção (ver CONTRATO.md §9). DOCUMENTO SERVIDO pelo `dracma_api` (`GET /openapi.yaml`, público, sem autenticação): é a cópia do contrato 1.1.0 recortada ao que a API IMPLEMENTA hoje (PIX) mais os acréscimos ADITIVOS já em produção (`Moeda.method_details`, motivos de `Problema.errors[].reason`, 404 no cancel, `Idempotency-Key` nas rotas `/sandbox/*`, ações de sandbox `refund`/`chargeback` do depósito). As rotas da superfície wire/SWIFT/câmbio da v1.1.0 estão servidas no recorte PIX (P056): nenhuma operação tem câmbio nem trilho bancário habilitado, então `GET /fx/quotes/{id}` e `GET /receipts/{id}` → 404, `GET /receipts` → página vazia, `POST /withdrawals/{id}/recall` → 409 `invalid_state_transition` (saque PIX não tem recall) e `POST /sandbox/bank-transfers/incoming` → 400 `rail_not_enabled`. Cada rota daqui está registrada no roteador v1 e vice-versa (teste de compatibilidade no CI). O diff contra o contrato do briefing fica em `dracma_api/docs/openapi-divergencias.md`. license: name: Proprietário — uso interno entre as partes servers: - url: https://api.dracma.example/v1 description: Produção (mTLS obrigatório) - url: https://sandbox.dracma.example/v1 description: Sandbox (nenhum dinheiro real se move; sink por construção) security: - mtls: [] assinaturaHMAC: [] tags: - name: depositos - name: saques - name: jogadores - name: operacao - name: regras - name: reconciliacao - name: cambio description: v1.1.0 — cotações de câmbio (FX) para depósito/saque com moeda de pagamento diferente da carteira. - name: recebimentos description: v1.1.0 — créditos bancários (wire/SWIFT) recebidos sem casar com um depósito. Somente leitura pela API. - name: sandbox description: Superfície de controle SÓ de não-produção. Em prd responde 404. paths: /ping: get: tags: [operacao] operationId: ping summary: Verifica conectividade e credencial (sem efeito). responses: "200": description: Credencial válida. content: application/json: schema: type: object required: [ok, operation_id, environment, server_time] properties: ok: { type: boolean, const: true } operation_id: { type: string } environment: { type: string, enum: [production, sandbox] } server_time: { type: string, format: date-time } "401": { $ref: "#/components/responses/NaoAutenticado" } /currencies: get: tags: [operacao] operationId: listarMoedas summary: Registro de moedas e ativos habilitados para a operação. responses: "200": description: Moedas habilitadas. content: application/json: schema: type: object required: [data] properties: data: type: array items: { $ref: "#/components/schemas/Moeda" } /deposits: post: tags: [depositos] operationId: criarDeposito summary: Cria uma intenção de depósito e devolve as instruções de pagamento. description: | Idempotente por `Idempotency-Key` (obrigatório) E por `external_id` (único por operação). A mesma chave com o mesmo corpo devolve a MESMA resposta; com corpo diferente, 409 `idempotency_key_reused`. Pode responder 202 com `status=created` e sem `instructions` quando o PSP ainda não devolveu o PIX/checkout: o operador consulta ou espera o webhook `deposit.pending`. v1.1.0 — `wire`/`swift`: as instruções trazem a conta de recebimento e uma `reference` OBRIGATÓRIA que o pagador DEVE pôr na transferência; sem ela o crédito vira `Recebimento` não casado (CONTRATO.md §6.4). parameters: - $ref: "#/components/parameters/IdempotencyKey" requestBody: required: true content: application/json: schema: { $ref: "#/components/schemas/CriarDeposito" } examples: pix: value: external_id: dep_0192a3b4-5c6d-7e8f-9a0b-1c2d3e4f5a6b player: external_ref: 0192a3b4-0000-7000-8000-000000000001 document: { type: CPF, number: "12345678909", country: BR } name: Jogador Teste email: jogador1@test.nerobet.lol amount: { amount: "5000", currency: BRL } method: pix expires_in_seconds: 1800 sepa: summary: v1.1.0 — depósito por transferência SEPA (EUR) value: external_id: dep_0192a3b4-5c6d-7e8f-9a0b-1c2d3e4f5a70 player: external_ref: 0192a3b4-0000-7000-8000-000000000002 document: { type: NATIONAL_ID, number: "X1234567", country: ES } name: Jugador Prueba amount: { amount: "10000", currency: EUR } method: wire method_options: wire: { rail: sepa_ct, reference_valid_days: 10 } responses: "201": description: Depósito criado com instruções. content: application/json: schema: { $ref: "#/components/schemas/Deposito" } "202": description: Depósito aceito; instruções ainda não disponíveis. content: application/json: schema: { $ref: "#/components/schemas/Deposito" } "400": { $ref: "#/components/responses/Invalido" } "401": { $ref: "#/components/responses/NaoAutenticado" } "403": { $ref: "#/components/responses/Proibido" } "409": { $ref: "#/components/responses/Conflito" } "422": { $ref: "#/components/responses/RegraDeNegocio" } "429": { $ref: "#/components/responses/Limitado" } "503": { $ref: "#/components/responses/Indisponivel" } get: tags: [depositos] operationId: listarDepositos summary: Lista depósitos da operação (cursor). parameters: - { name: external_id, in: query, schema: { type: string } } - { name: player_ref, in: query, schema: { type: string } } - { name: status, in: query, schema: { $ref: "#/components/schemas/StatusDeposito" } } - { name: updated_since, in: query, schema: { type: string, format: date-time } } - $ref: "#/components/parameters/Cursor" - $ref: "#/components/parameters/Limite" responses: "200": description: Página de depósitos. content: application/json: schema: type: object required: [data, next_cursor] properties: data: { type: array, items: { $ref: "#/components/schemas/Deposito" } } next_cursor: { type: [string, "null"] } /deposits/{id}: get: tags: [depositos] operationId: obterDeposito summary: Estado autoritativo de um depósito (usado no fetch-back do webhook). parameters: - $ref: "#/components/parameters/IdDeposito" responses: "200": description: Depósito. content: application/json: schema: { $ref: "#/components/schemas/Deposito" } "404": { $ref: "#/components/responses/NaoEncontrado" } /deposits/{id}/cancel: post: tags: [depositos] operationId: cancelarDeposito summary: Cancela um depósito ainda não pago. Idempotente. parameters: - $ref: "#/components/parameters/IdDeposito" - $ref: "#/components/parameters/IdempotencyKey" responses: "200": description: Depósito cancelado (ou já estava cancelado). content: application/json: schema: { $ref: "#/components/schemas/Deposito" } "409": { $ref: "#/components/responses/Conflito" } "404": { $ref: "#/components/responses/NaoEncontrado" } /withdrawals: post: tags: [saques] operationId: criarSaque summary: Pede um saque; o motor de regras decide automático ou revisão. description: | O operador JÁ reservou (debitou) o valor do jogador antes de chamar. A resposta traz `status` = `approved`/`processing` (automático) ou `under_review` (manual), com `decision.reasons`. Se `review_requested` vier `true`, o Dracma DEVE mandar para revisão manual — o operador só pode tornar a decisão MAIS restritiva, nunca menos. `player_context` é INFORMATIVO: o Dracma nunca automatiza com base nele; a regra X× usa os depósitos que o PRÓPRIO Dracma liquidou. parameters: - $ref: "#/components/parameters/IdempotencyKey" requestBody: required: true content: application/json: schema: { $ref: "#/components/schemas/CriarSaque" } examples: pix: value: external_id: wdr_0192a3b4-5c6d-7e8f-9a0b-1c2d3e4f5a6c player: external_ref: 0192a3b4-0000-7000-8000-000000000001 document: { type: CPF, number: "12345678909", country: BR } name: Jogador Teste amount: { amount: "5000", currency: BRL } method: pix destination: pix: { key_type: cpf, key: "12345678909" } review_requested: false swift: summary: v1.1.0 — saque SWIFT em USD de carteira USD value: external_id: wdr_0192a3b4-5c6d-7e8f-9a0b-1c2d3e4f5a71 player: external_ref: 0192a3b4-0000-7000-8000-000000000003 document: { type: PASSPORT, number: "P1234567", country: GB } name: Test Player amount: { amount: "250000", currency: USD } method: swift destination: swift: charge_bearer: SHA account: holder_name: Test Player holder_type: individual holder_address: { line1: "1 Example Street", city: London, postal_code: "EC1A 1AA", country: GB } country: GB iban: GB82WEST12345698765432 bic: WESTGB2LXXX purpose: { code: gaming_winnings } review_requested: false responses: "201": description: Saque registrado com decisão. content: application/json: schema: { $ref: "#/components/schemas/Saque" } "400": { $ref: "#/components/responses/Invalido" } "401": { $ref: "#/components/responses/NaoAutenticado" } "403": { $ref: "#/components/responses/Proibido" } "409": { $ref: "#/components/responses/Conflito" } "422": { $ref: "#/components/responses/RegraDeNegocio" } "429": { $ref: "#/components/responses/Limitado" } "503": { $ref: "#/components/responses/Indisponivel" } get: tags: [saques] operationId: listarSaques summary: Lista saques da operação (cursor). parameters: - { name: external_id, in: query, schema: { type: string } } - { name: player_ref, in: query, schema: { type: string } } - { name: status, in: query, schema: { $ref: "#/components/schemas/StatusSaque" } } - { name: updated_since, in: query, schema: { type: string, format: date-time } } - $ref: "#/components/parameters/Cursor" - $ref: "#/components/parameters/Limite" responses: "200": description: Página de saques. content: application/json: schema: type: object required: [data, next_cursor] properties: data: { type: array, items: { $ref: "#/components/schemas/Saque" } } next_cursor: { type: [string, "null"] } /withdrawals/{id}: get: tags: [saques] operationId: obterSaque summary: Estado autoritativo de um saque (usado no fetch-back do webhook). parameters: - $ref: "#/components/parameters/IdSaque" responses: "200": description: Saque. content: application/json: schema: { $ref: "#/components/schemas/Saque" } "404": { $ref: "#/components/responses/NaoEncontrado" } /withdrawals/{id}/cancel: post: tags: [saques] operationId: cancelarSaque summary: Cancela um saque que ainda NÃO foi enviado ao PSP (requested/under_review/approved). parameters: - $ref: "#/components/parameters/IdSaque" - $ref: "#/components/parameters/IdempotencyKey" responses: "200": description: Saque cancelado (ou já estava). content: application/json: schema: { $ref: "#/components/schemas/Saque" } "409": { $ref: "#/components/responses/Conflito" } "404": { $ref: "#/components/responses/NaoEncontrado" } /withdrawals/{id}/recall: post: tags: [saques] operationId: pedirRecallDeSaque summary: v1.1.0 — Pede o recall (devolução) de um saque wire/SWIFT já enviado. description: | Só para `wire`/`swift` em `sent`, `in_transit` ou `paid`, dentro da janela de recall do trilho (METODOS.md). Leva a `recall_requested`; o banco do beneficiário decide: `recalled` (o dinheiro volta ao CAIXA DA OPERAÇÃO, não ao jogador — DECISOES D18) ou volta ao estado anterior com evento `withdrawal.recall_rejected`. Recall é PEDIDO, não garantia. Escopo `withdrawals:write`. Idempotente. parameters: - $ref: "#/components/parameters/IdSaque" - $ref: "#/components/parameters/IdempotencyKey" requestBody: required: true content: application/json: schema: type: object required: [reason_code] additionalProperties: false properties: reason_code: { type: string, enum: [duplicate, wrong_beneficiary, wrong_amount, fraud_suspected, other] } justification: { type: string, maxLength: 1000 } responses: "202": description: Pedido de recall registrado. content: application/json: schema: { $ref: "#/components/schemas/Saque" } "400": { $ref: "#/components/responses/Invalido" } "404": { $ref: "#/components/responses/NaoEncontrado" } "409": { $ref: "#/components/responses/Conflito" } "422": { $ref: "#/components/responses/RegraDeNegocio" } /fx/quotes: post: tags: [cambio] operationId: criarCotacao summary: v1.1.0 — Cota câmbio para um depósito/saque (sem efeito em dinheiro). description: | Devolve uma `Cotacao` válida até `expires_at`. Usar o `id` em `fx_quote_id` do depósito/saque trava a taxa (CONTRATO.md §6.5). Exige escopo `deposits:write` ou `withdrawals:write`, conforme `purpose`. Não consome saldo; cotação não usada simplesmente expira. parameters: - $ref: "#/components/parameters/IdempotencyKey" requestBody: required: true content: application/json: schema: { $ref: "#/components/schemas/PedidoCotacao" } responses: "201": description: Cotação emitida. content: application/json: schema: { $ref: "#/components/schemas/Cotacao" } "400": { $ref: "#/components/responses/Invalido" } "401": { $ref: "#/components/responses/NaoAutenticado" } "403": { $ref: "#/components/responses/Proibido" } "422": { $ref: "#/components/responses/RegraDeNegocio" } "503": { $ref: "#/components/responses/Indisponivel" } /fx/quotes/{id}: get: tags: [cambio] operationId: obterCotacao summary: v1.1.0 — Consulta uma cotação de câmbio. parameters: - { name: id, in: path, required: true, schema: { type: string, pattern: "^fxq_[A-Za-z0-9]+$" } } responses: "200": description: Cotação. content: application/json: schema: { $ref: "#/components/schemas/Cotacao" } "404": { $ref: "#/components/responses/NaoEncontrado" } /receipts: get: tags: [recebimentos] operationId: listarRecebimentos summary: v1.1.0 — Créditos bancários recebidos que NÃO casaram com um depósito. description: | Somente leitura. Casar um recebimento com um depósito ou devolvê-lo é ação do PAINEL (PAINEL.md §5.1), nunca da API: uma credencial vazada não pode apontar dinheiro recebido para um jogador escolhido. parameters: - { name: status, in: query, schema: { $ref: "#/components/schemas/StatusRecebimento" } } - { name: updated_since, in: query, schema: { type: string, format: date-time } } - $ref: "#/components/parameters/Cursor" - $ref: "#/components/parameters/Limite" responses: "200": description: Página de recebimentos. content: application/json: schema: type: object required: [data, next_cursor] properties: data: { type: array, items: { $ref: "#/components/schemas/Recebimento" } } next_cursor: { type: [string, "null"] } "400": { $ref: "#/components/responses/Invalido" } /receipts/{id}: get: tags: [recebimentos] operationId: obterRecebimento summary: v1.1.0 — Consulta um recebimento bancário não casado. parameters: - { name: id, in: path, required: true, schema: { type: string, pattern: "^rcv_[A-Za-z0-9]+$" } } responses: "200": description: Recebimento. content: application/json: schema: { $ref: "#/components/schemas/Recebimento" } "404": { $ref: "#/components/responses/NaoEncontrado" } /players/{external_ref}/limits: get: tags: [jogadores] operationId: limitesDoJogador summary: Posição do jogador perante as regras (quanto ainda saca no automático). parameters: - { name: external_ref, in: path, required: true, schema: { type: string, maxLength: 64 } } - { name: currency, in: query, required: true, schema: { type: string } } responses: "200": description: Posição. content: application/json: schema: { $ref: "#/components/schemas/LimitesJogador" } "404": { $ref: "#/components/responses/NaoEncontrado" } /balances: get: tags: [operacao] operationId: saldosDaOperacao summary: Saldo de liquidação da operação por moeda (o "caixa" para pagar saques). responses: "200": description: Saldos. content: application/json: schema: type: object required: [data] properties: data: type: array items: type: object required: [available, pending_in, pending_out, as_of] properties: available: { $ref: "#/components/schemas/Money" } pending_in: { $ref: "#/components/schemas/Money" } pending_out: { $ref: "#/components/schemas/Money" } as_of: { type: string, format: date-time } /rules: get: tags: [regras] operationId: obterRegras summary: Conjunto de regras VIGENTE da operação (somente leitura pela API). description: | A API do operador NÃO altera regra diretamente: uma credencial vazada não pode subir o próprio limite. Mudança = `POST /rules/change-requests`, que só vale depois de aprovada no painel por DUAS pessoas (REGRAS.md §9). responses: "200": description: Regras vigentes. headers: ETag: { schema: { type: string } } content: application/json: schema: { $ref: "#/components/schemas/ConjuntoDeRegras" } /rules/change-requests: post: tags: [regras] operationId: pedirMudancaDeRegras summary: Propõe mudança de regra; fica pendente de aprovação dupla no painel. parameters: - $ref: "#/components/parameters/IdempotencyKey" - { name: If-Match, in: header, required: true, schema: { type: string } } requestBody: required: true content: application/json: schema: type: object required: [rules, justification] properties: rules: { $ref: "#/components/schemas/ConjuntoDeRegras" } justification: { type: string, maxLength: 1000 } responses: "202": description: Pedido registrado, aguardando aprovação. content: application/json: schema: type: object required: [id, status] properties: id: { type: string, examples: [chg_01J9ZQ] } status: { type: string, enum: [pending_approval] } "412": { $ref: "#/components/responses/Conflito" } /reconciliation: get: tags: [reconciliacao] operationId: reconciliacao summary: Movimentos liquidados num dia (UTC), com totais e digest assinado. parameters: - { name: date, in: query, required: true, schema: { type: string, format: date } } - { name: currency, in: query, required: true, schema: { type: string } } - $ref: "#/components/parameters/Cursor" - $ref: "#/components/parameters/Limite" responses: "200": description: Relatório do dia (paginado; totais e digest só na última página). content: application/json: schema: { $ref: "#/components/schemas/Reconciliacao" } "409": description: Dia ainda aberto (a janela fecha às 00:00 UTC + 2h de carência). content: application/problem+json: schema: { $ref: "#/components/schemas/Problema" } /events: get: tags: [operacao] operationId: listarEventos summary: Replay de eventos (mesmo envelope do webhook), para recuperar webhook perdido. parameters: - { name: after, in: query, schema: { type: string }, description: "id de evento; devolve os posteriores" } - { name: object_id, in: query, schema: { type: string } } - $ref: "#/components/parameters/Limite" responses: "200": description: Eventos em ordem de criação. content: application/json: schema: type: object required: [data, next_cursor] properties: data: { type: array, items: { $ref: "#/components/schemas/Evento" } } next_cursor: { type: [string, "null"] } # ---------- Superfície de CONTROLE do sandbox (só não-prd) ---------- /sandbox/deposits/{id}/pay: post: tags: [sandbox] operationId: sandboxPagarDeposito summary: Simula o pagador pagando o PIX/cartão/cripto. parameters: - $ref: "#/components/parameters/IdDeposito" - $ref: "#/components/parameters/IdempotencyKey" requestBody: content: application/json: schema: type: object properties: payer_document: { $ref: "#/components/schemas/Documento" } paid_amount: { $ref: "#/components/schemas/Money" } responses: "200": { description: Aplicado; webhook disparado., content: { application/json: { schema: { $ref: "#/components/schemas/Deposito" } } } } /sandbox/deposits/{id}/expire: post: tags: [sandbox] operationId: sandboxExpirarDeposito summary: Simula a expiração de um depósito pendente. parameters: [ { $ref: "#/components/parameters/IdDeposito" }, { $ref: "#/components/parameters/IdempotencyKey" } ] responses: "200": { description: Aplicado., content: { application/json: { schema: { $ref: "#/components/schemas/Deposito" } } } } /sandbox/deposits/{id}/refund: post: tags: [sandbox] operationId: sandboxEstornarDeposito summary: ADITIVO (dracma_api) — Simula o estorno de um depósito pago (`paid → refunded`, CONTRATO.md §6.1). description: | Sobe `version` e dispara o evento `deposit.refunded` (webhook) na mesma transação. Corpo vazio (ou `{}`). Fora de `paid` → 409 `invalid_state_transition`. Em produção → 404. parameters: [ { $ref: "#/components/parameters/IdDeposito" }, { $ref: "#/components/parameters/IdempotencyKey" } ] responses: "200": { description: Aplicado; webhook disparado., content: { application/json: { schema: { $ref: "#/components/schemas/Deposito" } } } } "404": { $ref: "#/components/responses/NaoEncontrado" } "409": { $ref: "#/components/responses/Conflito" } /sandbox/deposits/{id}/chargeback: post: tags: [sandbox] operationId: sandboxChargebackDeposito summary: ADITIVO (dracma_api) — Simula MED/chargeback de um depósito pago (`paid → chargeback`, CONTRATO.md §6.1). description: | Sobe `version` e dispara o evento `deposit.chargeback` (webhook) na mesma transação. Corpo vazio (ou `{}`). Fora de `paid` → 409 `invalid_state_transition`. Em produção → 404. parameters: [ { $ref: "#/components/parameters/IdDeposito" }, { $ref: "#/components/parameters/IdempotencyKey" } ] responses: "200": { description: Aplicado; webhook disparado., content: { application/json: { schema: { $ref: "#/components/schemas/Deposito" } } } } "404": { $ref: "#/components/responses/NaoEncontrado" } "409": { $ref: "#/components/responses/Conflito" } /sandbox/withdrawals/{id}/{action}: post: tags: [sandbox] operationId: sandboxAgirNoSaque summary: Simula a decisão manual e o resultado do PSP. parameters: - $ref: "#/components/parameters/IdSaque" - $ref: "#/components/parameters/IdempotencyKey" - name: action in: path required: true description: | v1.1.0 acrescenta (wire/SWIFT): `compliance_hold`, `compliance_clear`, `compliance_reject`, `send`, `transit`, `recall_accept`, `recall_reject`. `return` passa a valer também a partir de `sent`/`in_transit`. schema: type: string enum: [approve, reject, pay, fail, return, compliance_hold, compliance_clear, compliance_reject, send, transit, recall_accept, recall_reject] responses: "200": { description: Aplicado., content: { application/json: { schema: { $ref: "#/components/schemas/Saque" } } } } "409": { $ref: "#/components/responses/Conflito" } /sandbox/bank-transfers/incoming: post: tags: [sandbox] operationId: sandboxTransferenciaRecebida summary: v1.1.0 — Simula um crédito wire/SWIFT chegando na conta de recebimento. parameters: [ { $ref: "#/components/parameters/IdempotencyKey" } ] description: | Com `reference` de um depósito `pending`, casa com ele (valor igual → `paid`; diferente → `under_review`); sem referência, com referência desconhecida ou de depósito expirado, cria um `Recebimento` `unmatched`. `screening: hit` força `pending_compliance`/`frozen`. requestBody: required: true content: application/json: schema: type: object required: [amount, rail] additionalProperties: false properties: amount: { $ref: "#/components/schemas/Money" } rail: { $ref: "#/components/schemas/Trilho" } reference: { type: string, maxLength: 140 } payer_name: { type: string, maxLength: 140 } payer_document: { $ref: "#/components/schemas/Documento" } payer_country: { type: string, pattern: "^[A-Z]{2}$" } screening: { type: string, enum: [clear, hit], default: clear } responses: "400": { $ref: "#/components/responses/Invalido" } "200": description: Aplicado; devolve o objeto afetado. content: application/json: schema: oneOf: - { $ref: "#/components/schemas/Deposito" } - { $ref: "#/components/schemas/Recebimento" } /sandbox/outage: post: tags: [sandbox] operationId: sandboxQueda summary: Liga/desliga a simulação de indisponibilidade (toda rota não-sandbox responde 503). parameters: [ { $ref: "#/components/parameters/IdempotencyKey" } ] requestBody: required: true content: application/json: schema: type: object required: [down] properties: down: { type: boolean } responses: "200": { description: Aplicado. } /sandbox/webhooks: get: tags: [sandbox] operationId: sandboxWebhooksEnviados summary: Log das entregas de webhook (evento, tentativa, status HTTP recebido). responses: "200": { description: Log., content: { application/json: { schema: { type: object } } } } /sandbox/webhooks/redeliver: post: tags: [sandbox] operationId: sandboxReentregar summary: Reentrega um evento já enviado (testa a deduplicação do operador). parameters: [ { $ref: "#/components/parameters/IdempotencyKey" } ] requestBody: required: true content: application/json: schema: type: object required: [event_id] properties: event_id: { type: string } responses: "200": { description: Reentregue. } webhooks: evento: post: summary: Mudança de estado de depósito/saque/regra, entregue ao operador. description: | POST na URL cadastrada da operação. Headers `Dracma-Event-Id`, `Dracma-Timestamp`, `Dracma-Signature` (CONTRATO.md §4). O operador responde 2xx em até 10 s; qualquer outra coisa = nova tentativa com backoff (CONTRATO.md §5). O operador NÃO credita pelo corpo do webhook: faz fetch-back (`GET /deposits/{id}`) e age pelo estado autoritativo. v1.1.0: tipo de evento desconhecido → responder 200 e NÃO mover dinheiro (CONTRATO.md §6.0). requestBody: required: true content: application/json: schema: { $ref: "#/components/schemas/Evento" } responses: "200": { description: Recebido (inclusive duplicata). } "401": { description: Assinatura/timestamp inválido — o Dracma NÃO insiste além da política normal e alerta. } components: securitySchemes: mtls: type: mutualTLS description: Certificado de cliente por operação, emitido pela CA do Dracma. Obrigatório em produção. assinaturaHMAC: type: apiKey in: header name: Dracma-Signature description: | HMAC-SHA256 do pedido canônico com o segredo da chave `Dracma-Key-Id`, mais `Dracma-Timestamp` e `Dracma-Nonce`. Algoritmo em CONTRATO.md §3. parameters: IdempotencyKey: name: Idempotency-Key in: header required: true schema: { type: string, minLength: 16, maxLength: 128, pattern: "^[A-Za-z0-9_.:-]+$" } description: Retida por 30 dias. Mesma chave + mesmo corpo = mesma resposta; corpo diferente = 409. IdDeposito: name: id in: path required: true schema: { type: string, pattern: "^dep_[A-Za-z0-9]+$" } IdSaque: name: id in: path required: true schema: { type: string, pattern: "^wdr_[A-Za-z0-9]+$" } Cursor: name: cursor in: query schema: { type: string } Limite: name: limit in: query schema: { type: integer, minimum: 1, maximum: 200, default: 50 } responses: Invalido: description: Corpo/parâmetro inválido. content: { application/problem+json: { schema: { $ref: "#/components/schemas/Problema" } } } NaoAutenticado: description: Credencial/assinatura/timestamp/nonce inválido. content: { application/problem+json: { schema: { $ref: "#/components/schemas/Problema" } } } Proibido: description: Credencial válida sem permissão (método/moeda não habilitado, IP fora da allowlist). content: { application/problem+json: { schema: { $ref: "#/components/schemas/Problema" } } } NaoEncontrado: description: Objeto inexistente NESTA operação (objeto de outra operação também é 404). content: { application/problem+json: { schema: { $ref: "#/components/schemas/Problema" } } } Conflito: description: Idempotência violada, transição de estado inválida ou ETag divergente. content: { application/problem+json: { schema: { $ref: "#/components/schemas/Problema" } } } RegraDeNegocio: description: Recusa definitiva de regra (não repetir). content: { application/problem+json: { schema: { $ref: "#/components/schemas/Problema" } } } Limitado: description: Rate limit. `Retry-After` presente. headers: Retry-After: { schema: { type: integer } } content: { application/problem+json: { schema: { $ref: "#/components/schemas/Problema" } } } Indisponivel: description: Dracma/PSP indisponível. Repetir com a MESMA Idempotency-Key. headers: Retry-After: { schema: { type: integer } } content: { application/problem+json: { schema: { $ref: "#/components/schemas/Problema" } } } schemas: Money: type: object required: [amount, currency] additionalProperties: false properties: amount: type: string pattern: "^(0|[1-9][0-9]{0,37})$" description: Inteiro na menor unidade da moeda (sem sinal; o sentido vem do objeto). currency: type: string pattern: "^[A-Z0-9]{3,12}(\\.[A-Z0-9]{2,12})?$" description: ISO 4217 (BRL, USD, EUR…) ou ativo cripto com rede (USDT.TRON, USDC.ETH, BTC.BTC). Moeda: type: object required: [code, kind, exponent, methods] properties: code: { type: string, examples: [BRL, USDT.TRON] } kind: { type: string, enum: [fiat, crypto] } exponent: { type: integer, minimum: 0, maximum: 18, description: "casas da menor unidade (BRL=2, BTC=8, ETH=18)" } methods: type: array items: { $ref: "#/components/schemas/Metodo" } rails: type: array description: v1.1.0 — trilhos bancários habilitados para a moeda nesta operação, com prazos e cut-off. items: { $ref: "#/components/schemas/TrilhoDisponivel" } method_details: type: array description: | ADITIVO (dracma_api) — por método habilitado, o que o PSP de cash-in da operação exige no `POST /deposits` (`deposit_requirements`, caminhos de campo do corpo) e avisos de comportamento (`psp_notes`). Campo exigido e ausente → 400 `validation_failed` com `errors[].reason = required_by_operation`. `methods` continua sendo a lista de métodos. items: { $ref: "#/components/schemas/DetalheMetodo" } DetalheMetodo: type: object required: [method, deposit_requirements, psp_notes] properties: method: { $ref: "#/components/schemas/Metodo" } deposit_requirements: type: array description: Caminhos de campo exigidos no `CriarDeposito` (lista aberta; hoje `player.name`, `player.email`). items: { type: string, examples: [player.name, player.email] } psp_notes: type: array description: Avisos de comportamento do PSP (lista aberta; hoje `payer_identity_unavailable`). items: { type: string, examples: [payer_identity_unavailable] } Metodo: type: string enum: [pix, card, crypto, bank_transfer, boleto, wire, swift] description: | `pix` — PIX (BR). `card` — cartão, checkout hospedado. `crypto` — ativo com rede. `boleto` — boleto bancário (BR), só depósito. `bank_transfer` — v1.0, transferência bancária genérica; MANTIDO por compatibilidade e equivalente a `wire` com o trilho escolhido pelo Dracma. Integração nova DEVERIA usar `wire`/`swift`. `wire` (v1.1.0) — transferência bancária DOMÉSTICA/regional num trilho (`rail`): TED, ACH, Fedwire, SEPA, SEPA Instant, Faster Payments, SPEI… `swift` (v1.1.0) — transferência INTERNACIONAL pela rede SWIFT (MT103/pacs.008), com correspondentes, OUR/SHA/BEN e câmbio. Lista ABERTA: métodos novos só se acrescentam; o operador trata valor desconhecido como não suportado, nunca como erro fatal (CONTRATO.md §6.0). Tabela método × país × moeda × prazo em METODOS.md. Trilho: type: string pattern: "^[a-z][a-z0-9_]{1,31}$" description: | v1.1.0 — trilho (rail) de um `wire`. Lista ABERTA, registrada em METODOS.md (ex.: `ted`, `ach`, `ach_same_day`, `fedwire`, `sepa_ct`, `sepa_instant`, `faster_payments`, `bacs`, `chaps`, `spei`, `eft_ca`, `npp`, `imps`, `neft`). Para `swift` o trilho é `swift`. Trilho não habilitado → 400 `rail_not_enabled`. examples: [sepa_ct, ach, spei, faster_payments] TrilhoDisponivel: type: object required: [method, rail, directions, settlement] properties: method: { $ref: "#/components/schemas/Metodo" } rail: { $ref: "#/components/schemas/Trilho" } directions: { type: array, items: { type: string, enum: [in, out] } } settlement: type: object required: [min_days, max_days, business_days, calendar] properties: min_days: { type: integer, minimum: 0, maximum: 10, description: "D+n mínimo" } max_days: { type: integer, minimum: 0, maximum: 10, description: "D+n máximo (SWIFT típico D+1…D+5)" } business_days: { type: boolean, description: "true = D+n conta só dias úteis do calendário" } calendar: { type: string, examples: [BR, US-FED, TARGET2, GB, MX], description: "calendário de dias úteis (METODOS.md §3)" } cut_off_local: { type: [string, "null"], pattern: "^[0-2][0-9]:[0-5][0-9]$", description: "hora limite; depois dela, conta a partir do próximo dia útil. null = 24x7" } timezone: { type: [string, "null"], examples: [America/Sao_Paulo, America/New_York, Europe/Brussels] } limits: type: object properties: min: { $ref: "#/components/schemas/Money" } max: { $ref: "#/components/schemas/Money" } recall_window_days: { type: [integer, "null"], description: "janela em que um recall ainda pode ser pedido" } Endereco: type: object description: v1.1.0 — endereço postal (exigido para o beneficiário SWIFT; FATF R.16). required: [line1, city, country] additionalProperties: false properties: line1: { type: string, maxLength: 140 } line2: { type: string, maxLength: 140 } city: { type: string, maxLength: 70 } region: { type: string, maxLength: 70 } postal_code: { type: string, maxLength: 16 } country: { type: string, pattern: "^[A-Z]{2}$" } RoteamentoBancario: type: object description: | v1.1.0 — código de roteamento doméstico. Validação por `scheme` (REGRAS.md §17.1): `aba` 9 dígitos com dígito verificador 3-7-1; `sort_code` 6 dígitos; `bsb` 6 dígitos; `ifsc` `^[A-Z]{4}0[A-Z0-9]{6}$`; `transit` (CA) 5+3 dígitos; `ispb` 8 dígitos; `compe` 3 dígitos; `bank_code` genérico por país. required: [scheme, code] additionalProperties: false properties: scheme: { type: string, enum: [aba, sort_code, bsb, ifsc, transit, ispb, compe, bank_code] } code: { type: string, pattern: "^[A-Z0-9]{3,11}$" } branch: { type: string, maxLength: 16, description: "agência (BR) ou filial, quando o país exige" } BancoIntermediario: type: object description: v1.1.0 — banco intermediário/correspondente indicado pelo beneficiário (SWIFT). required: [bic] additionalProperties: false properties: bic: { type: string, pattern: "^[A-Z]{4}[A-Z]{2}[A-Z0-9]{2}([A-Z0-9]{3})?$" } name: { type: string, maxLength: 140 } country: { type: string, pattern: "^[A-Z]{2}$" } account: { type: string, maxLength: 34, description: "conta do banco do beneficiário no correspondente, se informada" } ContaBancaria: type: object description: | v1.1.0 — conta bancária de beneficiário (saque) ou de recebimento (instrução de depósito). Exatamente UMA forma de identificar a conta: `iban` OU (`account_number` + `routing`) OU `clabe`. Validação (REGRAS.md §17.1): IBAN com tamanho do país e mod-97 = 1 (ISO 13616); BIC 8 ou 11 com país coerente; CLABE 18 dígitos com dígito verificador; ABA com checksum. Falha → 400 `validation_failed` com `errors[].reason`. required: [holder_name, country] additionalProperties: false properties: holder_name: { type: string, maxLength: 140, description: "nome do titular como o banco o registra" } holder_type: { type: string, enum: [individual, business] } holder_address: { $ref: "#/components/schemas/Endereco" } holder_document: { $ref: "#/components/schemas/Documento" } country: { type: string, pattern: "^[A-Z]{2}$", description: "país da conta" } iban: { type: string, pattern: "^[A-Z]{2}[0-9]{2}[A-Z0-9]{11,30}$", description: "sem espaços, maiúsculas; checksum mod-97 conferido no servidor" } bic: { type: string, pattern: "^[A-Z]{4}[A-Z]{2}[A-Z0-9]{2}([A-Z0-9]{3})?$", description: "BIC/SWIFT de 8 ou 11" } account_number: { type: string, pattern: "^[A-Z0-9-]{4,34}$" } account_type: { type: string, enum: [checking, savings, payment] } routing: { $ref: "#/components/schemas/RoteamentoBancario" } clabe: { type: string, pattern: "^[0-9]{18}$", description: "México (SPEI)" } bank_name: { type: string, maxLength: 140 } bank_address: { $ref: "#/components/schemas/Endereco" } oneOf: - { required: [iban] } - { required: [account_number, routing] } - { required: [clabe] } DestinoWire: type: object description: v1.1.0 — destino de saque `wire` (doméstico/regional). required: [account] additionalProperties: false properties: rail: { $ref: "#/components/schemas/Trilho" } account: { $ref: "#/components/schemas/ContaBancaria" } DestinoSwift: type: object description: | v1.1.0 — destino de saque `swift`. `account.bic`, `account.holder_address` e `account.holder_name` são OBRIGATÓRIOS (sem eles → 400 `validation_failed`, reason `bic_required`/`address_required`). required: [account] additionalProperties: false properties: account: { $ref: "#/components/schemas/ContaBancaria" } intermediary_bank: { $ref: "#/components/schemas/BancoIntermediario" } charge_bearer: { $ref: "#/components/schemas/PortadorDeTarifa" } PortadorDeTarifa: type: string enum: [OUR, SHA, BEN] description: | v1.1.0 — quem paga as tarifas SWIFT (campo 71A): `OUR` a operação paga tudo (beneficiário recebe o valor cheio); `SHA` cada lado paga o seu banco e os correspondentes descontam do valor em trânsito; `BEN` tudo descontado do beneficiário. Ausente → default da operação nas regras (DECISOES D14). Proposito: type: object description: | v1.1.0 — propósito do pagamento, exigido em `swift` e em corredores que o pedem. `code` é canônico do Dracma; o Dracma o traduz para o código do corredor (ISO 20022 ExternalPurpose, natureza de câmbio do BCB etc.). required: [code] additionalProperties: false properties: code: { type: string, enum: [gaming_winnings, balance_withdrawal, deposit_refund, other] } description: { type: string, maxLength: 140 } Tarifa: type: object description: v1.1.0 — um item de tarifa. `bearer` diz de quem foi descontado. required: [type, amount, bearer] additionalProperties: false properties: type: { type: string, examples: [dracma, psp, correspondent, fx, return, recall], description: "lista aberta" } amount: { $ref: "#/components/schemas/Money" } bearer: { type: string, enum: [operator, beneficiary, payer] } estimated: { type: boolean, description: "true enquanto o correspondente não confirmou o valor" } Cambio: type: object description: | v1.1.0 — câmbio aplicado a um depósito/saque. `sell` sai do lado da operação, `buy` chega ao outro lado. `rate` = unidades de `buy` por 1 unidade MAIOR de `sell`, decimal em string (fórmula em MULTITENANT.md §6). required: [sell, buy, rate, risk_bearer] additionalProperties: false properties: quote_id: { type: [string, "null"], pattern: "^fxq_[A-Za-z0-9]+$" } sell: { $ref: "#/components/schemas/Money" } buy: { $ref: "#/components/schemas/Money" } rate: { type: string, pattern: "^[0-9]{1,12}(\\.[0-9]{1,12})?$" } mid_rate: { type: [string, "null"], pattern: "^[0-9]{1,12}(\\.[0-9]{1,12})?$" } spread_bps: { type: [integer, "null"], minimum: 0, maximum: 10000 } locked: { type: boolean, description: "true = taxa travada (cotação aceita); false = indicativa, fixada no envio" } risk_bearer: { type: string, enum: [dracma, operator, beneficiary], description: "quem assume a variação entre a cotação e a liquidação (DECISOES D15)" } quoted_at: { type: [string, "null"], format: date-time } expires_at: { type: [string, "null"], format: date-time } executed_at: { type: [string, "null"], format: date-time } PedidoCotacao: type: object required: [purpose, sell_currency, buy_currency] additionalProperties: false description: Exatamente um de `sell_amount` (valor na moeda da carteira) ou `buy_amount`. properties: purpose: { type: string, enum: [deposit, withdrawal] } method: { $ref: "#/components/schemas/Metodo" } sell_currency: { type: string } buy_currency: { type: string } sell_amount: { type: string, pattern: "^(0|[1-9][0-9]{0,37})$" } buy_amount: { type: string, pattern: "^(0|[1-9][0-9]{0,37})$" } oneOf: - { required: [sell_amount] } - { required: [buy_amount] } Cotacao: type: object required: [id, status, sell, buy, rate, expires_at, risk_bearer] properties: id: { type: string, examples: [fxq_01J9ZQ7] } status: { type: string, enum: [active, used, expired] } sell: { $ref: "#/components/schemas/Money" } buy: { $ref: "#/components/schemas/Money" } rate: { type: string } mid_rate: { type: string } spread_bps: { type: integer } risk_bearer: { type: string, enum: [dracma, operator, beneficiary] } quoted_at: { type: string, format: date-time } expires_at: { type: string, format: date-time } Compliance: type: object description: | v1.1.0 — estado do screening de sanções/PLD do objeto. NUNCA traz a lista, o nome casado nem o motivo detalhado (tipping-off, SEGURANCA.md §13.1). required: [status] additionalProperties: false properties: status: { type: string, enum: [not_required, pending, information_requested, cleared, rejected, frozen] } checked_at: { type: [string, "null"], format: date-time } reference: { type: [string, "null"], description: "id opaco do caso de compliance no Dracma" } documents_requested: type: array description: tipos de documento pedidos (ex. source_of_funds, proof_of_address, invoice) items: { type: string } DevolucaoBancaria: type: object description: v1.1.0 — dados de uma transferência devolvida pelo banco (return). required: [reason_code, returned_at] additionalProperties: false properties: reason_code: { type: string, description: "código do trilho (ISO 20022 ExternalReturnReason, ex. AC01, AC04, AM05, BE04; ACH R01…)" } reason: { type: string, enum: [invalid_account, closed_account, beneficiary_mismatch, regulatory, duplicate, recall, other], description: "categoria canônica (lista aberta)" } returned_amount: { $ref: "#/components/schemas/Money" } return_fee: { oneOf: [ { $ref: "#/components/schemas/Money" }, { type: "null" } ] } returned_at: { type: string, format: date-time } Recall: type: object description: v1.1.0 — pedido de recall de um saque enviado. required: [status, requested_at] additionalProperties: false properties: status: { type: string, enum: [requested, accepted, rejected] } reason_code: { type: string } requested_by: { type: string, enum: [operator, panel, dracma] } requested_at: { type: string, format: date-time } decided_at: { type: [string, "null"], format: date-time } RastreioBancario: type: object description: v1.1.0 — identificadores de rastreio no trilho. additionalProperties: false properties: uetr: { type: [string, "null"], format: uuid, description: "SWIFT gpi UETR" } rail_reference: { type: [string, "null"], description: "E2E id SEPA, IMAD/OMAD Fedwire, trace number ACH, clave de rastreo SPEI" } last_update_at: { type: [string, "null"], format: date-time } Documento: type: object required: [type, number, country] additionalProperties: false properties: type: { type: string, enum: [CPF, CNPJ, PASSPORT, NATIONAL_ID, TAX_ID] } number: { type: string, maxLength: 32, description: "só dígitos para CPF/CNPJ" } country: { type: string, pattern: "^[A-Z]{2}$" } Jogador: type: object required: [external_ref, document] additionalProperties: false properties: external_ref: type: string maxLength: 64 description: Identificador OPACO e imutável do jogador no operador (nunca e-mail/telefone). document: { $ref: "#/components/schemas/Documento" } name: { type: string, maxLength: 120 } email: { type: string, format: email, maxLength: 254 } birth_date: { type: string, format: date } kyc_level: { type: string, enum: [none, basic, full], description: "informativo" } ip: { type: string, description: "IP do jogador no pedido, para risco" } device_id: { type: string, maxLength: 128 } CriarDeposito: type: object required: [external_id, player, amount, method] additionalProperties: false properties: external_id: { type: string, maxLength: 64, description: "id do operador; único por operação" } player: { $ref: "#/components/schemas/Jogador" } amount: { $ref: "#/components/schemas/Money" } method: { $ref: "#/components/schemas/Metodo" } method_options: type: object additionalProperties: false properties: card: type: object properties: return_url: { type: string, format: uri } crypto: type: object properties: pay_currency: { type: string, description: "ativo em que o jogador paga (USDT.TRON); amount segue na moeda da carteira" } wire: type: object additionalProperties: false description: v1.1.0 properties: rail: { $ref: "#/components/schemas/Trilho" } reference_valid_days: { type: integer, minimum: 1, maximum: 30, default: 10, description: "dias corridos em que a referência casa; substitui expires_in_seconds para wire" } swift: type: object additionalProperties: false description: v1.1.0 properties: reference_valid_days: { type: integer, minimum: 1, maximum: 30, default: 15 } pay_currency: { type: string, description: "moeda em que o pagador envia, se ≠ da carteira (exige câmbio habilitado)" } boleto: type: object additionalProperties: false description: v1.1.0 properties: due_in_days: { type: integer, minimum: 1, maximum: 30, default: 3 } fx_quote_id: { type: string, pattern: "^fxq_[A-Za-z0-9]+$", description: "v1.1.0 — trava a cotação (pagamento em moeda ≠ da carteira)" } expires_in_seconds: { type: integer, minimum: 300, maximum: 86400, default: 1800 } locale: { type: string, examples: [pt-BR, en, es] } metadata: type: object maxProperties: 20 additionalProperties: { type: string, maxLength: 256 } InstrucoesDeposito: type: object additionalProperties: false properties: pix: type: object required: [copy_paste, expires_at] properties: copy_paste: { type: string, description: "BR Code EMV (payload do QR)" } qr_code_png_base64: { type: string, description: "opcional; o operador pode gerar o QR do copy_paste" } expires_at: { type: string, format: date-time } card: type: object required: [checkout_url, expires_at] properties: checkout_url: { type: string, format: uri, description: "página hospedada/hosted fields do Dracma; o PAN nunca passa pelo operador" } expires_at: { type: string, format: date-time } crypto: type: object required: [address, pay_amount, expires_at] properties: address: { type: string } memo: { type: string } pay_amount: { $ref: "#/components/schemas/Money" } rate: { type: string, description: "cotação travada, decimal em string" } expires_at: { type: string, format: date-time } wire: { $ref: "#/components/schemas/InstrucoesBancarias" } swift: { $ref: "#/components/schemas/InstrucoesBancarias" } boleto: type: object description: v1.1.0 required: [digitable_line, due_date] properties: digitable_line: { type: string, pattern: "^[0-9]{47,48}$" } barcode: { type: string, pattern: "^[0-9]{44}$" } pdf_url: { type: string, format: uri } due_date: { type: string, format: date } InstrucoesBancarias: type: object description: | v1.1.0 — como o pagador transfere. A `reference` é OBRIGATÓRIA na transferência (campo de remessa/"remittance information"/"concepto"): é o único elo entre o crédito e o depósito. Sem ela → `Recebimento` não casado. required: [account, reference, amount, expires_at] additionalProperties: false properties: rail: { $ref: "#/components/schemas/Trilho" } account: { $ref: "#/components/schemas/ContaBancaria" } intermediary_bank: { $ref: "#/components/schemas/BancoIntermediario" } reference: type: string pattern: "^RF[0-9]{2}[A-Z0-9]{1,14}$" description: "referência de credor ISO 11649 (RF + 2 dígitos de controle mod-97), ≤ 18 caracteres para caber no menor campo de remessa (Faster Payments)" amount: { $ref: "#/components/schemas/Money" } charge_bearer: { $ref: "#/components/schemas/PortadorDeTarifa" } expires_at: { type: string, format: date-time, description: "até quando a referência casa; depois disso o crédito vira Recebimento com EXPIRED_REFERENCE" } StatusDeposito: type: string enum: [created, pending, under_review, paid, expired, cancelled, failed, returned, refunded, chargeback, pending_compliance, frozen, paid_partial] description: | Máquina de estados em CONTRATO.md §6.1. Só `paid` credita (pelo `amount`). v1.1.0: `pending_compliance` (fundos recebidos, em screening — não credita), `frozen` (retido por sanção/ordem legal — não credita), `paid_partial` (terminal; aceito no painel valor MENOR que o cobrado — credita `paid_amount`, só operador v1.1; operador v1.0 trata como desconhecido e não credita). Deposito: type: object required: [id, external_id, status, version, amount, method, player_ref, created_at, updated_at] properties: id: { type: string, examples: [dep_01J9ZQ3] } external_id: { type: string } status: { $ref: "#/components/schemas/StatusDeposito" } version: { type: integer, minimum: 1, description: "incrementa a cada transição; ordena webhooks" } amount: { $ref: "#/components/schemas/Money" } paid_amount: { oneOf: [ { $ref: "#/components/schemas/Money" }, { type: "null" } ] } method: { $ref: "#/components/schemas/Metodo" } player_ref: { type: string } payer: type: [object, "null"] description: Quem de fato pagou (preenchido em paid/under_review). properties: document: { $ref: "#/components/schemas/Documento" } name: { type: string } matches_player: { type: boolean } instructions: { oneOf: [ { $ref: "#/components/schemas/InstrucoesDeposito" }, { type: "null" } ] } failure_code: { type: [string, "null"] } review_reasons: type: array description: "v1.0.1 — motivos de under_review (PAYER_MISMATCH, AMOUNT_MISMATCH, PAID_AFTER_EXPIRY). Vazio fora de revisão." items: { type: string } created_at: { type: string, format: date-time } updated_at: { type: string, format: date-time } paid_at: { type: [string, "null"], format: date-time } expires_at: { type: [string, "null"], format: date-time } rail: { oneOf: [ { $ref: "#/components/schemas/Trilho" }, { type: "null" } ], description: "v1.1.0" } received_amount: { oneOf: [ { $ref: "#/components/schemas/Money" }, { type: "null" } ], description: "v1.1.0 — total recebido do pagador (pode exceder `amount`; o excedente vira Recebimento a devolver)" } receipts: { type: array, items: { type: string }, description: "v1.1.0 — ids `rcv_` dos créditos bancários ligados a este depósito" } fee: { oneOf: [ { $ref: "#/components/schemas/Money" }, { type: "null" } ], description: "v1.1.0 — soma das tarifas com bearer=operator" } fees: { type: array, items: { $ref: "#/components/schemas/Tarifa" }, description: "v1.1.0" } fx: { oneOf: [ { $ref: "#/components/schemas/Cambio" }, { type: "null" } ], description: "v1.1.0" } compliance: { oneOf: [ { $ref: "#/components/schemas/Compliance" }, { type: "null" } ], description: "v1.1.0" } expected_settlement_date: { type: [string, "null"], format: date, description: "v1.1.0 — quando o valor fica disponível no caixa da operação (não é o crédito ao jogador)" } return: { oneOf: [ { $ref: "#/components/schemas/DevolucaoBancaria" }, { type: "null" } ], description: "v1.1.0 — em `returned` por trilho bancário" } CriarSaque: type: object required: [external_id, player, amount, method, destination] additionalProperties: false properties: external_id: { type: string, maxLength: 64 } player: { $ref: "#/components/schemas/Jogador" } amount: { $ref: "#/components/schemas/Money" } method: { $ref: "#/components/schemas/Metodo" } destination: type: object additionalProperties: false minProperties: 1 maxProperties: 1 description: "Exatamente UM método de destino, coerente com `method`." properties: pix: type: object required: [key_type, key] properties: key_type: { type: string, enum: [cpf, cnpj, email, phone, random] } key: { type: string, maxLength: 140 } crypto: type: object required: [address] properties: address: { type: string } memo: { type: string } bank_transfer: type: object description: v1.0 — forma livre, mantida por compatibilidade. Integração nova usa `wire`/`swift`. properties: country: { type: string } iban: { type: string } account: { type: object, additionalProperties: { type: string } } wire: { $ref: "#/components/schemas/DestinoWire" } swift: { $ref: "#/components/schemas/DestinoSwift" } purpose: { $ref: "#/components/schemas/Proposito" } fx_quote_id: { type: string, pattern: "^fxq_[A-Za-z0-9]+$", description: "v1.1.0 — cotação aceita; sem ela, câmbio é indicativo e fixado no envio (CONTRATO.md §6.5)" } payout_currency: { type: string, description: "v1.1.0 — moeda que o beneficiário recebe, se ≠ `amount.currency`. Exige câmbio habilitado; senão 422 `fx_not_enabled`." } review_requested: type: boolean default: false description: Pré-checagem do operador pediu revisão. Se true, o Dracma NÃO paga no automático. review_reasons: { type: array, items: { type: string } } player_context: type: object description: INFORMATIVO. Nunca usado para liberar automático. properties: total_deposited: { $ref: "#/components/schemas/Money" } total_withdrawn: { $ref: "#/components/schemas/Money" } account_created_at: { type: string, format: date-time } net_gaming_result: { type: string, description: "inteiro com sinal, menor unidade" } metadata: type: object maxProperties: 20 additionalProperties: { type: string, maxLength: 256 } StatusSaque: type: string enum: [requested, under_review, approved, processing, paid, rejected, failed, cancelled, returned, pending_compliance, sent, in_transit, recall_requested, recalled] description: | CONTRATO.md §6.2. Terminais com dinheiro de volta ao jogador — rejected, failed, cancelled, returned. Terminal pago — paid. v1.1.0 (não terminais, não movem dinheiro no operador): `pending_compliance`, `sent`, `in_transit`, `recall_requested`. Terminal v1.1.0: `recalled` (o dinheiro volta ao caixa da operação; o que a bet faz com o jogador é DECISOES D18). Decisao: type: object required: [mode, reasons, ruleset_version] properties: mode: { type: string, enum: [automatic, manual] } reasons: type: array description: Códigos de REGRAS.md (ex. AUTO_MULTIPLIER_EXCEEDED, FIRST_WITHDRAWAL, PIX_KEY_CHANGED). items: { type: string } ruleset_version: { type: integer } decided_by: { type: array, items: { type: string }, description: "ids opacos dos revisores (manual)" } decided_at: { type: [string, "null"], format: date-time } Saque: type: object required: [id, external_id, status, version, amount, method, player_ref, decision, created_at, updated_at] properties: id: { type: string, examples: [wdr_01J9ZQ4] } external_id: { type: string } status: { $ref: "#/components/schemas/StatusSaque" } version: { type: integer, minimum: 1 } amount: { $ref: "#/components/schemas/Money" } fee: { oneOf: [ { $ref: "#/components/schemas/Money" }, { type: "null" } ] } method: { $ref: "#/components/schemas/Metodo" } player_ref: { type: string } decision: { $ref: "#/components/schemas/Decisao" } psp_reference: { type: [string, "null"], description: "E2E id do PIX, hash da tx cripto; v1.1.0: UETR no SWIFT" } failure_code: { type: [string, "null"] } created_at: { type: string, format: date-time } updated_at: { type: string, format: date-time } paid_at: { type: [string, "null"], format: date-time } rail: { oneOf: [ { $ref: "#/components/schemas/Trilho" }, { type: "null" } ], description: "v1.1.0" } fees: { type: array, items: { $ref: "#/components/schemas/Tarifa" }, description: "v1.1.0 — detalhe; `fee` continua sendo a soma com bearer=operator" } charge_bearer: { oneOf: [ { $ref: "#/components/schemas/PortadorDeTarifa" }, { type: "null" } ], description: "v1.1.0" } fx: { oneOf: [ { $ref: "#/components/schemas/Cambio" }, { type: "null" } ], description: "v1.1.0" } purpose: { oneOf: [ { $ref: "#/components/schemas/Proposito" }, { type: "null" } ], description: "v1.1.0" } compliance: { oneOf: [ { $ref: "#/components/schemas/Compliance" }, { type: "null" } ], description: "v1.1.0" } expected_settlement_date: { type: [string, "null"], format: date, description: "v1.1.0 — data prevista de crédito ao beneficiário (calendário do trilho; recalculada a cada transição)" } sent_at: { type: [string, "null"], format: date-time, description: "v1.1.0" } tracking: { oneOf: [ { $ref: "#/components/schemas/RastreioBancario" }, { type: "null" } ], description: "v1.1.0" } return: { oneOf: [ { $ref: "#/components/schemas/DevolucaoBancaria" }, { type: "null" } ], description: "v1.1.0 — preenchido em `returned` por trilho bancário" } recall: { oneOf: [ { $ref: "#/components/schemas/Recall" }, { type: "null" } ], description: "v1.1.0" } StatusRecebimento: type: string enum: [unmatched, matched, returned, frozen] description: v1.1.0 — CONTRATO.md §6.4. Recebimento: type: object description: | v1.1.0 — crédito bancário recebido na conta da operação que NÃO casou automaticamente com um depósito. Não credita ninguém por si. Painel casa (`matched` → o depósito segue a própria máquina) ou devolve (`returned`). required: [id, status, version, amount, rail, reason, received_at, created_at, updated_at] properties: id: { type: string, examples: [rcv_01J9ZQ8] } status: { $ref: "#/components/schemas/StatusRecebimento" } version: { type: integer, minimum: 1 } amount: { $ref: "#/components/schemas/Money" } method: { $ref: "#/components/schemas/Metodo" } rail: { $ref: "#/components/schemas/Trilho" } reason: { type: string, enum: [NO_REFERENCE, UNKNOWN_REFERENCE, EXPIRED_REFERENCE, REFERENCE_ALREADY_PAID, EXCESS_AMOUNT, CURRENCY_MISMATCH], description: "lista aberta" } reference_provided: { type: [string, "null"], maxLength: 140 } payer: type: [object, "null"] description: dados do remetente como o banco informou (MASCARADOS na listagem) properties: name: { type: string } account_masked: { type: string } bic: { type: string } country: { type: string } matched_deposit_id: { type: [string, "null"] } return_by: { type: [string, "null"], format: date, description: "data-limite após a qual o Dracma devolve ao remetente (DECISOES D17)" } return: { oneOf: [ { $ref: "#/components/schemas/DevolucaoBancaria" }, { type: "null" } ] } received_at: { type: string, format: date-time } created_at: { type: string, format: date-time } updated_at: { type: string, format: date-time } LimitesJogador: type: object required: [player_ref, currency, total_deposited, total_withdrawn, auto_multiplier, auto_withdrawal_remaining] properties: player_ref: { type: string } currency: { type: string } total_deposited: { $ref: "#/components/schemas/Money" } total_withdrawn: { $ref: "#/components/schemas/Money" } auto_multiplier: { type: string, description: "X da regra, decimal em string (\"1\", \"1.5\")" } auto_withdrawal_remaining: { $ref: "#/components/schemas/Money" } blocked: { type: boolean } ConjuntoDeRegras: type: object description: Forma completa em REGRAS.md §10. Campos desconhecidos são rejeitados. required: [version, currencies] properties: version: { type: integer } timezone: { type: string, examples: [America/Sao_Paulo] } currencies: type: object additionalProperties: type: object properties: auto_multiplier: { type: string, default: "1" } deposit_min: { type: string } deposit_max: { type: string } withdrawal_min: { type: string } withdrawal_max: { type: string } auto_max_per_withdrawal: { type: string } auto_max_per_day: { type: string } dual_approval_above: { type: string } max_withdrawals_per_day: { type: integer } first_withdrawal_manual: { type: boolean, default: true } pix_key_change_cooldown_hours: { type: integer, default: 24 } require_payer_matches_player: { type: boolean, default: true } auto_hours: { type: [object, "null"], properties: { from: { type: string }, to: { type: string } } } bank_transfers: type: object description: v1.1.0 — regras de wire/SWIFT (REGRAS.md §17). Forma completa em REGRAS.md §10.1. properties: method_limits: type: object description: "por moeda → por método (`wire`, `swift`): withdrawal_min/max, deposit_min/max, auto_max_per_withdrawal (menor unidade, string). Vale o mais restritivo entre isto e `currencies.`." additionalProperties: type: object additionalProperties: type: object properties: deposit_min: { type: string } deposit_max: { type: string } withdrawal_min: { type: string } withdrawal_max: { type: string } auto_max_per_withdrawal: { type: [string, "null"] } swift_auto_enabled: { type: boolean, default: false, description: "false = todo saque SWIFT vai para revisão (SWIFT_MANUAL)" } default_charge_bearer: { $ref: "#/components/schemas/PortadorDeTarifa" } allowed_charge_bearers: { type: array, items: { $ref: "#/components/schemas/PortadorDeTarifa" } } beneficiary_change_cooldown_hours: { type: integer, default: 72 } screening_required_above: { type: [object, "null"], description: "por moeda: valor a partir do qual wire DOMÉSTICO também passa por pending_compliance (SWIFT sempre passa)", additionalProperties: { type: string } } blocked_countries: { type: array, items: { type: string, pattern: "^[A-Z]{2}$" }, description: "corredores vetados pela operação (somam-se aos vetados globalmente pelo Dracma)" } deposit_partial_accept_allowed: { type: boolean, default: false } deposit_excess_policy: { type: string, enum: [review, return_excess], default: review } unmatched_return_after_business_days: { type: integer, minimum: 1, maximum: 60, default: 10 } fx: type: object properties: enabled: { type: boolean, default: false } max_spread_bps: { type: integer, minimum: 0, maximum: 10000 } quote_ttl_seconds: { type: integer, minimum: 30, maximum: 86400, default: 900 } requote_tolerance_bps: { type: integer, minimum: 0, maximum: 10000, default: 50, description: "piora máxima de taxa aceita ao recotar uma cotação expirada no envio" } risk_bearer: { type: string, enum: [dracma, operator, beneficiary], default: beneficiary } Evento: type: object required: [id, type, created_at, operation_id, object_id, object_version, data] properties: id: { type: string, examples: [evt_01J9ZQ5] } type: type: string enum: - deposit.pending - deposit.under_review - deposit.paid - deposit.expired - deposit.cancelled - deposit.failed - deposit.returned - deposit.refunded - deposit.chargeback - withdrawal.under_review - withdrawal.approved - withdrawal.processing - withdrawal.paid - withdrawal.rejected - withdrawal.failed - withdrawal.cancelled - withdrawal.returned - rules.updated # --- v1.1.0 (aditivos; desconhecido → 200 sem mover dinheiro) --- - 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.unmatched - receipt.matched - receipt.returned - receipt.frozen created_at: { type: string, format: date-time } operation_id: { type: string } object_id: { type: string } object_version: { type: integer } data: type: object required: [object] properties: object: oneOf: - { $ref: "#/components/schemas/Deposito" } - { $ref: "#/components/schemas/Saque" } - { $ref: "#/components/schemas/ConjuntoDeRegras" } - { $ref: "#/components/schemas/Recebimento" } Reconciliacao: type: object required: [date, currency, items, next_cursor] properties: date: { type: string, format: date } currency: { type: string } items: type: array items: type: object required: [object_id, external_id, kind, status, amount, settled_at] properties: object_id: { type: string } external_id: { type: string } kind: type: string enum: [deposit, withdrawal, refund, chargeback, fee, return, recall, receipt] description: | v1.1.0 acrescenta `return` (wire/SWIFT devolvido ao caixa), `recall` (recall aceito) e `receipt` (recebimento não casado, em suspenso). Item de câmbio usa sempre o `amount` na moeda da carteira; a perna em outra moeda fica no objeto (`fx`). status: { type: string } amount: { $ref: "#/components/schemas/Money" } fee: { oneOf: [ { $ref: "#/components/schemas/Money" }, { type: "null" } ] } settled_at: { type: string, format: date-time } next_cursor: { type: [string, "null"] } totals: type: [object, "null"] properties: deposits_paid: { $ref: "#/components/schemas/Money" } withdrawals_paid: { $ref: "#/components/schemas/Money" } refunds: { $ref: "#/components/schemas/Money" } chargebacks: { $ref: "#/components/schemas/Money" } fees: { $ref: "#/components/schemas/Money" } count: { type: integer } digest: type: [string, "null"] description: SHA-256 hex da lista canônica do dia (CONTRATO.md §8), assinado no header `Dracma-Signature` da resposta. Problema: type: object required: [type, title, status, code] properties: type: { type: string, format: uri-reference } title: { type: string } status: { type: integer } code: type: string description: Catálogo em CONTRATO.md §7. detail: { type: string } trace_id: { type: string } retryable: { type: boolean } existing: type: object description: "v1.0.1 — em 409 duplicate_external_id, o objeto já existente (Deposito ou Saque)." errors: type: array items: type: object properties: field: { type: string } message: { type: string } reason: type: string description: "v1.1.0 — motivo máquina (lista aberta): iban_checksum, iban_length, bic_format, bic_country_mismatch, routing_checksum, clabe_checksum, account_invalid, bic_required, address_required, purpose_required, rail_mismatch. ADITIVO (dracma_api), emitidos hoje: required, format, enum, length, range, invalid, invalid_json, unknown, unknown_field, too_large, max_length, currency_mismatch, required_by_operation, reserved_domain_required"