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 exemplo BRL.

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), inclusive status, version, paid_amount e decision;
  • 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 campo trace_id do 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, rail e type de evento desconhecidos como não terminais: grave, não mova dinheiro e confira depois pelo fetch-back (GET /deposits/{id} ou GET /withdrawals/{id});
  • trate code e errors[].reason desconhecidos pela classe HTTP e pelo campo retryable;
  • aceite null em 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.

Veja também