Convenções HTTP¶
Para: Dev — Conferido em 2026-10-18.
Regras que valem para toda chamada à API /v1: como representar dinheiro e datas, quais IDs existem de verdade,
o que mandar nos cabeçalhos, quanto tempo esperar uma resposta e como o Dracma evolui sem quebrar o seu código.
O que é¶
A API do Dracma é uma API JSON sobre HTTPS, feita para ser chamada do servidor do seu conector, nunca de um navegador. Esta página reúne as convenções comuns. Autenticação, erros, idempotência, paginação e limites têm página própria; os links estão no fim da página.
| Item | Convenção |
|---|---|
| Base | https://<host da API>/v1. O host muda por ambiente: veja Ambientes, tipos de operação e hosts. |
| Corpo | JSON em UTF-8, um objeto na raiz. Só POST tem corpo. |
| Resposta de sucesso | application/json. |
| Resposta de erro | application/problem+json (Erros). |
| Navegador | Não suportado. A API não responde a CORS para o seu domínio; chame do servidor. |
| Métodos | GET, HEAD e POST. Qualquer outro método autenticado recebe 403 insufficient_scope. |
Quando usar¶
Consulte esta página ao montar o cliente HTTP do conector e ao revisar um erro 400 que você não entende. Ela responde
a perguntas como "mando amount como número?", "o created_at tem fração de segundo?" e "quanto espero antes de
desistir de um POST?".
Como funciona¶
Dinheiro: Money¶
Todo valor monetário é um objeto Money com dois campos:
amount: string com um inteiro na menor unidade da moeda (centavo no BRL). Nunca número JSON, nunca decimal, nunca com sinal ou expoente.currency: código da moeda, por exemploBRL.
O amount casa com ^(0|[1-9][0-9]{0,37})$: de 1 a 38 dígitos, sem zeros à esquerda. Por isso "05000",
"50.00", "-1", "5e3" e 5000 (número) são recusados com 400 validation_failed. Em criação de depósito e de
saque o valor também precisa ser maior que zero.
{ "amount": "5000", "currency": "BRL" }
Esse exemplo é R$ 50,00. O número de casas da moeda vem de exponent em GET /v1/currencies: o BRL tem exponent
2. A API nunca devolve nem aceita valor decimal; a conversão é sua.
Converta sem float. Um float de 64 bits perde precisão (0.1 + 0.2) e nenhum banco do mundo aceita "quase
R$ 50,00". Faça a conversão por texto, validando as casas decimais:
import re
def para_menor_unidade(texto: str, expoente: int) -> str:
"""'50.00' (BRL, expoente 2) -> '5000'. Sem float."""
m = re.fullmatch(r"(\d+)(?:\.(\d*))?", texto)
if not m or len(m.group(2) or "") > expoente:
raise ValueError("valor inválido ou com mais casas que a moeda")
inteiro, frac = m.group(1), (m.group(2) or "").ljust(expoente, "0")
return (inteiro + frac).lstrip("0") or "0"
def para_decimal(minima: str, expoente: int) -> str:
"""'5000' -> '50.00'. Só para exibir ao jogador."""
if not re.fullmatch(r"0|[1-9][0-9]{0,37}", minima):
raise ValueError("amount fora do formato do Dracma")
s = minima.rjust(expoente + 1, "0")
return f"{s[:-expoente]}.{s[-expoente:]}" if expoente else s
export function paraMenorUnidade(texto: string, expoente: number): string {
const m = /^(\d+)(?:\.(\d*))?$/.exec(texto);
if (!m || (m[2] ?? "").length > expoente) throw new Error("valor inválido ou com mais casas que a moeda");
const bruto = m[1] + (m[2] ?? "").padEnd(expoente, "0");
return bruto.replace(/^0+(?=\d)/, "");
}
| Entrada | Expoente | Saída |
|---|---|---|
"50.00" |
2 | "5000" |
"0.05" |
2 | "5" |
"19" |
2 | "1900" |
"1234.5" |
2 | "123450" |
"1.234" |
2 | erro: mais casas do que a moeda |
Guarde e some valores como inteiros (bigint, BigInteger, numeric(38,0) no banco). Um amount de 38 dígitos não
cabe em int64 nem em double.
Datas e horas¶
Todo instante é RFC 3339 em UTC, sempre terminado em Z. O que muda é a precisão:
| Objeto | Formato dos instantes | Exemplo |
|---|---|---|
Depósito (created_at, updated_at, paid_at, expires_at), evento, server_time do ping |
segundos | 2026-10-18T15:04:05Z |
Saque (created_at, updated_at, paid_at) e return.returned_at |
segundos ou fração de segundo | 2026-10-18T15:04:05.123456Z |
Escreva um parser tolerante: aceite com e sem fração (time.RFC3339Nano em Go, Date.parse em JavaScript,
datetime.fromisoformat em Python 3.11+). Não compare instantes como texto e não assuma número fixo de casas. Os
parâmetros de entrada updated_since aceitam as duas formas.
Dias de reconciliação e janelas de regra seguem outro calendário (o fuso da operação). Veja Dia, fuso e horários.
Identificadores¶
Os IDs que o Dracma cria são opacos, com prefixo. Trate-os como texto: não derive nada do formato, não ordene e não extraia partes.
| Prefixo | Objeto | Observação |
|---|---|---|
dep_ |
Depósito | Vem em id do depósito. |
wdr_ |
Saque | Vem em id do saque. |
evt_ |
Evento (webhook e GET /events) |
É o Dracma-Event-Id do webhook. |
chg_ |
Pedido de mudança de regras | Resposta de POST /rules/change-requests. |
key_test_, key_live_ |
Chave de API (Dracma-Key-Id) |
O prefixo indica o tipo da operação (Autenticação). |
Reservado no contrato
O contrato cita também os prefixos rcn_ (reconciliação), rcv_ (recebimento bancário) e fxq_ (cotação de
câmbio). Nenhuma rota os devolve hoje: reconciliação não tem ID próprio, e recebimentos e cotações nunca existem
(GET /receipts/{id} e GET /fx/quotes/{id} respondem sempre 404 not_found). Veja
Rotas e campos reservados.
IDs seus têm formato controlado por você, dentro de um limite:
| Campo | Formato |
|---|---|
external_id (depósito, saque) |
1 a 64 caracteres [A-Za-z0-9_.:-]; único por operação e por tipo |
player.external_ref |
1 a 64 caracteres [A-Za-z0-9_.:-]; opaco, imutável, nunca e-mail nem telefone |
Idempotency-Key |
16 a 128 caracteres [A-Za-z0-9_.:-] (Idempotência) |
O operation_id que aparece no ping e nos eventos identifica a sua operação. Ele vem sempre da chave de API; um
operation_id na raiz do corpo de uma requisição é ignorado, e aninhado em qualquer outro lugar vira 400
validation_failed (unknown_field).
Cabeçalhos e corpo¶
Requisição.
| Cabeçalho | Quando | Observação |
|---|---|---|
Dracma-Key-Id, Dracma-Timestamp, Dracma-Nonce, Dracma-Signature |
toda chamada | Autenticação HMAC. |
Idempotency-Key |
todo POST |
Obrigatório, inclusive em cancel e em /sandbox/*. |
Content-Type: application/json; charset=utf-8 |
POST com corpo |
Recomendado. A API não valida este cabeçalho (interpreta o corpo como JSON de qualquer jeito), mas proxies e bibliotecas HTTP do seu lado dependem dele. |
Accept |
opcional | Ignorado: a resposta é sempre JSON. |
If-Match |
POST /rules/change-requests |
Obrigatório nessa rota. |
traceparent |
opcional | Se vier, o Dracma continua o seu trace (seção Rastreamento). |
POST de ação sem corpo (cancel, por exemplo) leva Idempotency-Key e assina o sha256 de um corpo vazio. O
Content-Type não é necessário.
HEAD funciona em toda rota GET e devolve só os cabeçalhos; assine com o método HEAD. OPTIONS e os métodos PUT,
PATCH e DELETE não existem e voltam 403 insufficient_scope.
Validação estrita. O corpo é recusado com 400 validation_failed antes de qualquer efeito se tiver:
- UTF-8 inválido (
invalid_utf8); - JSON malformado (
syntax) ou conteúdo depois do objeto (trailing_data); - a mesma chave duas vezes, em qualquer nível, mesmo variando maiúsculas e minúsculas (
duplicate_key); - campo que o schema não tem (
unknown_field), inclusivestatus,version,paid_amountedecision; - tipo errado (
type), por exemplo número onde vai string.
A tabela de reason está em Erros.
Tamanhos de corpo¶
Há dois limites em camadas:
| Camada | Limite | O que você recebe ao passar |
|---|---|---|
| API | 64 KiB | 413 com corpo problem+json: code: validation_failed e errors[0] = {field: "body", reason: "too_large"} |
| Entrada pública (borda) | 1 MB | 413 sem corpo problem+json |
Um pedido de 100 KiB chega à API e recebe o erro completo. Um pedido de 2 MB é cortado antes, e o seu cliente deve tratar
413 sem corpo. Na prática nenhum corpo legítimo passa de poucos KiB; se o seu passa, o metadata está grande demais.
Timeouts¶
Configure o cliente HTTP do conector com 10 s de timeout total. Dentro disso:
| Chamada | Tempo de resposta | Resposta |
|---|---|---|
POST /v1/deposits |
até cerca de 7 s | 201 com as instruções de pagamento prontas, ou 202 com o depósito em created se o PSP não respondeu a tempo |
POST /v1/withdrawals |
rápido; o envio ao banco é assíncrono | sempre 201, nunca 202 |
POST /v1/sandbox/deposits/{id}/pay |
espera até 3 s pelo evento de pagamento | 200 com o depósito no estado atual |
| Demais rotas | normalmente abaixo de 1 s |
Um 202 em depósito não é erro: o objeto existe. Siga pelo webhook e pelo GET /v1/deposits/{id}. Um timeout do seu
lado não diz se houve efeito: repita com a mesma Idempotency-Key (Idempotência e repetição).
Rastreamento¶
Toda resposta /v1 (sucesso ou erro, inclusive 401) traz:
Dracma-Trace-Id: o ID do trace da requisição, 32 caracteres hexadecimais. É o mesmo valor do campotrace_iddo erro;traceparent: o contexto W3C do span do servidor.
Se você mandar um traceparent válido, o Dracma continua o seu trace e o Dracma-Trace-Id devolvido é o seu. Guarde
o Dracma-Trace-Id de toda resposta que não seja 2xx no log do conector. Ao abrir um chamado (Suporte),
informe esse ID, a hora em UTC e o operation_id; com ele o suporte acha a requisição inteira.
Leitor tolerante e evolução¶
A v1 só cresce: ganha campos, estados, eventos, métodos e códigos de erro. Por isso o seu código deve ser um leitor tolerante:
- ignore campo desconhecido em qualquer objeto;
- trate
status,method,kind,railetypede evento desconhecidos como não terminais: grave, não mova dinheiro e confira depois pelo fetch-back (GET /deposits/{id}ouGET /withdrawals/{id}); - trate
codeeerrors[].reasondesconhecidos pela classe HTTP e pelo camporetryable; - aceite
nullem campo que hoje vem preenchido e vice-versa.
A política completa está em Versionamento.
Exemplos¶
Chamada de ping com o cabeçalho de rastreamento da resposta (GET /v1/ping, valores fictícios):
HTTP/1.1 200 OK
Content-Type: application/json
Dracma-Trace-Id: 4bf92f3577b34da6a3ce929d0e0e4736
traceparent: 00-4bf92f3577b34da6a3ce929d0e0e4736-00f067aa0ba902b7-01
{"ok":true,"operation_id":"0192a3b4-5c6d-7e8f-9a0b-1c2d3e4f5a6b","environment":"sandbox","server_time":"2026-10-18T15:04:05Z"}
Comparar o server_time com o relógio do conector é a forma mais barata de detectar clock skew (Autenticação HMAC).
Erros e casos de borda¶
| Situação | Resposta |
|---|---|
amount como número, com sinal, expoente, zeros à esquerda ou mais de 38 dígitos |
400 validation_failed |
amount igual a "0" em criação de depósito ou saque |
400 validation_failed com reason: required |
GET em rota que só existe como POST (por exemplo GET /v1/deposits/{id}/cancel) |
404 not_found |
POST (ou PUT, PATCH, DELETE, OPTIONS) em rota que não existe ou não aceita esse método |
403 insufficient_scope (o escopo é conferido antes da rota) |
Rota desconhecida em GET, com credencial válida |
404 not_found |
| Rota desconhecida com assinatura inválida | 401, não 404: a autenticação vem antes do roteamento |
Barra final (/v1/deposits/) |
A API ignora a barra, mas o caminho assinado é o que você enviou, com a barra |
Parâmetro de query inválido (limit=abc) |
400 validation_failed |
Caminho fora de /v1/* (/health, /, /metrics) |
404 sem corpo, da borda |
| Qualquer erro interno | 500 internal_error, sem detalhe técnico; o trace_id leva ao log do Dracma |
Falhas de TLS e erros de proxy no caminho até o Dracma não têm corpo problem+json: trate resposta que não é JSON
como erro de rede e repita com a mesma Idempotency-Key.