Message Protocol

Every game interaction goes through POST /game/play. The HTTP body is a thin envelope; the game-specific payload lives inside a single message field as protobuf-JSON.

The Envelope

Request

POST /game/play HTTP/1.1
Authorization: Bearer <sessionToken>
Content-Type: application/json
{
  "message": { "bet": { "amount": "1.00", "over": 5000 } }
}

Response

{
  "message": { "...": "game-specific result" },
  "roundId": "9f1c…"
}
  • message (request) — the game-specific Query. The RGS resolves which game from the session, not the body, so you never name the game here.
  • message (response) — the game-specific Result.
  • roundId — the round this bet belongs to. For multi-action games, pass it back on follow-up actions.

The client wrapper is tiny — it stringifies { message } and unwraps message from the response:

import type { Query, Result } from "@jackpot-studio/jps-proto/dice";

export async function playRound(body: Query, token: string): Promise<Result> {
  const res = await fetch(`${RGS_BASE_URL}/game/play`, {
    method: "POST",
    headers: {
      "Content-Type": "application/json",
      Authorization: `Bearer ${token}`,
    },
    body: JSON.stringify({ message: body }),
  });
  const json = await res.json();
  return json.message;
}

Message Shapes

Each game ships a protobuf schema in jps-proto that generates TypeScript types (@jackpot-studio/jps-proto/<game>) and the Go structs the RGS uses. Three message types matter to the client:

Type Direction Description
Query client → RGS A oneof request. The variant you set (bet, start, next, reveal, cashout, verify, …) selects the action.
Result RGS → client metadata (balance + round id) + a oneof response mirroring the request, plus optional state and error.
GameState RGS → client The persisted, client-safe view of a round (used by /game/current and /game/round).

A Query is a oneofset exactly one variant:

// Dice bet
const q: Query = { bet: { amount: "1.00", over: 5000 } };

// Mines: start a round, then reveal a tile in a follow-up call
const start:  Query = { start:  { amount: "1.00", mineCount: 3, gridSize: 25 } };
const reveal: Query = { reveal: { field: 7, roundId } };

Every Result carries shared metadata:

{
  "metadata": { "balance": "104.00", "roundId": "9f1c…" },
  "bet": { "roll": 7421, "result": { "payout": "2.00", "multiplier": "2.0" } }
}

The shared result object (common.Result) is how payouts are reported across all games:

Field Type Meaning
payout string Amount credited back to the player (0 on a loss).
multiplier string Payout ÷ wager for this round.

Wire Format Rules

The RGS marshals responses with protojson (EmitDefaultValues). Match these rules when building requests and parsing responses:

  • Field names are camelCasemineCount, dropPoint, roundId (not mine_count).
  • Money is always a decimal string"1.00", never a float. This avoids float rounding on currency. Wagers, payouts, and multipliers are all strings.
  • Enums — send either the numeric value (the generated TS types use numbers, e.g. risk: 1) or the string name ("RISK_LOW"); the RGS accepts both. Responses return enums as their string name.
  • Default values are emitted — zero-valued fields appear in responses rather than being omitted.

Errors

A failed bet returns a non-2xx HTTP status with a structured error body rather than a Result. See Errors for the categories, codes, and how to surface them. Successful responses never contain an error.