Slotdaddy Player Runtime API
Audience: studios integrating games against Slotdaddy.
This document specifies the HTTP surface a running game client (the player runtime) speaks to the Slotdaddy RGS. It does not cover the studio control plane (math uploads, key rotation, environments); that is a separate spec. Operator integration (wallet adapters, settlement webhooks, jurisdiction routing) is handled per operator on a bespoke basis and is not documented here.
Design overview
The runtime API is intentionally small: five POST endpoints, an opaque round.state carrier, and a handful of optional headers. The shape is meant to be easy to implement on the client side and easy to certify on the server side.
Three values shape the design:
- Server authority. The server runs the math, owns the RNG, and settles the bet. The client never finalizes a payout.
- Forward compatible additivity. New capabilities ship as optional headers, optional response fields, and new endpoints under reserved prefixes. Old clients keep working.
- Replay by default. Every settled round surfaces a deterministic
bookIDandrngSeedso disputes and certification can re-derive the exact outcome the player saw.
If you find Slotdaddy violating any of these, file a bug.
Transport, auth, and URL parameters
The operator launches a game by opening a URL like:
https://games.slotdaddy.com/<game-id>/?sessionID=<opaque>&rgs_url=rgs.slotdaddy.com&lang=en&device=desktop
The four query parameters are required and are read once at boot by the client SDK:
| Param | Required | Notes |
|---|---|---|
sessionID | yes | Opaque session token issued by the operator and minted into Slotdaddy when the operator opens the wallet session. The sole auth credential, passed in every POST body. |
rgs_url | yes | Hostname of the RGS the client should talk to. Lets Slotdaddy route by jurisdiction (e.g. rgs.slotdaddy.com, rgs-us.slotdaddy.com). |
lang | no (default en) | ISO 639-1 language code chosen by the operator (not necessarily the browser language). |
device | no (default desktop) | desktop or mobile. Hint for responsive design. |
All API calls are POST with Content-Type: application/json. Auth is implicit: sessionID is included in every request body. There is no Authorization header.
Optional request headers
These are opt-in. A client that omits them gets the default behavior described elsewhere in this spec.
| Header | Purpose |
|---|---|
Idempotency-Key | Stripe-style. See Idempotency. |
X-Slotdaddy-Schema-Version | Integer. Pin the round.state event schema version the client understands. Defaults to 1 if absent. |
X-Slotdaddy-Client | Free-form <name>/<version> for diagnostics (e.g. web-sdk/2.4.1). |
Endpoints
Five POST endpoints make up the runtime surface.
POST /wallet/authenticate body: { sessionID, language }
POST /wallet/balance body: { sessionID }
POST /wallet/play body: { sessionID, amount, mode }
POST /wallet/end-round body: { sessionID }
POST /bet/event body: { sessionID, event }
POST /wallet/authenticate
Opens the session for play. Must be the first call. Returns balance, jurisdiction config, bet levels, and, if a round was left active by a prior session, the round to resume.
Request
{
"sessionID": "sess_8a2f...",
"language": "en"
}Response (200)
{
"balance": { "amount": 5000000, "currency": "USD" },
"config": {
"minBet": 200000,
"maxBet": 100000000,
"stepBet": 100000,
"defaultBetLevel": 1000000,
"betLevels": [200000, 500000, 1000000, 2500000, 5000000],
"jurisdiction": {
"socialCasino": false,
"disabledFullscreen": false,
"disabledTurbo": false,
"disabledSuperTurbo": false,
"disabledAutoplay": false,
"disabledSlamstop": false,
"disabledSpacebar": false,
"disabledBuyFeature": false,
"displayNetPosition": false,
"displayRTP": true,
"displaySessionTimer": false,
"minimumRoundDuration": 0
}
},
"round": null,
"serverTime": "2026-05-11T15:30:00.000Z",
"supportedSchemaVersions": [1]
}All amounts are integers in the operator's minor currency unit times API_MULTIPLIER = 1_000_000 (e.g. 5000000 = 5.00 USD). This keeps amounts representable as 64 bit integers without floating point drift.
If round is non-null, the operator's previous session ended mid-round and the client must resume it (replay round.state and call /wallet/end-round once animations complete).
POST /wallet/balance
Lightweight balance refresh, used by the SDK on a 60 second idle timer. Most endpoints that change state already return balance; reserve this for idle polling.
Request
{ "sessionID": "sess_8a2f..." }Response (200)
{ "balance": { "amount": 5000000, "currency": "USD" } }POST /wallet/play
Places a bet, debits the wallet, runs math, and returns the round outcome. The full game outcome is encoded in round.state (see The round.state contract).
amount must be a multiple of config.stepBet between config.minBet and config.maxBet; if enforceBetLevels is on (default), it must also be in config.betLevels. mode is a string; see Mode taxonomy.
A round must not already be active for this session. If one is, the server returns ROUND_ALREADY_ACTIVE.
Request
{
"sessionID": "sess_8a2f...",
"amount": 1000000,
"mode": "BASE"
}Response (200)
{
"balance": { "amount": 4000000, "currency": "USD" },
"round": {
"betID": 91823471,
"amount": 1000000,
"payout": 2500000,
"payoutMultiplier": 2.5,
"active": true,
"mode": "BASE",
"state": { "events": [/* BookEvent[], opaque to the SDK */] },
"bookID": "lines_v3_base_00041527",
"rngSeed": "a1b2c3d4e5f60718"
}
}bookID and rngSeed are the deterministic identifiers used for replay and audit. Clients that don't need them can ignore them.
active: true means the client owes the server an /wallet/end-round once the player has seen the outcome. active: false (rare, used for single shot bets that can't be resumed) means the round is already closed.
POST /wallet/end-round
Closes an active round. The client decides when to call this, typically after win animations finish (or sooner for fast round formats). This is what minimumRoundDuration enforces: the server rejects calls that come in too early.
Request
{ "sessionID": "sess_8a2f..." }Response (200)
{ "balance": { "amount": 6500000, "currency": "USD" } }POST /bet/event
Lightweight client to server signal during a round. Used for telemetry, fraud detection, and minimumRoundDuration enforcement. The client does not wait on the response; the server is free to reject unknown event names.
Request (string form)
{ "sessionID": "sess_8a2f...", "event": "client_loaded" }Request (extended form, also accepted)
{
"sessionID": "sess_8a2f...",
"event": "animation_complete",
"payload": { "durationMs": 1840 }
}The extended form is backward compatible: omit payload and the wire is the bare string form.
Response (200)
{ "event": "animation_complete" }Recommended event names (Slotdaddy reserves these, studios may add their own):
| Event | Meaning |
|---|---|
client_loaded | Asset bundle is ready; safe to fade out the loader. |
animation_complete | Win animation for the current round finished. |
user_skip | Player pressed slam stop / spacebar / "skip". |
auto_play_start | Autoplay sequence began. |
auto_play_stop | Autoplay sequence ended (user or rule). |
The round.state contract
round.state is opaque to the runtime SDK and owned by the math package for the game. Concretely it is whatever JSON the math package emits, typically an ordered list of book events the client renders one by one.
The runtime makes no schema level assertions about state and will not reject a round on its shape. This is intentional: it means a new game type can ship without touching the RGS.
Slotdaddy recommends but does not enforce the typed BookEvent discriminated union for state. Studios that adopt it get the Slotdaddy web SDK's stock event handlers for free; studios that diverge own their handlers.
Mode taxonomy
mode is a free-form string on the wire, but Slotdaddy reserves a documented taxonomy. Servers accept any string; the taxonomy lets the SDK, game UI, and reporting tools speak a common language.
| Mode | Use |
|---|---|
BASE | Standard paid spin. |
BONUS | Resumed continuation of a triggered bonus (used when the bonus is bet-resumable). |
BUY_<feature> | Bonus buy purchase, e.g. BUY_FREESPINS, BUY_SUPERBONUS. Disabled when jurisdiction.disabledBuyFeature is true. |
ANTE | Ante bet style; increases trigger frequency. |
FREE_SPINS_RESUME | Special resume of a free spins sequence across sessions. |
STUDIO_<name> | Studio defined mode. Free namespace; reporting groups them under "studio". |
Any other string is accepted and reported as "unclassified". Slotdaddy will never reject a mode it doesn't recognize; the taxonomy is a reporting convention, not a wire constraint.
Idempotency
Network timeouts on /wallet/play are the worst kind of failure: the client doesn't know whether the bet was placed, and naively retrying risks double debiting the wallet. Slotdaddy solves this with an opt-in idempotency header.
If a client sends Idempotency-Key: <opaque> on /wallet/play, the server stores the response keyed by (sessionID, Idempotency-Key) for 24 hours. A repeat request with the same key and same body returns the original response verbatim, same betID, same state, same balance debit (idempotent, wallet is not debited twice). Clients that don't send the header get the default behavior: the server processes each request as new.
Conflict rules (HTTP 409, error code IDEMPOTENCY_CONFLICT):
- Same key, different body → 409.
- Reused key after 24h → treated as new (no replay).
Recommendation: generate a UUIDv4 per spin attempt; reuse it across retries; rotate on success.
Error model
Every non-2xx response carries a stable envelope:
{
"error": {
"code": "BET_OUT_OF_RANGE",
"message": "Bet 999 is below minBet 200000.",
"retriable": false,
"details": { "minBet": 200000, "maxBet": 100000000 }
}
}The envelope is stable across endpoints and across schema versions. Clients can dispatch on error.code rather than parsing message strings.
| HTTP | code | When | retriable |
|---|---|---|---|
| 400 | BAD_REQUEST | Malformed body. | no |
| 400 | BET_OUT_OF_RANGE | amount outside [minBet, maxBet] or not a multiple of stepBet. | no |
| 400 | BET_LEVEL_INVALID | amount not in config.betLevels (when enforced). | no |
| 400 | MODE_NOT_AVAILABLE | mode disallowed by jurisdiction or game config (e.g. BUY_* when disabled). | no |
| 401 | SESSION_INVALID | sessionID unknown. | no |
| 401 | SESSION_EXPIRED | sessionID no longer valid. | no |
| 402 | WALLET_INSUFFICIENT_FUNDS | Operator wallet declined the debit. | no |
| 409 | ROUND_ALREADY_ACTIVE | A round is open; call /wallet/end-round first. | no |
| 409 | ROUND_NOT_ACTIVE | /wallet/end-round called with no active round. | no |
| 409 | IDEMPOTENCY_CONFLICT | Reused Idempotency-Key with a different body. | no |
| 422 | MINIMUM_ROUND_DURATION_NOT_MET | /wallet/end-round called before jurisdiction.minimumRoundDuration ms. | yes (after delay) |
| 429 | RATE_LIMITED | Too many requests for this session. | yes |
| 500 | INTERNAL | Server fault; safe to retry with the same Idempotency-Key. | yes |
| 503 | UPSTREAM_UNAVAILABLE | Operator wallet adapter down. | yes |
retriable: true means the same request may succeed if replayed (use the same Idempotency-Key).
Schema versioning
round.state shape can evolve. To keep old game builds running while the math contract grows:
- Clients optionally send
X-Slotdaddy-Schema-Version: <int>. - Server echoes
X-Slotdaddy-Schema-Versionin the response. - If absent, server defaults to v1 (the schema described in this document).
- New versions add fields; never rename, never remove, never change types.
AuthenticateResponse.supportedSchemaVersions lets the client probe what the server can speak.
Realtime channel (additive, optional)
The core API is strict request/response, which keeps the common case simple. For fast round formats (crash, instant, multiplier curves) and for low latency balance updates on shared wallets, Slotdaddy offers an optional Server-Sent Events stream:
GET /wallet/stream?sessionID=<opaque>
Accept: text/event-stream
Message shapes:
event: balanceUpdate
data: {"balance":{"amount":4000000,"currency":"USD"}}
event: roundEvent
data: {"betID":91823471,"event":{"type":"multiplier","value":12.4}}
event: roundComplete
data: {"betID":91823471,"payout":2500000}
Clients that don't open this URL see no behavior change; everything they need still arrives via the POST endpoints. The stream is purely a latency optimization for games that benefit.
Sequence diagrams
Standard spin
Resume after disconnect
Idempotent retry after timeout
Currencies and languages
The v1 spec uses closed enums for currency and language so SDK builds get strict type checking. Both are slated to become open strings in v2 once the jurisdiction matrix is finalized. The wire is already a string today; this is a typing concern only.
Supported in v1: Currency includes USD, CAD, EUR, JPY, RUB, CNY, BRL, MXN, INR, IDR, KRW, PLN, VND, TRY, CLP, ARS, PEN, NGN, SAR, ILS, AED, TWD, NOK, DKK, KWD, JOD, CRC, TND, SGD, MYR, OMR, QAR, BHD, PHP, XGC, XSC. Language includes ar, de, en, es, fi, fr, hi, id, ja, ko, pl, pt, ru, tr, vi, zh.
Reserved for future specs (not in scope here)
These prefixes are explicitly carved out so they cannot collide with player runtime endpoints:
/studio/v1/*: studio control plane (math upload, game/version registry, environments, key rotation, replay search)./admin/*: internal ops surface (auditor replay, force outcome tools, support tools).
Operator integration (wallet adapters, settlement webhooks, jurisdiction routing) is handled per operator on a bespoke basis and intentionally has no public surface.
Versioning policy for this document
- v1 is the current version. Wire shapes here are frozen.
- Future minor versions (v1.x) may add optional fields and new endpoints; they will not remove or rename anything.
- Major versions (v2+) may break; they will ship behind
X-Slotdaddy-Schema-Version: 2and v1 will be supported in parallel for at least 12 months.