Integrate
API reference
Updated 13 October 2026
Download openapi.b2b.yaml OpenAPI 3.1 · version 2.1.0
Companion of the RGS integration guide. Two halves:
- RGS side (
serversbelow):POST /b2b/launch, the signed reads under/b2b/…,GET /games,GET /healthandGET /ready,GET /fairand the public round verificationGET /fair/{room}/{nonce}(JSON) /…/verifica(HTML). - Operator side: the four wallet callbacks (
/bet,/win,/rollback,/balance) that the operator hosts under itswalletBaseUrl, plus the optional events webhook (eventsWebhookUrl, tagevents); they carry a path-levelserversentry so tools keep them apart from ours.
Signature v2, one rule for every call in both directions. Each request carries
x-timestamp (epoch milliseconds) and x-signature, the lowercase hex HMAC-SHA256, keyed
with the operator's secret, of
NERVE-V2\nrequest\n<METHOD>\n<path>\n<timestamp>\n<body>
and every wallet response the operator sends is signed over
NERVE-V2\nresponse\n<METHOD>\n<path>\n<timestamp>\n<status>\n<sha256 hex of the request body>\n<body>
On calls to the operator the path is the logical one (/bet, /win, /rollback, /balance,
/events), whatever prefix the configured URL carries; on calls to the RGS it is the request
target exactly as sent, query string included. Every wallet response must be signed by the
operator with direction response, the x-timestamp of the request it answers, the HTTP
status it carries and the SHA-256 of the request body it answers (2.1) — an unsigned or
mis-signed response, a rewritten status included, counts as no response. Requests are valid
for 60 seconds.
Redirects are never followed. The integration guide (§6.1) has sample code and test vectors.
Timeouts and retries on every wallet callback. The RGS waits walletTimeoutMs (default
4000 ms) per attempt, from the request to the last byte of the body. On a network error or a
timeout it retries walletRetries times (default 1, so two attempts in total) with the very
same body and betId; it never retries because of an HTTP response, whatever the status. So
the same /bet, /win, /rollback or /balance body can legitimately arrive twice: the
idempotency on betId is what makes that harmless. Both numbers are per operator, in the
registry (see OperatorRegistryEntry under components/schemas, the reference for every
registry field: allowedOrigins, enabled, ipAllowlist, limits, eventsWebhookUrl…).
Retries of calls to the RGS. A signature names one request: once verified, the RGS
remembers it for two minutes. The same signed request sent again from the same IP address is
a retry, not a new request: POST /b2b/launch and the promo opt-out answer it with the
response they gave the first time — same status, headers and body, the same game token —
plus idempotent-replayed: true, whatever that status was; a read is simply answered again.
From a different IP address it is 401 REPLAY. A retry that arrives while the first request
is still being processed waits for it, up to 15 seconds, then answers
409 REPLAY_IN_PROGRESS. A new attempt is a new signature, with a fresh x-timestamp.
Authentication
| Scheme | Header | What is signed |
|---|---|---|
launchSignature | x-signature | signature v2 (see the introduction) with direction request, method POST, path /b2b/launch and the exact body; sent with x-timestamp, valid for 60 seconds |
readSignature | x-signature | signature v2 (see the introduction) with direction request, the method, the path with its query exactly as sent, and the body (empty on GET); sent with x-timestamp, valid for 60 seconds. Same secret as the launch and the wallet callbacks; the path is inside the signature, so a signature captured on one endpoint does not open another. |
callbackSignature | x-signature | signature v2 (see the introduction) with direction request, method POST, the logical path (/bet, /win, /rollback, /balance, /events) and the exact body; sent with x-timestamp. The operator verifies it on the raw bytes, with a constant-time comparison |
Launch
opening the game for a player (RGS side)
POST /b2b/launch
Authorise a player and get the iframe URL
Called by the operator's backend. Signed with signature v2 (see the introduction):
direction request, method POST, path /b2b/launch, and the body exactly as sent,
byte for byte — serialise once, sign that string, send that string. x-timestamp must be
within 60 seconds of the RGS clock.
Signed: launchSignature (see Authentication)
Parameters
| Name | In | Type | Required | Description |
|---|---|---|---|---|
x-timestamp | header | string · pattern ^[0-9]{1,16}$ | yes | epoch milliseconds, digits only; signed, and valid for 60 seconds |
Request body
| Field | Type | Required | Description |
|---|---|---|---|
operator | string | yes | operator id |
player | PlayerId | yes | |
room | string · pattern ^[a-zA-Z0-9_-]{1,64}$ | yes | one of the operator's room ids (real-money or demo) |
displayName | string · max 64 chars | the public name other players see (cleaned to 24 chars); a generated alias otherwise | |
lang | string · pattern ^[a-z]{2}(-[A-Z]{2})?$ | opens the client in this language (en, es; regional tags map to the base) | |
lobbyUrl | string · uri · max 512 chars | https URL of the lobby — “Back to the lobby”, and the only origin that receives balance bridge events | |
sessionId | string · pattern ^[\x21-\x7e]{1,64}$ | your own session id for this player (optional): a different value starts a new responsible-gaming session (time, reality check, session cap, net); without it a 30-minute absence does. Not an identity: the wallet never sees it |
Responses
| Status | Meaning | Body |
|---|---|---|
200 | launch accepted | LaunchResponse application/json |
400 | a field is missing or malformed (field names it: one of player, room, x-timestamp, displayName, lang, lobbyUrl, sessionId). A missing or non-string operator is not a 400: it is 404 OPERATOR_UNKNOWN | LaunchError application/json |
401 | STALE (x-timestamp outside the 60 s window), REPLAY (a signature already seen within the last two minutes, sent again from a different IP address; from the same address it is a retry, and gets the first answer) or BAD_SIGNATURE | LaunchError application/json |
403 | OPERATOR_SUSPENDED (kill-switch, enabled: false in the registry), IP_NOT_ALLOWED (outside the operator's allowlist) — both checked only after a valid signature — or ROOM_NOT_OWNED: the room does not exist, is not this operator's, or is neither real-money nor demo | LaunchError application/json |
404 | OPERATOR_UNKNOWN: the operator id is not in the registry — or is missing from the body, or is not a string. Answered before the signature is checked: operator ids are not secret | LaunchError application/json |
409 | REPLAY_IN_PROGRESS: a retry of a launch that is still being processed, and did not finish within 15 seconds. Ask again in a moment: once the first finishes, a retry gets its answer | LaunchError application/json |
429 | RATE_LIMITED: this IP has already had 30 launches refused with 400, 401, 403 or 404 in the last minute. Successful launches never count, and neither do the 503 answers below | LaunchError application/json |
503 | ROOM_INACTIVE (room paused), MAINTENANCE (a demo room during a maintenance window: no new launches, players inside finish; a real-money room answers RGS_NOT_READY with MAINTENANCE in issues), RGS_NOT_READY (+ issues, real-money room while the readiness checks of /ready fail, or any launch while the database is unreachable: DB_NOT_READY), ROOM_RECOVERY_REQUIRED (room held by a previous process after a crash; in a room with tables, no table can take a player and one is held), ROOM_FULL (a room with tables: every open table is at its cap and no new one can open yet; never to a player who already has a seat in the room), TOKEN_DISABLED, DB_REQUIRED | LaunchError application/json |
POST /b2b/players/{player}/sessions/revoke
End a player's sessions now
Ends every game session of the player at once (since 13 October 2026): the player's open
sockets close with 4401 — the game shows that the session has ended and offers the way
back to your lobby — and every game token issued to the player up to this instant, the
launch token and its renewals alike, is refused from now on, at the handshake and on
/me/*. A launch made after the call issues a fresh token that works. Use it when you
close or block the account, when a self-exclusion lands, or when a gameUrl has been
shared where it should not have been: before this endpoint a leaked link lived until its
hour ran out.
Signed like the read endpoints (signature v2, method POST, the path with its query
exactly as sent), sent with no body. The revocation lives in the memory of the game
process (one replica per service): a restart forgets it, and a token issued before the
restart would be accepted again within its own one-hour lifetime. That is the residual
risk, stated on purpose.
Every accepted call writes an audit row on our side (action session.revoke, orgId =
the calling operator, no admin actor). A retry of the same signed call, from the same IP
address within two minutes, gets the first response with idempotent-replayed: true and
writes nothing.
Signed: readSignature (see Authentication)
Parameters
| Name | In | Type | Required | Description |
|---|---|---|---|---|
player | path | PlayerId | yes | |
operator | query | string | yes | |
x-timestamp | header | string · pattern ^[0-9]{1,16}$ | yes | epoch milliseconds, digits only; signed, and valid for 60 seconds |
Responses
| Status | Meaning | Body |
|---|---|---|
200 | the sessions are ended | object application/json |
400 | BAD_REQUEST: player longer than 128 characters (message: "player too long"), not printable ASCII without spaces (message: "player: printable ASCII without spaces"), or missing/non-numeric x-timestamp | |
401 | STALE, REPLAY (a signature already seen within two minutes, from another IP address; from the same one the call is a retry) or BAD_SIGNATURE | |
403 | OPERATOR_SUSPENDED or IP_NOT_ALLOWED | |
404 | OPERATOR_UNKNOWN | |
409 | REPLAY_IN_PROGRESS: a retry of a call still being processed after 15 seconds; ask again in a moment | |
429 | RATE_LIMITED (failed calls only; the same per-IP budget as the reads) |
Catalog
machine-readable game list and icons (RGS side)
GET /games
The game catalog, built from the engine constants
Responses
| Status | Meaning | Body |
|---|---|---|
200 | all games with math, limits, features and absolute icon URLs | Catalog application/json |
GET /games/{id}
One catalog entry
Parameters
| Name | In | Type | Required | Description |
|---|---|---|---|---|
id | path | string · one of god-candle, diamond-hands, big-fish, hypnofrog | yes |
Responses
| Status | Meaning | Body |
|---|---|---|
200 | the entry | CatalogGame application/json |
404 | GAME_UNKNOWN |
GET /games/{id}/{file}
Game icon (SVG, or PNG at 512 / 256 / 128 px)
Parameters
| Name | In | Type | Required | Description |
|---|---|---|---|---|
id | path | string · one of god-candle, diamond-hands, big-fish, hypnofrog | yes | |
file | path | string · one of icon.svg, icon-512.png, icon-256.png, icon-128.png | yes |
Responses
| Status | Meaning | Body |
|---|---|---|
200 | the icon, cacheable for a day | string image/svg+xml string · binary image/png |
404 | unknown game or file |
Reads
signed read-only endpoints for the operator's own rounds, bets and settlement (RGS side)
GET /b2b/settlements
Durable wallet transactions, including interrupted rounds
Signed with signature v2 (method GET, the path with its query exactly as sent, empty body).
Both ISO bounds are required. Selects stakes by the moment the debit was requested, in
[from, to), at most 90 days. Contains OPEN and ROLLBACK_PENDING as well as terminal stakes.
Decimal amounts are strings. A null payout means no recorded win obligation, not zero.
The delivery status is separate from the stake status. No cross-currency total.
truncated and x-report-truncated require splitting the time window when true.
CSV protects formula-like identifiers with a leading apostrophe; JSON preserves exact IDs.
Signed: readSignature (see Authentication)
Parameters
| Name | In | Type | Required | Description |
|---|---|---|---|---|
operator | query | string | yes | |
x-timestamp | header | string · pattern ^[0-9]{1,16}$ | yes | epoch milliseconds, digits only; signed, and valid for 60 seconds |
from | query | string · date-time | yes | included |
to | query | string · date-time | yes | excluded |
format | query | string · one of json, csv |
Responses
| Status | Meaning | Body |
|---|---|---|
200 | Scoped ledger rows in JSON or spreadsheet-safe CSV, capped at 20000 rows. The CSV (content-disposition: attachment; filename="wallet-settlements.csv", UTF-8 with BOM) has the same columns as the JSON
rows, in this order: betId, playerId, roomId, roundId, currency, stake, stakeStatus, payout, deliveryStatus, multiplier, createdAt, updatedAt, deliveryUpdatedAt.
| SettlementsResponse application/json string text/csv |
400 | BAD_RANGE (from/to missing, not ISO dates, or from ≥ to), RANGE_TOO_WIDE (more than 90 days), or BAD_REQUEST (missing or non-numeric x-timestamp) | |
401 | STALE | |
403 | OPERATOR_SUSPENDED or IP_NOT_ALLOWED | |
404 | OPERATOR_UNKNOWN | |
429 | RATE_LIMITED | |
503 | DB_REQUIRED (no database on this service) or DB_UNAVAILABLE (database configured but unreachable right now: retry) |
GET /b2b/round/{id}
One round, as its operator sees it
id is the roundId we send you on every /bet and /win, i.e. <table>:<nonce>: the
table is the room id itself unless the room has tables, then <room>--t<n> from table 2.
Signed with signature v2, with the same secret as the launch: method GET, the path with
its query exactly as sent (percent-encoded as sent: rm-acme%3A42 and rm-acme:42 are two
different signed strings), empty body. The path is inside the signature, so a signature
captured on one endpoint does not open another.
Returns the outcome, the generator inputs (so the round can be re-derived — see also the
public /fair/{roomId}/{nonce}), the round totals, and the caller's own bets. A round
appears only once it has settled: while it is running the answer is 404 NOT_FOUND. bets[].betId is the wire betId — the one you saw on /bet and
/win, and the one /b2b/bet/{id} accepts — never an internal identifier.
Signed: readSignature (see Authentication)
Parameters
| Name | In | Type | Required | Description |
|---|---|---|---|---|
id | path | string | yes | |
operator | query | string | yes | |
x-timestamp | header | string · pattern ^[0-9]{1,16}$ | yes | epoch milliseconds, digits only; signed, and valid for 60 seconds |
Responses
| Status | Meaning | Body |
|---|---|---|
200 | the round | OperatorRound application/json |
400 | BAD_ROUND_ID (not <roomId>:<nonce>) or BAD_REQUEST (missing/non-numeric x-timestamp) | |
401 | STALE, REPLAY (a signature already seen within two minutes, from another IP address; from the same one the call is a retry) or BAD_SIGNATURE | |
403 | OPERATOR_SUSPENDED or IP_NOT_ALLOWED (after a valid signature, as on the launch) | |
404 | OPERATOR_UNKNOWN, or NOT_FOUND. NOT_FOUND is returned when the round does not
exist, when it has not settled yet, and when it belongs to another operator, on
purpose: a different answer would let a caller with a valid secret probe another
operator's rounds.
| |
429 | RATE_LIMITED (failed reads only) | |
503 | DB_REQUIRED (no database on this service) or DB_UNAVAILABLE (database configured but unreachable right now: retry) |
GET /b2b/bet/{id}
One bet, and why the player has not been paid
id is the betId you saw on /bet. Signed with signature v2 (method GET, the path with
its query exactly as sent, empty body).
⚠️ betIds contain # (<room>.<instance>:<nonce>:<slot>#<n>): percent-encode the id
in the path (%23), or your HTTP client cuts the URL at the #, the ?operator= query
with it, and the answer is 404 OPERATOR_UNKNOWN. Sign the encoded path, exactly as sent.
Returns the stake's state towards your wallet (OPEN debit requested and position not
closed yet, an ambiguous /bet included · REJECTED refused by you with a signed 4xx,
never debited · SETTLED outcome final on our side, won or lost — the delivery of the
credit is win.status · ROLLBACK_PENDING refund claimed by our sweeper · ROLLED_BACK
refunded), the state of the win obligation, and the timeline of what the server received
for that bet, in arrival order.
Signed: readSignature (see Authentication)
Parameters
| Name | In | Type | Required | Description |
|---|---|---|---|---|
id | path | string | yes | |
operator | query | string | yes | |
x-timestamp | header | string · pattern ^[0-9]{1,16}$ | yes | epoch milliseconds, digits only; signed, and valid for 60 seconds |
Responses
| Status | Meaning | Body |
|---|---|---|
200 | the bet | OperatorBet application/json |
400 | BAD_BET_ID (longer than 256 characters) or BAD_REQUEST (missing/non-numeric x-timestamp) | |
401 | STALE, REPLAY (a signature already seen within two minutes, from another IP address; from the same one the call is a retry) or BAD_SIGNATURE | |
403 | OPERATOR_SUSPENDED or IP_NOT_ALLOWED | |
404 | OPERATOR_UNKNOWN, or NOT_FOUND (unknown or not yours — see /b2b/round/{id}) | |
429 | RATE_LIMITED | |
503 | DB_REQUIRED (no database on this service) or DB_UNAVAILABLE (database configured but unreachable right now: retry) |
GET /b2b/report
Settlement per currency and day
Signed with signature v2 (method GET, the path with its query exactly as sent, empty
body): the window is inside the signature, so a signature for one day does not open the
whole history. Both bounds are required, exactly as on /b2b/settlements (400 BAD_RANGE
"from/to required" without them); from is included and to excluded, so September is
from=2026-09-01&to=2026-10-01. There is no other window parameter.
Rows are (currency, day of settlement) — keys currency, day — with the number of
bets (bet), distinct players (player), wagered, payout and ggr, plus per-currency
subtotals (subtotals, which carry no player count: distinct players do not add up
across days). See ReportResponse. There is never a grand total across currencies:
adding euros and dollars produces a number that exists nowhere.
Signed: readSignature (see Authentication)
Parameters
| Name | In | Type | Required | Description |
|---|---|---|---|---|
from | query | string · date | yes | included |
to | query | string · date | yes | excluded |
format | query | string · one of csv | omit for JSON | |
operator | query | string | yes | |
x-timestamp | header | string · pattern ^[0-9]{1,16}$ | yes | epoch milliseconds, digits only; signed, and valid for 60 seconds |
Responses
| Status | Meaning | Body |
|---|---|---|
200 | rows, subtotals, and whether the result was truncated (truncated: the row cap, 20000
by default, was reached — narrow the window). With format=csv: content-disposition: attachment; filename="settlement-<operator>-<from>_<to>.csv", UTF-8 with BOM, columns
currency, day, bet, player, wagered, payout, ggr; the CSV carries no subtotals.
| ReportResponse application/json string text/csv |
400 | BAD_RANGE (from/to missing, not ISO dates, or from ≥ to), RANGE_TOO_WIDE (max 90 days) or BAD_REQUEST (missing/non-numeric x-timestamp) | |
401 | STALE, REPLAY (a signature already seen within two minutes, from another IP address; from the same one the call is a retry) or BAD_SIGNATURE | |
403 | OPERATOR_SUSPENDED or IP_NOT_ALLOWED | |
404 | OPERATOR_UNKNOWN | |
429 | RATE_LIMITED | |
503 | DB_REQUIRED (no database on this service) or DB_UNAVAILABLE (database configured but unreachable right now: retry) |
Status
health, readiness and provably-fair information (RGS side)
GET /b2b/room/{id}/status
Operational readiness of the caller's room without waking it
Signed with signature v2 (method GET, the path with its query exactly as sent, empty body).
Timestamp window is 60 seconds. Does not wake the room.
realMoney.ready is operational eligibility, not regulatory approval.
tables lists the room's tables (a room without tables has one, itself); tables that
finished their hash chain are counted in retiredTables and listed on /fair.
Signed: readSignature (see Authentication)
Parameters
| Name | In | Type | Required | Description |
|---|---|---|---|---|
id | path | string | yes | |
operator | query | string | yes | |
x-timestamp | header | string · pattern ^[0-9]{1,16}$ | yes | epoch milliseconds, digits only; signed, and valid for 60 seconds |
Responses
| Status | Meaning | Body |
|---|---|---|
200 | Room status | object application/json |
400 | BAD_REQUEST (missing or non-numeric x-timestamp) | |
401 | STALE | |
403 | OPERATOR_SUSPENDED or IP_NOT_ALLOWED | |
404 | OPERATOR_UNKNOWN, or NOT_FOUND for both a missing room and another operator's room | |
429 | RATE_LIMITED | |
503 | DB_REQUIRED (no database on this service) or DB_UNAVAILABLE (database configured but unreachable right now: retry) |
GET /health
Liveness and identity of the installation
Does not touch the database unless ?db=1 is passed.
Parameters
| Name | In | Type | Required | Description |
|---|---|---|---|---|
db | query | string · one of 1 | also measure database latency (dbMs) |
Responses
| Status | Meaning | Body |
|---|---|---|
200 | alive | Health application/json |
GET /ready
Readiness — can the installation take real-money traffic now
Unlike /health (liveness, always 200 while the process is alive) this answers 503
when the service should not receive traffic: database configured but never up at boot
(dbBootOk: false) or fallen after it (db: ko), maintenance window, draining after a
stop signal, or — on a casino installation — any failed real-money check (schema not
migrated, generator fingerprint not verified). Zero database queries, like /health.
Before sending real money, read realMoney.ready, not only the status: on an installation
without a database (demo rooms only) the service is ready while realMoney.ready is false.
Responses
GET /fair
Provably-fair commitment of a room
Parameters
| Name | In | Type | Required | Description |
|---|---|---|---|---|
room | query | string · default demo | a room id, or a table id (<room>--t<n>): a table answers with its own fields and no tables list | |
tablesAfter | query | integer · min 0 · default 0 | the cursor of the tables list: the last table number already read (tablesNext of the previous page) |
Responses
| Status | Meaning | Body |
|---|---|---|
200 | the chain terminus, client seed, generator version, current nonce and persistent of the room's first table — persistent is true when the room persists its rounds, so /fair/{roomId}/{nonce} and its /verifica page answer; false for in-memory demo rooms, which have no tables. Then tables, the room's tables in number order, 50 per page, and tablesNext. ⚠️ An unknown room also answers 200, with the body {error: ROOM_NOT_FOUND}: check for error before reading the fields | FairInfo application/json |
400 | BAD_REQUEST: tablesAfter is not a table number | |
429 | RATE_LIMITED: the public reads (/fair, /fair/{room}/{nonce}, its /verifica page, /meta/…) share one per-IP budget, 120 per minute by default |
GET /fair/{room}/{nonce}
One settled round, with every generator input, to re-derive it independently
Public, like /fair: the seed of a settled round has already been revealed to every
player. Does not wake the room. A Round row exists only after settlement, so a round
still in progress is 404 ROUND_NOT_FOUND; so is any round of an in-memory demo room
(persistent: false in /fair). Verification in three steps:
SHA256(seed) === seedHash, SHA256^nonce(seed) === chainTerminus, and
generateRound(seed, clientSeed, nonce, math) reproduces outcome.
math is null for rounds written before the configuration snapshot existed.
Parameters
| Name | In | Type | Required | Description |
|---|---|---|---|---|
room | path | string | yes | |
nonce | path | integer · min 0 | yes |
Responses
| Status | Meaning | Body |
|---|---|---|
200 | the round's inputs and outcome | FairRound application/json |
400 | BAD_REQUEST: bad room id or nonce | |
404 | ROUND_NOT_FOUND | |
429 | RATE_LIMITED: same per-IP budget as /fair (120 per minute by default) | |
503 | DB_DISABLED (this installation keeps no rounds) or DB_UNAVAILABLE (the database is unreachable right now: retry) |
GET /fair/{room}/{nonce}/verifica
The same verification as an HTML page, for people
Runs the three checks server-side and shows the verdict; cacheable for an hour (a settled round never changes), and for 15 seconds while the round closed less than two minutes ago, since its command ledger may still be landing. Errors are HTML pages with the same status codes as the JSON endpoint — except the 429 of the per-IP limiter, which is the same JSON body as on /fair.
Parameters
| Name | In | Type | Required | Description |
|---|---|---|---|---|
room | path | string | yes | |
nonce | path | integer · min 0 | yes |
Responses
| Status | Meaning | Body |
|---|---|---|
200 | the verification page | string text/html |
400 | bad room id or nonce (HTML) | |
404 | round not found | |
429 | {error: RATE_LIMITED} as JSON, not HTML: same per-IP budget as /fair | |
503 | no database on this installation |
Wallet
seamless-wallet callbacks implemented by the OPERATOR under its walletBaseUrl
POST /bet you implement
Debit a stake (operator-implemented)
Idempotent on betId, also under concurrency. Signed both ways (signature v2, logical path
/bet). A 200 with a finite, non-negative balance means debited; a signed 4xx is a
definitive refusal (nothing debited, the player sees reason when it is one of the table,
ERROR otherwise); everything else is ambiguous and triggers an immediate /rollback from
the RGS: 5xx, any other status below 400 including redirects (never followed), a
timeout, an unsigned or mis-signed response, and a signed 200 whose balance is missing, not a
number, or negative (the RGS does not know what you did with the stake, and will not show
the player a number that does not exist). A /bet arriving after a /rollback of the same
betId must answer 409 {reason: ROLLED_BACK}. Because of the network retry (see the
introduction) the same /bet body can arrive twice: answer the second exactly like the first.
Signed: callbackSignature (see Authentication)
Parameters
| Name | In | Type | Required | Description |
|---|---|---|---|---|
x-operator | header | string | yes | the operator id the RGS is calling on behalf of |
Request body
| Field | Type | Required | Description |
|---|---|---|---|
playerId | PlayerId | yes | |
betId | BetId | yes | |
amount | Money | yes | |
currency | Currency | yes | |
roundId | RoundId | yes | |
multiplier | number · min 0 | Only on /win: the multiplier the position closed at, with four decimals and the same
value on every retry (relative to entry: 1.0 is break-even before the house edge; God
Candle can close below 1.0), or 0 on a lost bet (amount: 0). Absent on /bet.
Optional: ignore it if you do not need it. It is what a tournament tool needs to rank
by "max multiplier" instead of "total staked". The payout is not always
stake × multiplier: a position opened mid-round in God Candle, Diamond Hands or
Hypnofrog is paid stake × multiplier × RTP (guide §6.2).
|
Responses
| Status | Meaning | Body |
|---|---|---|
200 | debited | WalletOk application/json |
402 | refused — INSUFFICIENT_FUNDS (any signed 4xx with a reason from the table is read the same way) | WalletReject application/json |
409 | ROLLED_BACK: this betId was rolled back before the debit arrived; never debited | WalletReject application/json |
POST /win you implement
Credit a payout, or close a lost bet with amount 0 (operator-implemented)
Idempotent on betId, also under concurrency. amount: 0 closes a LOST bet and must be
accepted like any win; after it, a /rollback of the same betId must answer
409 ALREADY_SETTLED. Anything but a signed 200 is retried by the RGS with backoff for
about 26 hours. At the end of a round every open position is closed at once: expect up to
32 /win calls in flight from one RGS server. Never credit a /win for a betId you did
not debit, or rolled back: by design we never send one, and if one arrives answer it with a
signed 409 and alert your team — it is retried, then settled by hand. amount is
stake × multiplier × f, floored and capped at the room's maxWin: f is the room's RTP
for a position opened mid-round in God Candle, Diamond Hands or Hypnofrog, 1 otherwise.
Signed: callbackSignature (see Authentication)
Parameters
| Name | In | Type | Required | Description |
|---|---|---|---|---|
x-operator | header | string | yes | the operator id the RGS is calling on behalf of |
Request body
| Field | Type | Required | Description |
|---|---|---|---|
playerId | PlayerId | yes | |
betId | BetId | yes | |
amount | Money | yes | |
currency | Currency | yes | |
roundId | RoundId | yes | |
multiplier | number · min 0 | Only on /win: the multiplier the position closed at, with four decimals and the same
value on every retry (relative to entry: 1.0 is break-even before the house edge; God
Candle can close below 1.0), or 0 on a lost bet (amount: 0). Absent on /bet.
Optional: ignore it if you do not need it. It is what a tournament tool needs to rank
by "max multiplier" instead of "total staked". The payout is not always
stake × multiplier: a position opened mid-round in God Candle, Diamond Hands or
Hypnofrog is paid stake × multiplier × RTP (guide §6.2).
|
Responses
| Status | Meaning | Body |
|---|---|---|
200 | credited (or closed, for amount 0) | WalletOk application/json |
POST /rollback you implement
Refund a stake (operator-implemented)
Idempotent on betId. Refund the stake of an open bet. A bet already closed with /win
(including amount 0) → 409 ALREADY_SETTLED, no refund. A betId never seen → 200 no-op,
and REMEMBER it: a later /bet with that id must be refused with 409 ROLLED_BACK. The
request carries no player, so answer { "balance": 0 }: the balance of a /rollback
answer is never used for accounting, and a 0 is never shown to the player.
Signed: callbackSignature (see Authentication)
Parameters
| Name | In | Type | Required | Description |
|---|---|---|---|---|
x-operator | header | string | yes | the operator id the RGS is calling on behalf of |
Request body
| Field | Type | Required | Description |
|---|---|---|---|
betId | BetId | yes |
Responses
| Status | Meaning | Body |
|---|---|---|
200 | refunded, or nothing to refund (unknown or already rolled back) | WalletOk application/json |
409 | ALREADY_SETTLED: the bet was closed with /win; not refunded | WalletReject application/json |
POST /balance you implement
Current balance of a player (operator-implemented)
Signed: callbackSignature (see Authentication)
Parameters
| Name | In | Type | Required | Description |
|---|---|---|---|---|
x-operator | header | string | yes | the operator id the RGS is calling on behalf of |
Request body
Responses
| Status | Meaning | Body |
|---|---|---|
200 | the balance (anything else is shown to the player as 0) | WalletOk application/json |
Promo
promotions and player exclusions (operator-signed writes; operator free bets follow on this surface)
POST /b2b/promo/players/{player}/opt-out
Exclude a player from every promotion (or re-admit them)
Sets the player's promo opt-out flag. A player with the flag set is offered no promotion and cannot claim any: today that covers the welcome free bets of the operator's demo rooms, and every promotion that reaches real money will obey it. Use it to mirror your own harm indicators (UKGC SR Code 3.4.3, requirement 10). The player row is created if the player never launched a game: the exclusion usually precedes the first session, and that is the case that must work.
Signed like the read endpoints (signature v2, method POST, the path with its query
exactly as sent), sent with no body. optOut is in the signed query: a signature for
"exclude" is not valid for "re-admit".
Every accepted call writes an audit row on our side (action promo.opt_out, the flag
before and after, orgId = the calling operator, no admin actor): the exclusion is a
compliance decision, and who took it stays written. A retry of the same signed call, from
the same IP address within two minutes, gets the first response with
idempotent-replayed: true and writes nothing.
Signed: readSignature (see Authentication)
Parameters
| Name | In | Type | Required | Description |
|---|---|---|---|---|
player | path | PlayerId | yes | |
operator | query | string | yes | |
x-timestamp | header | string · pattern ^[0-9]{1,16}$ | yes | epoch milliseconds, digits only; signed, and valid for 60 seconds |
optOut | query | string · one of 0, 1 | yes | 1 excludes, 0 re-admits |
Responses
| Status | Meaning | Body |
|---|---|---|
200 | current state | object application/json |
400 | BAD_REQUEST: optOut not 0/1 (message: "optOut must be 0 or 1"), player longer than 128 characters (message: "player too long") or not printable ASCII without spaces (message: "player: printable ASCII without spaces"), or missing/non-numeric x-timestamp. The signature is checked first: a bad signature on a bad optOut is a 401 | |
401 | STALE, REPLAY (a signature already seen within two minutes, from another IP address; from the same one the call is a retry) or BAD_SIGNATURE | |
403 | OPERATOR_SUSPENDED or IP_NOT_ALLOWED | |
404 | OPERATOR_UNKNOWN | |
409 | REPLAY_IN_PROGRESS: a retry of a call still being processed after 15 seconds; ask again in a moment | |
429 | RATE_LIMITED (failed calls only; the same per-IP budget as the reads) | |
503 | DB_REQUIRED (no database on this service) or DB_UNAVAILABLE (database configured but unreachable right now: retry) |
Events
game events that are not transactions, POSTed by the RGS to the OPERATOR's eventsWebhookUrl (optional, no retry)
POST {eventsWebhookUrl} you implement
Receive a game event that is not a transaction (operator-implemented, optional)
Sent only when the registry entry has an eventsWebhookUrl (https, or http on loopback for
local tests); without it nothing is sent. Signed like the wallet callbacks — signature v2,
direction request, method POST, logical path /events whatever the configured URL is:
the same code that verifies /bet verifies this. x-nerve-event repeats the event name so
a router can dispatch without parsing the body; x-operator names the operator.
Timeout walletTimeoutMs (default 4000 ms), no retry and no queue: a timeout or any
non-2xx is logged and dropped. It is a signal to your CRM, not an obligation to reconcile,
and a signal delivered ten minutes late is worthless. The body carries no amounts, losses
or balance — only the event, the room, the player as you know them, a count and the
instant. The response body is ignored and need not be signed.
Events today: onboarding_at_risk — a player with no first bet on record has closed N bets
(default 5) in this session without a single cashout. Emitted at most once per session.
Signed: callbackSignature (see Authentication)
Parameters
| Name | In | Type | Required | Description |
|---|---|---|---|---|
x-operator | header | string | yes | the operator id the RGS is calling on behalf of |
x-timestamp | header | string · pattern ^[0-9]{1,16}$ | yes | epoch milliseconds, digits only; signed, and valid for 60 seconds |
x-nerve-event | header | string · one of onboarding_at_risk | yes | the event name, identical to event in the body |
Request body
| Field | Type | Required | Description |
|---|---|---|---|
event | string · one of onboarding_at_risk | yes | |
roomId | string | yes | the room you launched, never a table id |
player | string · max 128 chars | yes | the player id exactly as you sent it in the launch — never our internal id |
bets | integer | yes | closed bets without a cashout in this session |
at | string · date-time | yes | ISO 8601 |
Responses
| Status | Meaning | Body |
|---|---|---|
200 | acknowledged (any 2xx counts; the body is ignored) |
Schemas
PlayerId
the operator's pseudonymous player id, exactly as sent in the launch: printable ASCII without spaces, 1 to 128 characters (since 13 October 2026 a launch or opt-out with another character is a 400 on player)
string · pattern ^[\x21-\x7e]{1,128}$ · max 128 chars
Currency
ISO-style code configured for the operator (one per operator)
string
BetId
globally unique, table id first — <table>.<instance>:<nonce>:<slot>#<n>, where the table is the room id unless the room has tables and slot is a per-round public label, never the player id; opaque — idempotency key of bet, win and rollback
string · max 256 chars
RoundId
<table>:<nonce>, identical on every call of the round; the table is the room id unless the room has tables, then <room>--t<n> from table 2
string
Money
amount in major units of the operator's currency (12.5 is €12.50), with at most that currency's decimals: two for EUR or USD, none for JPY. Stakes are rounded and payouts floored to that scale
number
LaunchRequest
signed as the exact bytes sent; the timestamp travels in the x-timestamp header, not in the body
| Field | Type | Required | Description |
|---|---|---|---|
operator | string | yes | operator id |
player | PlayerId | yes | |
room | string · pattern ^[a-zA-Z0-9_-]{1,64}$ | yes | one of the operator's room ids (real-money or demo) |
displayName | string · max 64 chars | the public name other players see (cleaned to 24 chars); a generated alias otherwise | |
lang | string · pattern ^[a-z]{2}(-[A-Z]{2})?$ | opens the client in this language (en, es; regional tags map to the base) | |
lobbyUrl | string · uri · max 512 chars | https URL of the lobby — “Back to the lobby”, and the only origin that receives balance bridge events | |
sessionId | string · pattern ^[\x21-\x7e]{1,64}$ | your own session id for this player (optional): a different value starts a new responsible-gaming session (time, reality check, session cap, net); without it a 30-minute absence does. Not an identity: the wallet never sees it |
LaunchResponse
| Field | Type | Required | Description |
|---|---|---|---|
token | string | yes | the game token (also in the gameUrl fragment) |
gameUrl | string · uri | yes | the iframe URL: <base>/<gc|dh|bf|hf>/?room=…[&lang=…]#t=<token> — use it as is; lobbyUrl travels inside the signed token |
ttlSec | integer | yes | |
alias | string | yes | the public name that will be shown |
realMoney | boolean | yes | false when the room is the operator's demo room |
LaunchError
| Field | Type | Required | Description |
|---|---|---|---|
error | string · one of BAD_REQUEST, STALE, REPLAY, REPLAY_IN_PROGRESS, BAD_SIGNATURE, OPERATOR_SUSPENDED, IP_NOT_ALLOWED, ROOM_NOT_OWNED, OPERATOR_UNKNOWN, RATE_LIMITED, ROOM_INACTIVE, MAINTENANCE, TOKEN_DISABLED, DB_REQUIRED, RGS_NOT_READY, ROOM_RECOVERY_REQUIRED, ROOM_FULL | yes | |
field | string | the offending field, on BAD_REQUEST | |
issues | array of string | on RGS_NOT_READY: which readiness checks fail (MAINTENANCE, DRAINING, DB_NOT_READY, MIGRATIONS_NOT_VERIFIED, ENGINE_NOT_VERIFIED) |
Catalog
| Field | Type | Required | Description |
|---|---|---|---|
provider | string | yes | |
env | string · one of staging, prod | ||
rev | string · nullable | git revision of the running build | |
integration | object | ||
integration.launch | string | ||
integration.wallet | string | ||
integration.health | string | ||
games | array of <a href="#schema-cataloggame">CatalogGame</a> | yes |
CatalogGame
| Field | Type | Required | Description |
|---|---|---|---|
id | string · one of god-candle, diamond-hands, big-fish, hypnofrog | yes | |
gameType | string · one of GOD_CANDLE, DIAMOND_HANDS, BIG_FISH, HYPNOFROG | yes | |
name | string | yes | |
tagline | string | ||
category | string · one of crash | yes | |
status | string · one of live, preview | yes | |
path | string · one of /gc/, /dh/, /bf/, /hf/ | yes | |
demoRoom | string | yes | |
demoUrl | string · uri | yes | |
fairUrl | string · uri | yes | |
math | CatalogMath | yes | |
limits | object | yes | |
limits.minBet | number | ||
limits.maxBet | number | ||
limits.maxWin | number · nullable | ||
limits.positionsPerRound | integer | positions a player can hold open at the same time; more can be opened during the round as earlier ones close | |
limits.configurable | string · one of per-room | ||
currency | string · one of operator | yes | |
languages | array of string | yes | |
platforms | array of string | yes | |
features | array of string | yes | |
fairness | object | yes | |
fairness.model | string · one of sha256-hash-chain | ||
fairness.verify | string | ||
icons | object | yes | |
icons.svg | string · uri | ||
icons.png512 | string · uri | ||
icons.png256 | string · uri | ||
icons.png128 | string · uri |
CatalogMath
| Field | Type | Required | Description |
|---|---|---|---|
rtp | number | ||
houseEdge | number | ||
rtpConfigurable | boolean | ||
maxMultiplier | number | Highest multiplier a round can actually reach. Equals capMultiplier only where the barrier is exact (God Candle); Diamond Hands, Big Fish and Hypnofrog stop below it (pre-cap rule). | |
capMultiplier | number | The configured cap. | |
generatorVersion | string | ||
volatility | string · one of high | ||
medianRoundSec | number | ||
maxRoundSec | number | ||
p50MaxMultiplier | number | ||
p90MaxMultiplier | number | ||
reach2x | number · nullable | ||
reach10x | number · nullable | ||
simulatedRounds | integer | rounds simulated for the volatility figures; Hypnofrog's median round duration was measured over 300,000 |
Health
| Field | Type | Required | Description |
|---|---|---|---|
ok | boolean | always true while the process is alive — liveness | |
world | string · one of casino | ||
env | string · one of staging, prod | ||
activeRooms | integer | ||
db | string · one of absent, ready, down, ko | absent = no database configured; down = not reachable at boot; ko = the last real access failed after boot; ready = up at boot and no failure since. Zero queries: observed, not probed | |
dbLastOkSec | integer · nullable | seconds since the last successful database access | |
migrations | string · nullable | ok, behind:N (N migrations not applied — the only way to see a casino database left behind, since those services never run migrate deploy), ahead:N, or null when unknown | |
impronta | string · one of ok, diversa · nullable | outcome of the last self-check of the generators of this process: ok, diversa (a critical alarm is open), null = not checked yet | |
dbMs | integer · nullable | database round trip, only with ?db=1 (-1 on failure) | |
rev | string · nullable | git revision of the running build |
Ready
| Field | Type | Required | Description |
|---|---|---|---|
ready | boolean | ||
realMoney | object | ||
realMoney.ready | boolean | ||
realMoney.issues | array of string · one of <code>MAINTENANCE</code>, <code>DRAINING</code>, <code>DB_NOT_READY</code>, <code>MIGRATIONS_NOT_VERIFIED</code>, <code>ENGINE_NOT_VERIFIED</code> | ||
world | string · one of casino | ||
env | string · one of staging, prod | ||
rev | string · nullable | ||
db | string · one of absent, ok, ko, idle, unknown | absent (no database configured), or the observed state of the last real access: ok, ko, idle (pool closed after a quiet period — not a fault), unknown (nothing observed yet) | |
dbBootOk | boolean · nullable | null when no database is configured | |
maintenance | string · nullable | the maintenance reason | |
draining | boolean | ||
uptimeSec | integer | ||
activeRooms | integer |
FairInfo
| Field | Type | Required | Description |
|---|---|---|---|
chainTerminus | string | public end of the room's SHA-256 chain (the commitment); '' when the room row carries no terminus | |
clientSeed | string | ||
generatorVersion | string | ||
nonce | integer | current round counter | |
persistent | boolean | true = the room persists its rounds, so /fair/{room}/{nonce} and its /verifica page answer; false for in-memory demo rooms | |
tables | array of <a href="#schema-fairtable">FairTable</a> | every table of the room in number order, retired ones included, 50 per page; a room without tables lists one, itself. Tables are created before they are needed, with their chain end published, and used in creation order: createdAt shows that a table existed before the rounds it played | |
tablesNext | integer · nullable | the value of tablesAfter for the next page; null on the last |
FairTable
| Field | Type | Required | Description |
|---|---|---|---|
id | string | yes | the room id for table 1, <room>--t<n> from table 2 |
number | integer | yes | |
chainTerminus | string | yes | public end of this table's SHA-256 chain |
clientSeed | string | yes | |
createdAt | string · date-time | yes | |
retiredAt | string · date-time · nullable | yes | when the table finished its chain; null while it plays or can play |
TableStatus
| Field | Type | Required | Description |
|---|---|---|---|
id | string | yes | the room id for table 1, <room>--t<n> from table 2 |
number | integer | yes | |
active | boolean | yes | running in memory right now |
usable | boolean | yes | would take a player now, by the same rule as the launch |
closed | boolean | yes | taken out of the seating by us: it empties and goes to sleep |
retiring | boolean | yes | in the last rounds of its hash chain: takes no new players, then retires |
recoveryRequired | boolean | yes | held by a previous process after a crash |
quarantineUntil | number · nullable | yes | end of a quarantine after a fault, ms since epoch |
FairRound
| Field | Type | Required | Description |
|---|---|---|---|
roomId | string | the table of the round: the room id, or <room>--t<n> in a room with tables | |
nonce | integer | ||
gameType | string · one of GOD_CANDLE, DIAMOND_HANDS, BIG_FISH, HYPNOFROG · nullable | ||
clientSeed | string · nullable | the client seed of THAT round when stored, else the room's current one. A fixed text, or a drand round: drand:quicknet:<round>:<signature>, whose signature verifies against the quicknet public key (see the Provably fair page) | |
clientSeedFromRound | boolean | true when clientSeed is the round's own | |
seed | string | the revealed server seed | |
seedHash | string | SHA-256 of seed, committed before betting opened | |
chainTerminus | string · nullable | ||
generatorVersion | string · nullable | ||
math | object · nullable | the generator configuration the round ran on; null for rounds older than the configuration snapshot | |
outcome | object | ||
outcome.endReason | string · nullable | ||
outcome.instantRug | boolean · nullable | ||
outcome.endPrice | string | a decimal string, exact as stored (a JSON number on /b2b/round) | |
outcome.maxMultiplier | string | a decimal string, exact as stored (a JSON number on /b2b/round) | |
outcome.patternLabel | string · nullable | ||
outcome.liveStartedAt | string · date-time · nullable | ||
outcome.epochMs | integer · nullable | ||
outcome.endedAt | string · date-time · nullable | ||
positionsComplete | boolean | false when the public list is truncated; Big Fish aggregate verification is then incomplete, and Hypnofrog positions beyond the cap are not checked | |
positions | array of object | the round's positions (at most 500), by betId — the one the operator's wallet saw on /bet, which starts with the room id — with no player identity | |
positions[].betId | string | ||
positions[].entryEpoch | integer · nullable | ||
positions[].fishIndex | integer · nullable | Big Fish (fish index) and Hypnofrog (the position's initial fly) | |
positions[].switches | array of object · nullable | Hypnofrog only — the fly switches accepted while the round was live, in order of receipt, from the command ledger; null when the ledger for that position is missing (the position is then not re-derivable) | |
positions[].switches[].beat | integer | the beat at which the switch reached the server | |
positions[].switches[].to | integer | the fly the position moved to | |
positions[].exit | object · nullable | God Candle and Diamond Hands only — the exit as the server priced it, read from the command ledger; null when the ledger has no row for the position (bots are not logged, so in a demo room this is the ordinary case, and the verifier then re-derives from the path alone). Why the position exited (take profit, stop loss, manual, rug) is not public | |
positions[].exit.epoch | integer | the epoch (God Candle) or stab (Diamond Hands) at which the server priced the exit | |
positions[].exit.price | number | the absolute price the server applied at that epoch | |
positions[].exit.cashedOut | boolean | false = the ledger closed the position with the rug (RUG_SETTLE) | |
positions[].exit.msAfterLaunch | integer · nullable | the command's receive time minus the launch | |
positions[].exit.amount | number · nullable | the stake as the ledger recorded it | |
positions[].exit.multiplier | number · nullable | the closing multiplier as the ledger recorded it (four decimals) | |
positions[].exit.payout | number · nullable | the payout as the ledger recorded it | |
positions[].midRound | boolean · nullable | God Candle, Diamond Hands and Hypnofrog — true when the stake reached the server after the launch, so the mid-round slippage applies for certain (in Hypnofrog this is what tells a first-beat entry apart from a launch entry, both persisted with entry beat 0); false asserts nothing (a stake received before the launch can still be accepted after it while the wallet queue is busy); null when the ledger cannot tell | |
positions[].status | string | ||
positions[].cashoutMultiplier | number · nullable | ||
positions[].amount | number · nullable | the stake | |
positions[].payout | number · nullable | the payout | |
positions[].currency | string · nullable | ||
verificationPage | string | the path of the HTML verification page, /fair/<room>/<nonce>/verifica |
WalletMoveRequest
| Field | Type | Required | Description |
|---|---|---|---|
playerId | PlayerId | yes | |
betId | BetId | yes | |
amount | Money | yes | |
currency | Currency | yes | |
roundId | RoundId | yes | |
multiplier | number · min 0 | Only on /win: the multiplier the position closed at, with four decimals and the same
value on every retry (relative to entry: 1.0 is break-even before the house edge; God
Candle can close below 1.0), or 0 on a lost bet (amount: 0). Absent on /bet.
Optional: ignore it if you do not need it. It is what a tournament tool needs to rank
by "max multiplier" instead of "total staked". The payout is not always
stake × multiplier: a position opened mid-round in God Candle, Diamond Hands or
Hypnofrog is paid stake × multiplier × RTP (guide §6.2).
|
WalletOk
| Field | Type | Required | Description |
|---|---|---|---|
balance | Money | yes |
WalletReject
| Field | Type | Required | Description |
|---|---|---|---|
reason | string · one of INSUFFICIENT_FUNDS, PLAYER_UNKNOWN, PLAYER_BLOCKED, LIMIT_EXCEEDED, SESSION_EXPIRED, MAINTENANCE, ROLLED_BACK, ALREADY_SETTLED | yes | on /bet, one of the translated reasons or ROLLED_BACK; on /rollback, ALREADY_SETTLED |
balance | Money |
SettlementsResponse
| Field | Type | Required | Description |
|---|---|---|---|
operatorId | string | yes | |
from | string · date-time | yes | the lower bound actually used (inclusive) |
to | string · date-time | yes | the upper bound actually used (exclusive) |
rows | array of <a href="#schema-settlementrow">SettlementRow</a> | yes | ordered by createdAt, then by insertion |
truncated | boolean | yes | true when more rows exist than the cap (20000 by default): split the time window |
SettlementRow
one stake as your wallet saw it, joined with its win obligation when one was recorded. Decimals are strings, exact
| Field | Type | Required | Description |
|---|---|---|---|
betId | BetId | yes | |
playerId | PlayerId | yes | |
roomId | string | yes | the table the stake was played at: the room id, or <room>--t<n> in a room with tables |
roundId | RoundId | yes | |
currency | Currency | yes | |
stake | string | yes | the debited amount |
stakeStatus | string · one of OPEN, REJECTED, SETTLED, ROLLBACK_PENDING, ROLLED_BACK | yes | same meaning as status on /b2b/bet/{id} |
payout | string · nullable | yes | the win obligation; null = no recorded win obligation, not zero (a lost bet closed with /win amount 0 has a payout of zero, not null) |
deliveryStatus | string · nullable | yes | PENDING (still retrying), SETTLED (your /win answered 200) or FAILED (no more automatic retries); null when there is no obligation |
multiplier | string · nullable | yes | closing multiplier (0 = lost); null when there is no obligation |
createdAt | string · date-time | yes | when the stake was requested from your wallet |
updatedAt | string · date-time | yes | |
deliveryUpdatedAt | string · date-time · nullable | yes | last change of the obligation |
OperatorRound
| Field | Type | Required | Description |
|---|---|---|---|
roundId | RoundId | yes | |
roomId | string | yes | the table of the round: the room id, or <room>--t<n> in a room with tables |
nonce | integer | yes | |
gameType | string · one of GOD_CANDLE, DIAMOND_HANDS, BIG_FISH, HYPNOFROG | yes | |
endedAt | string · date-time | yes | |
outcome | object | yes | the outcome |
outcome.endReason | string | yes | |
outcome.instantRug | boolean | yes | |
outcome.endPrice | number | yes | a JSON number (a decimal string on the public /fair) |
outcome.maxMultiplier | number | yes | a JSON number (a decimal string on the public /fair) |
fairness | object | yes | the generator inputs, so the round can be re-derived without trusting us; the nullable ones are null on rounds written before the verifiable-round migration |
fairness.seed | string | yes | |
fairness.seedHash | string | yes | |
fairness.clientSeed | string · nullable | yes | |
fairness.generatorVersion | string · nullable | yes | |
fairness.configHash | string · nullable | yes | |
fairness.publicPath | string | yes | the public /fair/<roomId>/<nonce> path — the same inputs, unauthenticated; add /verifica for the HTML page to send a player to |
totals | object | yes | two sums, never a ratio; null (not zero) on rounds written before the round-totals migration |
totals.wagered | number · nullable | yes | |
totals.paidOut | number · nullable | yes | |
bets | array of object | yes | the caller's own positions that closed in this round, won or lost; a stake refunded with /rollback is not listed (see /b2b/bet/{id}) |
bets[].betId | BetId | yes | |
bets[].amount | Money | yes | |
bets[].payout | Money | yes | |
bets[].net | number | yes | payout − amount |
bets[].status | string · one of CASHED_OUT, RUGGED | yes | |
bets[].cashoutMultiplier | number · nullable | yes | |
bets[].currency | Currency | yes | |
bets[].settledAt | string · date-time | yes |
OperatorBet
| Field | Type | Required | Description |
|---|---|---|---|
betId | BetId | yes | |
playerId | PlayerId | yes | |
roundId | RoundId | yes | |
roomId | string | yes | the table the stake was played at: the room id, or <room>--t<n> in a room with tables |
amount | Money | yes | |
currency | Currency | yes | |
status | string · one of OPEN, REJECTED, SETTLED, ROLLBACK_PENDING, ROLLED_BACK | yes | the stake's state towards your wallet — see the endpoint description |
createdAt | string · date-time | yes | |
updatedAt | string · date-time | yes | |
win | object · nullable | yes | the win obligation towards your wallet; null when none was ever queued |
win.status | string · one of PENDING, SETTLED, FAILED | yes | PENDING = still retrying with backoff; SETTLED = your /win answered 200; FAILED = no more automatic retries (attempts exhausted, or a registry problem such as a currency change), handled by hand |
win.amount | Money | yes | |
win.multiplier | number · nullable | yes | closing multiplier (0 = lost) |
win.attempts | integer | yes | retries from the outbox: the first call is not one of them, the retry that succeeded is |
win.lastError | string · nullable | yes | what the last failed attempt got: ERROR for any answer other than a signed 200 (the reason in your body is not kept), CURRENCY_MISMATCH or OPERATOR_NOT_REGISTERED; kept after SETTLED |
timeline | array of object | yes | what the server received for this bet, and the exits it executed itself, in arrival order |
timeline[].kind | string · one of PLACE_BET, CASHOUT, CANCEL_BET, SET_ORDERS, SWITCH_BET, AUTO_CASHOUT, RUG_SETTLE | yes | SET_ORDERS = take profit or stop loss set; SWITCH_BET = Hypnofrog fly switch; AUTO_CASHOUT = an exit the server executed (take profit, stop loss, forced exit at the cap or the time limit); RUG_SETTLE = the position was still open at the loss |
timeline[].receivedAt | string · date-time | yes | |
timeline[].epoch | integer · nullable | yes | |
timeline[].price | number · nullable | yes | |
timeline[].outcome | string · one of ACCEPTED, REJECTED | yes | |
timeline[].reason | string · nullable | yes | why a command was refused, or why the server closed the position (TAKE_PROFIT, STOP_LOSS, RUG…) |
timeline[].multiplier | number · nullable | yes | |
timeline[].payout | number · nullable | yes |
ReportResponse
| Field | Type | Required | Description |
|---|---|---|---|
operatorId | string | yes | |
window | object | yes | the window actually used (from inclusive, to exclusive) and its label |
window.from | string · date-time | yes | |
window.to | string · date-time | yes | |
window.label | string | yes | |
rows | array of <a href="#schema-reportrow">ReportRow</a> | yes | one row per (currency, day of settlement); newest day first, then by currency |
subtotals | array of object | yes | one per currency over the rows above — never a grand total, and no player count |
subtotals[].currency | string | yes | |
subtotals[].bet | integer | yes | |
subtotals[].wagered | number | yes | |
subtotals[].payout | number | yes | |
subtotals[].ggr | number | yes | |
truncated | boolean | yes | true when the row cap (20000 by default) was reached: narrow the window |
ReportRow
| Field | Type | Required | Description |
|---|---|---|---|
currency | string | yes | currency code (? for a bet without one) |
day | string · date | yes | day of settlement (UTC) |
bet | integer | yes | number of bets |
player | integer | yes | distinct players |
wagered | number | yes | sum of stakes |
payout | number | yes | sum of payouts |
ggr | number | yes | wagered − payout |
OperatorEvent
| Field | Type | Required | Description |
|---|---|---|---|
event | string · one of onboarding_at_risk | yes | |
roomId | string | yes | the room you launched, never a table id |
player | string · max 128 chars | yes | the player id exactly as you sent it in the launch — never our internal id |
bets | integer | yes | closed bets without a cashout in this session |
at | string · date-time | yes | ISO 8601 |
OperatorRegistryEntry
One entry of the RGS operator registry — what we configure for you at onboarding, and what
several endpoints above depend on. Not an API payload. A walletBaseUrl that is not https
(http is accepted on loopback only) drops the whole entry at boot; every other invalid
field is dropped on its own, with a log line, and the entry stays. A currency we cannot
pay refuses the boot.
| Field | Type | Required | Description |
|---|---|---|---|
id | string · pattern ^[A-Za-z0-9_-]{1,64}$ | yes | the operator id: operator on the launch and the reads, x-operator on the callbacks. An entry with any other id is dropped at boot |
secret | string | yes | the shared secret: signs our callbacks and the events webhook, verifies your launches and reads |
walletBaseUrl | string · uri | yes | https base of the four callbacks (/bet, /win, /rollback, /balance are appended); http only on loopback |
currency | string · default EUR | one per operator, on every callback. Must be a code we know with at most two decimals: more than two (BTC, KWD) or an unknown code is refused at boot; zero decimals (JPY) is accepted, amounts then travel as integers and the room limits (minBet, maxBet, maxWin) are set in that currency | |
allowedOrigins | array of string | the origins allowed to iframe your REAL_MONEY rooms (Content-Security-Policy: frame-ancestors): https://host[:port], origin only — an entry with a path or without https is dropped. The browser checks every ancestor frame, so a page wrapping your lobby needs its origin here too. Absent = only the RGS itself, fail-closed: a launch opened inside your lobby stays blank until the origin is declared. Demo rooms can be framed by anyone | |
enabled | boolean · default true | false = suspended: launches and reads answer 403 OPERATOR_SUSPENDED, no new session opens; positions already open stay playable and cashable. A value that is not a boolean ("false" in quotes) also suspends | |
ipAllowlist | array of string | IPs allowed to call /b2b/*: exact IPv4 or IPv6 addresses, or IPv4 CIDR blocks (no IPv6 prefixes). Absent or empty = no filter. A defence in addition to the signature, never instead of it | |
walletTimeoutMs | number · default 4000 | per-attempt timeout of every callback (and of the events webhook) | |
walletRetries | integer · min 0 · max 5 · default 1 | retries on network error or timeout only | |
limits | object | per-operator caps, positive numbers: they only tighten a room's own limits — the lower of the two applies (the room's default when it sets none), a registry value can never raise a room | |
limits.maxBet | number | ||
limits.maxWin | number | ||
limits.maxExposure | number | ||
eventsWebhookUrl | string · uri | https (http on loopback only) URL that receives the events webhook; absent = no events are sent |