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-specificQuery. The RGS resolves which game from the session, not the body, so you never name the game here.message(response) — the game-specificResult.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 oneof — set 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
camelCase—mineCount,dropPoint,roundId(notmine_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.