Batalhas
Sete rotas dirigem o lobby inteiro, e nenhuma delas é um início: uma batalha começa sozinha
quando todos os assentos estão READY. Leia
Ciclo de vida da batalha primeiro.
Abrir uma batalha
POST /battles escopo: battles:write → 201
{
"game_id": "sweet-bonanza",
"seats_total": 3,
"entry_amount": "25.50",
"currency": "BRL",
"creator_player_ref": "alice",
"creator_game_url": "https://provider.example/session/abc"
}
seats_total precisa ser de 2 a 8. O criador é sentado no índice 0 automaticamente.
creator_game_url decide se a batalha pode começarInforme e o assento 0 fica READY. Omita e o assento 0 fica FILLED: reivindicado, mas
incapaz de jogar. Uma batalha de dois assentos criada sem ele não vai começar quando o outro
jogador entrar, porque o assento 0 continua fora de READY.
Omita apenas se você pretende chamar POST /battles/{id}/seats para o criador depois.
A resposta é a batalha inteira, incluindo um ws_token para o feed ao vivo:
{
"id": "btl_uCHSnowFVUXf73YaSH2j",
"status": "OPEN",
"entry_amount": "25.5",
"ws_url": "wss://slotbattle.example.com/ws?battle_id=btl_uCHSnowFVUXf73YaSH2j",
"ws_token": "eyJhbGciOiJFZERTQSIsInR5cCI6IkpXVCIsImtpZCI6ImsxIn0...",
"ws_token_expires_at": "2026-08-03T03:55:42Z",
"ws_subprotocol": "slotbattle.jwt",
"seats": [
{ "seat_index": 0, "seat_ref": "usr_s0", "player_ref": "alice", "is_bot": false, "status": "READY" },
{ "seat_index": 1, "seat_ref": "usr_s1", "player_ref": "", "is_bot": false, "status": "EMPTY" },
{ "seat_index": 2, "seat_ref": "usr_s2", "player_ref": "", "is_bot": false, "status": "EMPTY" }
],
"expires_at": "2026-08-03T03:47:10Z"
}
"25.50" voltou como "25.5", porque valores são normalizados. expires_at é a janela do
lobby: se não encher até lá, a batalha é cancelada.
ws_token pode estar ausenteSe a instância não tem chave de assinatura de viewer, a batalha é criada normalmente e o
ws_token fica ausente. É um campo omitempty, então não há erro para capturar, e a
visualização ao vivo fica indisponível até uma chave ser configurada. Cheque a presença dele em
vez de presumir.
Falhas que vale tratar
| Status | Código | Significado |
|---|---|---|
400 | invalid_request | seats_total fora de 2..8, creator_player_ref ou game_id faltando, ou campo desconhecido |
403 | game_not_allowed | O jogo não está na sua allowlist |
409 | conflict | O criador já está em outra batalha ativa |
503 | unavailable | Em capacidade máxima. Retente com backoff |
Sentar um jogador
POST /battles/{id}/seats escopo: battles:write → 200
{ "player_ref": "bob", "game_url": "https://provider.example/session/def" }
Ocupa o assento vazio de menor índice, ou promove o assento FILLED do chamador para READY.
Os dois campos são obrigatórios, então o assento fica sempre READY depois, que é o que pode
disparar o início automático.
Um jogador com assento READY ou PLAYING não pode entrar de novo (409). Um com assento
FILLED está promovendo, o que é permitido e é como um criador que omitiu creator_game_url
se torna pronto.
A resposta traz um ws_token novo, para o browser do jogador que entrou começar a assistir na
hora.
Encher o resto com bots
POST /battles/{id}/bot-seats escopo: bots:write → 200
Preenche todos os assentos vazios restantes. Bots recebem sessões demo emitidas sob as
credenciais de provedor do próprio casino, então caem em READY, que normalmente é o que
inicia a batalha.
{}
Um corpo vazio nomeia todos os bots automaticamente. Para nomeá-los:
{
"player_ref": "alice",
"bots": [{ "player_ref": "bot_lucky" }, { "player_ref": "bot_swift" }]
}
Informar bots significa informar ao menos tantas entradas quantos assentos vazios. Menos é
409.
Dois portões valem aqui e em nenhum outro lugar:
- Só o criador. Um
player_refque não é o criador é403. - A allowlist é rechecada. Um jogo revogado enquanto o lobby estava aberto interrompe a batalha neste ponto, em vez de deixá-la rodar.
Um 502 significa que a emissão da sessão demo de um bot falhou no provedor, o que é problema
upstream e não requisição ruim.
Sair
POST /battles/{id}/leave escopo: battles:write → 200
{ "player_ref": "bob" }
Libera o assento de volta para EMPTY, limpando jogador, URL de vídeo e qualquer resultado.
Sair não passa o lobby para outra pessoa. Cheque creator_player_ref antes de chamar isso em
nome de um criador, e garanta que a sua UI diga "cancelar", não "sair".
Cancelar
POST /battles/{id}/cancel escopo: battles:write → 200
Só o criador, só em OPEN. Uma batalha que já começou não pode ser cancelada aqui, e responde
409.
O corpo é opcional. Envie {"player_ref": "alice"} quando a chamada vier de uma ação de
jogador, para a checagem de criador rodar. Omita para um cancelamento iniciado por operador,
que pula essa checagem.
Ler e listar
GET /battles/{id} escopo: battles:read
GET /battles?status=OPEN&limit=20 escopo: battles:read
limit tem padrão 20 e é limitado a 100: pedir mais é cortado em silêncio, não recusado. Os
resultados vêm dos mais novos para os mais antigos.
Nenhuma das duas rotas emite ws_token. Emita um explicitamente com
tokens de viewer quando um espectador precisar conectar numa batalha que ele
não criou.
Uma batalha de outro casino responde 404, igual a uma que nunca existiu.
Não faça polling
Não fique dando polling em GET /battles/{id} para acompanhar uma batalha. Assine o
feed WebSocket para estado ao vivo, e trate o webhook como
autoridade sobre o resultado. Reserve as leituras para renderizar uma página que alguém acabou
de abrir.