openapi: 3.1.0 info: title: Dracma — API de pagamentos para operadores (v1) version: 1.2.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 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 (chave de API; o certificado mTLS é OPCIONAL, habilitado por instância), NUNCA do corpo. Um `operation_id` na RAIZ do corpo é ignorado; aninhado resulta em `400 unknown_field`. - Erro é `application/problem+json` (RFC 9457) com `code` estável. - Rotas `/sandbox/*` só existem fora de produção (ver CONTRATO.md §9). - O legado `/api/v1` NÃO faz parte deste contrato e está DEPRECIADO (ADR-0039): não é publicado no ingress público (só `/v1/*`) e toda resposta dele leva `Deprecation` (RFC 9745), `Sunset` (RFC 8594, proposta 2027-01-31) e `Link: <...>; rel="deprecation"`. Nenhuma operação `/v1` o utiliza; não integre nele. DOCUMENTO SERVIDO pelo `dracma_api` (`GET /openapi.yaml`, público, sem autenticação, mas o ponto de entrada público do Dracma só expõe `/v1/*`: baixe o documento do site de documentaçã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`. v1.2.0 (ADITIVA, ADR-0040 e `cripto-cobranca-v1.md`): cobrança em cripto (`method=crypto` em `POST /deposits`, cobra-se em BRL e paga-se em ativo com cotação travada na criação), cotação indicativa `GET /fx/quote` (`binding=false`; NÃO é `POST /fx/quotes`), links de pagamento (`/payment-links*`) e os eventos `deposit.crypto.detected`, `deposit.crypto.confirmations` e `payment_link.paid`. Estados novos `detected` e `confirming` nunca creditam: só `paid` credita (leitor tolerante). O provedor de liquidação é um id OPACO (`Deposito.provider`), nunca um enum. Escopos novos da chave: `fx:read`, `payment_links:read`, `payment_links:write`. license: name: Proprietário — uso interno entre as partes servers: - url: https://api.dracma.example/v1 description: Host de exemplo. Os hosts reais (produção e teste) chegam no onboarding; não existe host `sandbox.*`. security: - 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; v1.2.0 — cotação indicativa de cobrança em cripto (`GET /fx/quote`). - name: recebimentos description: v1.1.0 — créditos bancários (wire/SWIFT) recebidos sem casar com um depósito. Somente leitura pela API. - name: links description: v1.2.0 — links de pagamento públicos (cobram por PIX e/ou cripto quando o pagador age). - 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). v1.2.0 — `crypto`: cobra em BRL (`amount.currency=BRL`) e o jogador paga em `method_options.crypto.pay_currency` (obrigatório, ex. `BTC.BTC`; ⊆ ativos habilitados da operação). A cotação é travada NA CRIAÇÃO e o replay idempotente devolve as MESMAS instruções (mesmo endereço, mesma cotação, mesmo `expires_at`), nunca recota. Ordem de avaliação: auth/escopo → validação → `crypto_not_enabled` (403) → `asset_not_supported` → `amount_out_of_range` → regras → cotação (`quote_unavailable`, 503 com `Retry-After`) → endereço. `spread_bps`/`rate`/`pay_amount` no corpo são campo desconhecido (400); `fx_quote_id` com crypto é 422 `fx_quote_not_applicable`; `expires_in_seconds` é ignorado (a validade é a da cotação). parameters: - $ref: "#/components/parameters/IdempotencyKey" requestBody: required: true content: application/json: schema: { $ref: "#/components/schemas/CriarDeposito" } examples: crypto: value: external_id: dep_0192a3b4-5c6d-7e8f-9a0b-1c2d3e4f5a6c player: external_ref: 0192a3b4-0000-7000-8000-000000000001 document: { type: CPF, number: "12345678909", country: BR } amount: { amount: "10000", currency: BRL } method: crypto method_options: { crypto: { pay_currency: "BTC.BTC" } } 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@operador.test amount: { amount: "5000", currency: BRL } method: pix expires_in_seconds: 1800 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"] } "401": { $ref: "#/components/responses/NaoAutenticado" } "403": { $ref: "#/components/responses/Proibido" } /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" } "401": { $ref: "#/components/responses/NaoAutenticado" } "403": { $ref: "#/components/responses/Proibido" } "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" } /fx/quote: get: tags: [cambio] operationId: cotarCripto summary: v1.2.0 — Pré-visualiza (NÃO trava) quanto custa pagar um valor em BRL num ativo cripto. description: | Indicativa: `binding=false`, não reserva endereço nem taxa. O valor travado só nasce em `POST /deposits` (ou na ação do pagador num link) e pode diferir do que esta rota mostrou. Exige escopo `fx:read` e cripto habilitada na operação (senão 403 `crypto_not_enabled`). O `spread_bps` aplicado é o da configuração resolvida da operação (plataforma → tenant → operação), nunca da query: `spread_bps` na query é IGNORADO. Rate limit próprio (60/min por operação). `Cache-Control: no-store`. Nome parecido com `POST /fx/quotes` (câmbio entre moedas de carteira, `fxq_`): são coisas distintas e não se misturam. parameters: - { name: amount, in: query, required: true, description: "BRL na menor unidade (centavos), inteiro em string", schema: { type: string, pattern: "^[1-9][0-9]{0,17}$" } } - { name: currency, in: query, required: false, schema: { type: string, enum: [BRL], default: BRL } } - { name: pay_currency, in: query, required: true, schema: { $ref: "#/components/schemas/AtivoCripto" } } responses: "200": description: Cotação indicativa. headers: { Cache-Control: { schema: { type: string, const: no-store } } } content: { application/json: { schema: { $ref: "#/components/schemas/CotacaoCripto" } } } "400": { $ref: "#/components/responses/Invalido" } "401": { $ref: "#/components/responses/NaoAutenticado" } "403": { $ref: "#/components/responses/Proibido" } "422": { $ref: "#/components/responses/RegraDeNegocio" } "429": { $ref: "#/components/responses/Limitado" } "503": { $ref: "#/components/responses/Indisponivel" } /payment-links: post: tags: [links] operationId: criarLinkDePagamento summary: v1.2.0 — Cria um link de pagamento público (cobra por PIX e/ou cripto quando o pagador age). description: | O link NÃO é uma cobrança: não reserva endereço nem trava cotação. A cobrança (`Deposito`) nasce quando o pagador escolhe o método na página pública (`pay./l/`), com o `player` fixado aqui. Escopo `payment_links:write`. Idempotente por `Idempotency-Key` (replay byte a byte) e por `external_id` (único por operação: repetido com outra chave ⇒ 409 `duplicate_external_id` com o link existente em `existing`). Crypto em `allowed_methods` exige cripto habilitada (403 `crypto_not_enabled`) e ativos ⊆ habilitados (422 `asset_not_supported`). parameters: - { $ref: "#/components/parameters/IdempotencyKey" } requestBody: required: true content: { application/json: { schema: { $ref: "#/components/schemas/CriarLink" } } } responses: "201": { description: Link criado., content: { application/json: { schema: { $ref: "#/components/schemas/LinkDePagamento" } } } } "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: [links] operationId: listarLinksDePagamento summary: v1.2.0 — Lista os links da operação (cursor). description: Escopo `payment_links:read`. `limit` fora de 1..200 ⇒ 400. Filtro de outra operação devolve lista vazia. parameters: - { name: external_id, in: query, schema: { type: string } } - { name: status, in: query, schema: { $ref: "#/components/schemas/StatusLink" } } - { name: player_ref, in: query, schema: { type: string } } - $ref: "#/components/parameters/Cursor" - $ref: "#/components/parameters/Limite" responses: "200": description: Página. content: application/json: schema: type: object required: [data, next_cursor] properties: data: { type: array, items: { $ref: "#/components/schemas/LinkDePagamento" } } next_cursor: { type: [string, "null"] } "400": { $ref: "#/components/responses/Invalido" } "401": { $ref: "#/components/responses/NaoAutenticado" } "403": { $ref: "#/components/responses/Proibido" } /payment-links/{id}: get: tags: [links] operationId: obterLinkDePagamento summary: v1.2.0 — Estado autoritativo de um link (fetch-back do webhook `payment_link.paid`). description: Escopo `payment_links:read`. Id de outra operação, inexistente ou o `slug` público no lugar do id ⇒ 404. parameters: - { $ref: "#/components/parameters/IdLink" } responses: "200": { description: Link., content: { application/json: { schema: { $ref: "#/components/schemas/LinkDePagamento" } } } } "401": { $ref: "#/components/responses/NaoAutenticado" } "403": { $ref: "#/components/responses/Proibido" } "404": { $ref: "#/components/responses/NaoEncontrado" } /payment-links/{id}/cancel: post: tags: [links] operationId: cancelarLinkDePagamento summary: v1.2.0 — Cancela o link. Idempotente. description: | Escopo `payment_links:write`. `active -> cancelled`. Cobranças do link ainda `pending` SEM pagamento detectado são canceladas junto (endereço aposentado). Cobrança com pagamento `detected`/`confirming` segue até o fim (o dinheiro já está a caminho) e, se `paid`, o link vira `paid` quando for single-use. Link já `paid`/`expired` ⇒ 409 `invalid_state_transition`. Repetir em link `cancelled` ⇒ 200 com o mesmo corpo. parameters: - { $ref: "#/components/parameters/IdLink" } - { $ref: "#/components/parameters/IdempotencyKey" } responses: "200": { description: Cancelado (ou já estava)., content: { application/json: { schema: { $ref: "#/components/schemas/LinkDePagamento" } } } } "400": { $ref: "#/components/responses/Invalido" } "401": { $ref: "#/components/responses/NaoAutenticado" } "403": { $ref: "#/components/responses/Proibido" } "404": { $ref: "#/components/responses/NaoEncontrado" } "409": { $ref: "#/components/responses/Conflito" } "503": { $ref: "#/components/responses/Indisponivel" } /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_0192a3b45c6d7e8f9a0b1c2d3e4f5a6b] } 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: o dia D só abre em D+1 às 02:00 UTC (00:00 UTC de D+1 mais 2h de carência)." content: application/problem+json: schema: { $ref: "#/components/schemas/Problema" } "422": description: "A operação não tem endpoint de webhook configurado, então o digest não pode ser assinado (`code: webhook_endpoint_not_configured`, não retryable). Configure o endpoint e repita." content: application/problem+json: schema: { $ref: "#/components/schemas/Problema" } "503": description: "Dependência do Dracma fora (`code: service_unavailable`, retryable)." 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: As 50 entregas mais recentes da operação (da mais nova para a mais antiga), sem o corpo do evento. content: application/json: schema: type: object required: [data] properties: data: type: array items: type: object description: Entrega de webhook. Campos opcionais aparecem conforme o estado. properties: id: { type: string } event_id: { type: string } event_type: { type: string } environment: { type: string } status: { type: string, description: "pending, delivered, dlq ou skipped (lista aberta)" } skip_reason: { type: string } attempts: { type: integer } total_attempts: { type: integer } next_attempt_at: { type: [string, "null"], format: date-time } last_status_code: { type: [integer, "null"] } last_duration_ms: { type: [integer, "null"] } last_error: { type: [string, "null"], description: "truncado em 200 caracteres" } created_at: { type: string, format: date-time } delivered_at: { type: [string, "null"], format: date-time } resolved_at: { type: [string, "null"], format: date-time } resolution_reason: { type: [string, "null"] } /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; `scheduled` é o número de entregas reabertas (uma por URL cadastrada). content: application/json: schema: type: object required: [event_id, scheduled] properties: event_id: { type: string } scheduled: { type: integer } webhooks: evento: post: summary: Mudança de estado de depósito/saque, 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 (resposta do receptor). Qualquer resposta que não seja 2xx é reentregue por até 72 h, inclusive 401; responda 2xx ao que já foi tratado, mesmo duplicado." } deposit.crypto.detected: post: summary: "v1.2.0 — Transação vista na rede (0 confirmações) para o endereço da cobrança. NÃO credita." requestBody: { required: true, content: { application/json: { schema: { $ref: "#/components/schemas/Evento" } } } } responses: { "200": { description: Recebido. } } deposit.crypto.confirmations: post: summary: "v1.2.0 — Contagem de confirmações mudou (n/N). Coalescido: no máx. 1 a cada 30 s por cobrança; a última antes de `paid` sempre sai." requestBody: { required: true, content: { application/json: { schema: { $ref: "#/components/schemas/Evento" } } } } responses: { "200": { description: Recebido. } } payment_link.paid: post: summary: "v1.2.0 — Cobrança do link ficou `paid`. Fetch-back em GET /payment-links/{id} e GET /deposits/{charge_id}." requestBody: { required: true, content: { application/json: { schema: { $ref: "#/components/schemas/Evento" } } } } responses: { "200": { description: Recebido. } } components: securitySchemes: mtls: type: mutualTLS description: "Certificado de cliente por operação, emitido pela CA do Dracma. OPCIONAL: habilitado por instância; não é exigido pela API (só a assinatura HMAC é)." 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. Escopos da chave (deny-by-default; operação sem o escopo da rota ⇒ 403 `insufficient_scope`): `read` (GET), `deposits:write`, `withdrawals:write`, `rules:propose` e, desde a v1.2.0, `fx:read` (`GET /fx/quote`), `payment_links:read` (`GET /payment-links*`) e `payment_links:write` (`POST /payment-links`, `POST /payment-links/{id}/cancel`). `deposits:write` continua sendo o escopo de criar depósito cripto. 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]+$" } IdLink: name: id in: path required: true description: "Id do link (`plk_…`). O `slug` público da `url` NÃO é o id e responde 404." schema: { type: string, pattern: "^plk_[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 (escopo, método/moeda não habilitado, IP fora da allowlist, nas instâncias que a aplicam; a lista de IPs pode estar desligada). `crypto_not_enabled` quando o alvo é cripto (desligada na operação ou ativo fora da lista). 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], description: "v1.2.0 — ativos cripto habilitados da operação (kind=crypto, methods=[crypto]) vêm das CAPACIDADES do provedor ligado, não de lista fixa; sem provedor a lista não os traz." } 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_0192a3b45c6d7e8f9a0b1c2d3e4f5a6b] } 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). Em saque PIX `returned` só vêm `reason_code` (pode ser `unspecified`), `returned_amount` (pode ser MENOR que o saque: devolução parcial) e `returned_at`; `reason` e `return_fee` não vêm." 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], description: "em PIX só CPF e CNPJ: os outros resultam em 422 document_invalid (depósito) ou em bloqueio (saque)" } 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 description: "v1.2.0 — só `crypto` é aceito nesta versão (card/wire/swift/boleto seguem campo desconhecido ⇒ 400)." properties: crypto: type: object additionalProperties: false description: "OBRIGATÓRIO quando method=crypto (400 validation_failed, field=method_options.crypto.pay_currency, reason=required)." properties: pay_currency: { $ref: "#/components/schemas/AtivoCripto" } fx_quote_id: type: string pattern: "^fxq_[A-Za-z0-9]+$" description: "v1.2.0 — aceito no formato mas, com method=crypto, ⇒ 422 `fx_quote_not_applicable` (a cotação cripto é travada na criação). Com os demais métodos segue como antes." expires_in_seconds: { type: integer, minimum: 300, maximum: 86400, default: 1800, description: "IGNORADO quando method=crypto: a validade é a da cotação." } 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: { $ref: "#/components/schemas/InstrucoesCripto" } 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, detected, confirming] description: | Máquina de estados em CONTRATO.md §6.1. Só `paid` credita (pelo `amount`). v1.2.0 (só `method=crypto`): `detected` (transação vista na rede, 0 confirmações) e `confirming` (>= 1 e < `confirmations_required`) são NÃO terminais e NUNCA creditam; leitor v1.1 os trata como desconhecidos. Cripto: pending → detected → confirming → paid; pending → expired → under_review (pagamento tardio, `PAID_AFTER_EXPIRY`); (detected|confirming) → under_review (`AMOUNT_MISMATCH`/`WRONG_ASSET`); (detected|confirming) → pending (reorg antes de confirmar); paid → chargeback (reorg depois). 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_0192a3b45c6d7e8f9a0b1c2d3e4f5a6b] } 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; v1.2.0: WRONG_ASSET). Vazio fora de revisão." items: { type: string } provider: type: [string, "null"] pattern: "^[a-z0-9][a-z0-9_.-]{0,39}$" description: "v1.2.0 — id OPACO do provedor de liquidação que atende a cobrança (ADR-0041). NÃO é enum: valor desconhecido é válido (leitor tolerante); nenhuma lógica do operador pode depender dele. Informativo; só em method=crypto." crypto: description: "v1.2.0 — progresso da cobrança cripto; preenchido só em method=crypto. O operador credita o JOGADOR por `amount` (BRL) ao ver `paid`; o Dracma credita a OPERAÇÃO em ativo (`credited`)." oneOf: [ { $ref: "#/components/schemas/CriptoProgresso" }, { type: "null" } ] 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 } 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 — ACEITO e IGNORADO no recorte PIX (sem câmbio)" } payout_currency: { type: string, description: "v1.1.0 — ACEITO e IGNORADO no recorte PIX: o saque sai em BRL (sem 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], description: "em `requested` (consulta pendente ao diretório do PIX): `manual`, `reasons: []`, `decided_at: null`; não conclua nada de `mode` nesse estado" } 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_0192a3b45c6d7e8f9a0b1c2d3e4f5a6b] } 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_0192a3b45c6d7e8f9a0b1c2d3e4f5a6b] } 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_0192a3b45c6d7e8f9a0b1c2d3e4f5a6b] } 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 # --- 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 # --- v1.2.0 (aditivos; cripto e links) --- - deposit.crypto.detected - deposit.crypto.confirmations - payment_link.paid 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/Recebimento" } - { $ref: "#/components/schemas/LinkDePagamento" } AtivoCripto: type: string description: "v1.2.0 — código do registro de moedas (`GET /currencies`): ATIVO.REDE." pattern: "^[A-Z0-9]{3,12}\\.[A-Z0-9]{2,12}$" examples: ["BTC.BTC", "USDT.TRON"] DecimalString: type: string pattern: "^(0|[1-9][0-9]{0,20})(\\.[0-9]{1,18})?$" description: "v1.2.0 — decimal exato em string (nunca float)." MotivoRevisaoCripto: type: string description: "late_payment ⇒ review_reasons PAID_AFTER_EXPIRY; amount_mismatch ⇒ AMOUNT_MISMATCH; wrong_asset ⇒ WRONG_ASSET" enum: [late_payment, amount_mismatch, wrong_asset] CotacaoCripto: type: object description: "v1.2.0 — cotação INDICATIVA (`binding=false`) de pagar `amount` (BRL) em `pay_currency`." required: [amount, pay_currency, pay_amount, pay_amount_decimal, rate, mid_rate, spread_bps, quoted_at, expires_at, binding] properties: amount: { $ref: "#/components/schemas/Money" } pay_currency: { $ref: "#/components/schemas/AtivoCripto" } pay_amount: { $ref: "#/components/schemas/Money" } pay_amount_decimal: { $ref: "#/components/schemas/DecimalString" } rate: { type: string, pattern: "^[0-9]{1,12}(\\.[0-9]{1,8})?$", description: "BRL por 1 unidade MAIOR do ativo, já com o spread; arredondada PARA BAIXO em 8 casas. NÃO é a semântica de Cambio.rate." } mid_rate: { type: string, pattern: "^[0-9]{1,12}(\\.[0-9]{1,8})?$", description: "mediana das fontes, sem spread" } spread_bps: { type: integer, minimum: 0, maximum: 2000 } quoted_at: { type: string, format: date-time } expires_at: { type: string, format: date-time, description: "até quando um depósito criado AGORA manteria esta cotação (quoted_at + quote_ttl_seconds); informativo" } binding: { type: boolean, const: false } InstrucoesCripto: type: object description: "v1.2.0 — instruções de pagamento cripto (substitui o objeto `crypto` anterior; address, pay_amount, rate e expires_at mantêm nome e tipo). `memo` nunca é emitido." required: [address, network, pay_currency, pay_amount, pay_amount_decimal, rate, mid_rate, spread_bps, quoted_at, expires_at, payment_uri, checkout_url, confirmations_required, testnet] properties: address: { type: string, description: "endereço ÚNICO desta cobrança; nunca reutilizado; sem memo/tag" } network: { type: string, examples: [bitcoin, litecoin, ethereum, tron] } pay_currency: { $ref: "#/components/schemas/AtivoCripto" } pay_amount: { $ref: "#/components/schemas/Money" } pay_amount_decimal: { $ref: "#/components/schemas/DecimalString" } rate: { type: string, pattern: "^[0-9]{1,12}(\\.[0-9]{1,8})?$", description: "BRL por 1 unidade MAIOR do ativo (1 BTC), com spread, travada na criação. NÃO é a semântica de Cambio.rate." } mid_rate: { type: string, pattern: "^[0-9]{1,12}(\\.[0-9]{1,8})?$" } spread_bps: { type: integer, minimum: 0, maximum: 2000 } quoted_at: { type: string, format: date-time } expires_at: { type: string, format: date-time } payment_uri: { type: string, description: "BIP-21 (BTC/LTC), EIP-681 (ETH e ERC-20) ou URI TRON" } checkout_url: { type: string, format: uri, description: "página hospedada em pay.; URL-capacidade, tratar como segredo curto" } confirmations_required: { type: integer, minimum: 1 } testnet: { type: boolean, description: "true em sandbox/hml: o ativo é de TESTNET e não vale nada" } CriptoProgresso: type: object description: "v1.2.0 — progresso da cobrança cripto (`Deposito.crypto`)." required: [txid, confirmations, received, credited, spread_amount, detected_at, confirmed_at, review_reason] properties: txid: { type: [string, "null"] } confirmations: type: object required: [current, required] properties: current: { type: integer, minimum: 0 } required: { type: integer, minimum: 1 } received: { oneOf: [ { $ref: "#/components/schemas/Money" }, { type: "null" } ], description: "soma on-chain recebida (no ativo)" } credited: { oneOf: [ { $ref: "#/components/schemas/Money" }, { type: "null" } ], description: "creditado à OPERAÇÃO no ativo (equivalente ao `amount` ao mid_rate). Só em paid." } spread_amount: { oneOf: [ { $ref: "#/components/schemas/Money" }, { type: "null" } ], description: "received - credited, receita do Dracma. Só em paid." } detected_at: { type: [string, "null"], format: date-time } confirmed_at: { type: [string, "null"], format: date-time } review_reason: { oneOf: [ { $ref: "#/components/schemas/MotivoRevisaoCripto" }, { type: "null" } ] } StatusLink: type: string enum: [active, paid, expired, cancelled] MetodoDoLink: type: object required: [method] additionalProperties: false properties: method: { type: string, enum: [pix, crypto] } assets: type: array description: "só method=crypto; obrigatório e não vazio; ⊆ ativos habilitados da operação (rechecado quando o pagador age)" minItems: 1 uniqueItems: true items: { $ref: "#/components/schemas/AtivoCripto" } CriarLink: type: object description: | v1.2.0 — `fixed` exige `amount` e proíbe `min_amount`/`max_amount`; `open` exige `min_amount` e `max_amount` (min <= max; max <= teto do ativo) e proíbe `amount`. Valores sempre em BRL. `usage=multi` aceita `max_uses`. required: [external_id, player, amount_type, allowed_methods] additionalProperties: false properties: external_id: { type: string, maxLength: 64 } player: { $ref: "#/components/schemas/Jogador" } amount_type: { type: string, enum: [fixed, open] } amount: { $ref: "#/components/schemas/Money", description: "BRL; obrigatório se fixed, proibido se open" } min_amount: { $ref: "#/components/schemas/Money", description: "BRL; obrigatório se open" } max_amount: { $ref: "#/components/schemas/Money", description: "BRL; obrigatório se open; <= teto da operação" } allowed_methods: type: array minItems: 1 maxItems: 2 items: { $ref: "#/components/schemas/MetodoDoLink" } usage: { type: string, enum: [single, multi], default: single } max_uses: { type: integer, minimum: 2, maximum: 100000, description: "só multi; nº de cobranças PAGAS que encerram o link; ausente = ilimitado até expirar" } description: { type: string, maxLength: 140, description: "texto simples mostrado ao pagador; sem HTML; não pode conter PII" } expires_at: { type: string, format: date-time, description: "default agora+7d; mín. agora+5min; máx. agora+90d" } metadata: type: object maxProperties: 20 additionalProperties: { type: string, maxLength: 256 } LinkDePagamento: type: object description: "v1.2.0 — link de pagamento público. Também é o `data.object` de `payment_link.paid`." required: [id, external_id, status, version, usage, amount_type, allowed_methods, player_ref, url, uses_paid, expires_at, created_at, updated_at] properties: id: { type: string, pattern: "^plk_[A-Za-z0-9]+$" } external_id: { type: string } status: { $ref: "#/components/schemas/StatusLink" } version: { type: integer, minimum: 1 } usage: { type: string, enum: [single, multi] } amount_type: { type: string, enum: [fixed, open] } amount: { oneOf: [ { $ref: "#/components/schemas/Money" }, { type: "null" } ] } min_amount: { oneOf: [ { $ref: "#/components/schemas/Money" }, { type: "null" } ] } max_amount: { oneOf: [ { $ref: "#/components/schemas/Money" }, { type: "null" } ] } allowed_methods: { type: array, items: { $ref: "#/components/schemas/MetodoDoLink" } } player_ref: { type: string } description: { type: [string, "null"] } url: { type: string, format: uri, description: "https://pay./l/; o slug (128 bits) é a capacidade pública e é distinto do `id`" } max_uses: { type: [integer, "null"] } uses_paid: { type: integer, minimum: 0 } last_charge_id: { type: [string, "null"], description: "dep_ da cobrança mais recente do link" } expires_at: { type: string, format: date-time } paid_at: { type: [string, "null"], format: date-time } cancelled_at: { type: [string, "null"], format: date-time } metadata: { type: object, additionalProperties: { type: string } } created_at: { type: string, format: date-time } updated_at: { type: string, format: date-time } 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`, `recall` e `receipt`. No recorte PIX só `deposit`, `withdrawal`, `refund`, `chargeback` e `return` são emitidos (`return` = depósito ou saque PIX `returned`); `fee`, `recall` e `receipt` nunca. O item `return` conta em `count` mas não entra em nenhuma soma de `totals`. 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" } ], description: "sempre null hoje (o Dracma não desconta tarifa do crédito)" } 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", description: "sempre \"0\" hoje" } 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. v1.2.0 acrescenta `crypto_not_enabled` (403), `asset_not_supported` (422), `quote_unavailable` (503, retryable, com `reason`), `fx_quote_not_applicable` (422), `amount_out_of_range` (422), `payment_link_unavailable` (410, rota pública) e `payment_link_method_not_allowed` (422). reason: type: string description: "v1.2.0 — subcódigo máquina; em `quote_unavailable`: sources_diverge | stale | insufficient_sources | upstream_down | invalid_rate | params_invalid." 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"