# Casino Integration API This product is a game provider. It does not own player balances. The casino remains the source of truth for player accounts, available balance, debits, credits, refunds, KYC, AML, responsible gaming, and jurisdiction controls. ## V1 Currency Contract Rouge Reels supports configured operator currencies in v1. Supported currency codes are `USD`, `EUR`, `GBP`, `USDT`, `USDC`, `BTC`, `uBTC`, `ETH`, `LTC`, `DOGE`, `BNB`, `SOL`, `XRP`, `ADA`, `TRX`, `XLM`, and `ZEC`. Each operator configuration has one active currency. If a casino wants both USD and uBTC, create separate operator-currency profiles, for example `casino-usd` and `casino-ubtc`. The casino chooses the operator/session currency and all balances, debits, credits, rollbacks, limits, audit records, and reconciliation rows for that session must use the same currency. Rouge Reels does not perform FX conversion, wallet custody, blockchain settlement, or on-chain balance checks. The casino owns conversion, exchange-rate policy, rounding, and player-facing wallet display before calling the provider. Currency precision: - `USD`, `EUR`, `GBP`: 2 decimals - `USDT`, `USDC`: 2 decimals by default - `BTC`, `LTC`, `DOGE`, `BNB`, `SOL`, `ZEC`: 8 decimals - `uBTC`: 2 decimals, where `100.00 uBTC` means 100 micro-BTC - `ETH`: 6 decimals by default - `XRP`, `ADA`, `TRX`: 6 decimals - `XLM`: 7 decimals Example uBTC operator/session values: ```json { "operatorId": "casino-ubtc", "currency": "uBTC", "balance": 100000.00, "minBet": 1.00, "stakeStep": 0.01, "maxPayout": 5000000.00 } ``` ## Staging Quick Start Use `docs/staging-integration-start-here.md` as the casino-facing first step for the `bspin-staging` integration. It defines the staging base URL, operator id, callback base URL, required wallet endpoints, first launch flow, and evidence to send back. Use `docs/casino-demo-package.md` as the staging handoff package with iframe examples, OpenAPI/Postman links, thumbnail assets, wallet callback contract, and staging acceptance criteria. Use `docs/sandbox-integration-guide.md` only as an optional local sandbox guide with the built-in `demo` operator. It walks through: - local sandbox credentials and local startup - creating a launch token - creating a game session - checking the casino-provided balance - placing settled Dice, Limbo, Plinko, and Keno bets - starting, revealing, cashing out, and restoring an active Mines round - starting, acting on, settling, and restoring an active Blackjack round - replaying the same idempotent bet request - forcing an idempotency conflict - forcing a failed round and rollback - looking up the full round dispute packet - reviewing reconciliation rows and operator dashboard evidence Use `docs/postman_collection.json` to run the same sequence once the staging callback endpoints are connected. A casino partner should be able to complete the staging start page and checklist before production credentials are issued. ## Authentication Production casino callbacks must be signed with HMAC-SHA256 using the shared operator secret. Required headers: - `X-Operator-Id` - `X-Timestamp` - `X-Nonce` - `X-Signature` - `X-Idempotency-Key` for money-changing requests Signature payload: ```txt METHOD + "\n" + PATH + "\n" + X-Timestamp + "\n" + X-Nonce + "\n" + raw JSON body ``` `PATH` is the request path such as `/wallet/debit`. The signature is lowercase hex HMAC-SHA256 over the payload using the shared operator secret. Casinos should compare signatures with a timing-safe comparison. Reject requests when timestamps are outside the allowed clock skew, nonces are reused, signatures do not match, or idempotency keys conflict. Recommended replay storage is at least the allowed clock-skew window, with a default skew of five minutes. ## Callback Transport Local demo operators can use `sandbox://demo-casino`. Production operators should use an HTTPS `callbackBaseUrl`, for example: ```txt https://casino.example.com ``` The provider sends signed `POST` requests to the required wallet paths under that base URL: - `/wallet/validate-session` - `/wallet/balance` - `/wallet/debit` - `/wallet/credit` - `/wallet/rollback` Default provider behavior is a 3 second callback timeout and 1 retry for timeout, network, HTTP 429, and HTTP 5xx failures. Money-changing retries use the original `X-Idempotency-Key`, so a casino must return the original debit, credit, or rollback response instead of creating a second transaction. If a credit callback does not receive a positive casino response, Rouge Reels stores the round as `credit_pending` and retries the same credit idempotency key until it succeeds. If a debit callback is ambiguous or a post-debit provider error requires reversal, Rouge Reels stores the round as `rollback_pending` when rollback does not receive a positive response, then retries the same rollback idempotency key until it succeeds. Pending wallet retries run when the player starts a new session and from the protected 30-minute Vercel cron route. ## Casino-Provided Endpoints The casino implements these endpoints so the game provider can communicate with the casino wallet. ### Validate Session `POST /wallet/validate-session` Purpose: validates a launch token and returns a casino player reference. Request: ```json { "operatorId": "demo", "launchToken": "launch-token-from-casino", "gameType": "dice" } ``` Response: ```json { "valid": true, "playerRef": "player-123", "displayName": "Player 123", "currency": "USD" } ``` `currency` must match the configured operator currency. A session returning a different supported currency is rejected with `CURRENCY_MISMATCH`. Unsupported currency codes are rejected with `UNSUPPORTED_CURRENCY`. ### Get Balance `POST /wallet/balance` Request: ```json { "operatorId": "demo", "playerRef": "player-123", "gameType": "dice", "currency": "USD" } ``` Response: ```json { "balance": 1000.00, "currency": "USD" } ``` The request `currency` is the game session currency chosen for the operator profile. The response `currency` must match it. The amount is the playable balance available to the game in that exact currency. ### Debit Bet `POST /wallet/debit` Request: ```json { "operatorId": "demo", "playerRef": "player-123", "launchToken": "launch-token-from-casino", "gameType": "dice", "roundId": "round_abc", "amount": 10.00, "currency": "USD" } ``` Response: ```json { "casinoTransactionId": "casino_debit_abc", "balance": 990.00, "currency": "USD" } ``` ### Credit Win `POST /wallet/credit` Request: ```json { "operatorId": "demo", "playerRef": "player-123", "launchToken": "launch-token-from-casino", "gameType": "dice", "roundId": "round_abc", "amount": 19.80, "currency": "USD" } ``` Response: ```json { "casinoTransactionId": "casino_credit_abc", "balance": 1009.80, "currency": "USD" } ``` Rouge Reels sends a credit request for every successfully closed round. Losing results use `amount: 0` so the casino can close the round without changing the player balance. ### Rollback Transaction `POST /wallet/rollback` Request: ```json { "operatorId": "demo", "playerRef": "player-123", "gameType": "dice", "roundId": "round_abc", "debitIdempotencyKey": "round_abc:debit", "amount": 10.00, "currency": "USD", "reason": "Game result failed after successful debit" } ``` Rollback uses its own `X-Idempotency-Key` header so the rollback request can be retried safely. The `debitIdempotencyKey` field is the original debit request's `X-Idempotency-Key`, so the casino can locate the debit transaction to reverse. Response: ```json { "casinoTransactionId": "casino_rollback_abc", "balance": 1000.00, "currency": "USD" } ``` ## Game Provider Endpoints ### Embedded Currency Switch The casino can switch the embedded game to another player currency by creating a new casino launch token for the matching operator-currency profile and sending it to the iframe. The game does not trust a client-provided currency value; it creates a new session and uses the currency returned by the casino-validated session. Parent page message: ```js gameIframe.contentWindow.postMessage({ type: "ROUGE_REELS_SWITCH_CURRENCY", requestId: "currency-switch-1", operatorId: "casino-ubtc", launchToken: "new-launch-token-from-casino", game: "dice", launchMode: "single" }, "https://games.rougereels.com"); ``` Iframe reply: ```js { type: "ROUGE_REELS_SWITCH_CURRENCY_RESULT", requestId: "currency-switch-1", ok: true, session: { sessionId: "session_...", operatorId: "casino-ubtc", currency: "uBTC", balance: 100000, game: "dice" } } ``` If a round is settling, the iframe waits briefly before switching. If autobet is running, the iframe requests autobet stop and then switches after it becomes safe. Invalid tokens, unsupported currencies, and mismatched casino balances return `ok: false` with an error code. ### Create Local Demo Launch `POST /sandbox/launch` Creates a local/demo casino launch URL. `/sandbox/launch` is not used for the `bspin-staging` casino staging path; staging launch tokens should be generated by the casino backend. Use `launchMode: "single"` for a normal casino launch where the session is locked to one game. This is the default and the embedded client hides the game switcher. Use `launchMode: "lobby"` only when the casino wants to embed a provider mini-lobby that can switch among the operator's enabled games. Example request: ```json { "operatorId": "demo", "playerRef": "player-demo-1", "gameType": "dice", "launchMode": "single" } ``` Example single-game launch URL: ```text /game.html?operatorId=demo&game=dice&token=demo_launch_player-demo-1 ``` Example lobby launch URL: ```text /game.html?operatorId=demo&game=dice&token=demo_launch_player-demo-1&mode=lobby ``` For a casino-facing iframe, append `embed=1`. Embed mode hides the demo header, seed hash line, and operator/admin link. It shows a compact casino-provided balance well inside the betting panel by default. The game still keeps provably fair details inside the shield control. By default, embed mode uses natural content height so the casino can size the iframe without forced full-screen behavior. ```text /game.html?operatorId=demo&game=dice&token=demo_launch_player-demo-1&embed=1 ``` The launch URL can still control the embedded balance. Supported true values are `1`, `true`, `yes`, and `on`; supported false values are `0`, `false`, `no`, and `off`. The launch URL override wins over the operator default. If the parameter is omitted, the game shows the balance unless `operator.theme.showBalanceInEmbed === false`. ```text /game.html?operatorId=demo&game=dice&token=demo_launch_player-demo-1&embed=1&showBalance=1 ``` Operator theme example: ```json { "theme": { "showBalanceInEmbed": false } } ``` The embedded balance remains casino-provided. Rouge Reels does not custody wallet funds or poll balance in v1; the balance updates on session creation, settled bets, Mines start/cashout, and embedded currency/session switches. For a full-height iframe or kiosk-style placement, add `fill=1`. The casino should also give the iframe a real viewport height; on mobile, prefer `100dvh` so browser address bars are handled correctly. ```text /game.html?operatorId=demo&game=dice&token=demo_launch_player-demo-1&embed=1&fill=1 ``` ```html ``` ### Create Session `POST /api/sessions` Validates a launch token and creates a game session. For single-game launches, the returned session includes `gameType` and the provider rejects bets for any other game. For lobby launches, `gameType` is `null`, `launchMode` is `lobby`, and bets may use any game enabled in the returned public operator configuration. ### Place Bet `POST /api/bets` Request: ```json { "sessionId": "session_abc", "gameType": "dice", "amount": 10, "idempotencyKey": "bet_01JRETRYSAFE", "clientSeed": "player-seed", "params": { "target": 50, "direction": "over" } } ``` Response includes the settled bet, result, proof, casino transaction references, and latest casino balance. `idempotencyKey` is required. If the same key is retried with the same session, game, amount, client seed, and params, the API returns the original settled response without debiting or crediting the casino again. If the same key is reused with a different payload, the API returns `IDEMPOTENCY_CONFLICT`. If debit succeeds but settlement later fails, the API attempts a rollback and returns an error containing `roundId` and `roundStatus`. Use that `roundId` to retrieve the failed round and callback trail. To test rollback, create a valid session and submit a Dice bet with an invalid winning window, for example `params: { "target": 99, "direction": "over" }`. The debit callback succeeds first, game validation fails, Rouge Reels sends `/wallet/rollback`, and the API error response includes the rolled-back `roundId`. `Mines` and `Blackjack` do not use `POST /api/bets` because they have active round lifecycles. Use the active-round endpoints below instead. ### Mines Active Round Mines uses one casino debit at round start, reveal actions without wallet movement, and one casino credit at final close. Cashout sends the payout amount; bust sends `amount: 0` so the casino can close the round. The same active-round storage model is intended for future interactive games such as Blackjack hand actions or Chicken Road step/cashout flows. Start a round: ```http POST /api/mines/rounds Content-Type: application/json ``` ```json { "sessionId": "session_abc", "amount": 10, "idempotencyKey": "mines_start_01", "clientSeed": "player-seed", "params": { "mineCount": 3 } } ``` Reveal one tile: ```http POST /api/mines/rounds/{roundId}/reveal Content-Type: application/json ``` ```json { "sessionId": "session_abc", "tile": 12, "idempotencyKey": "mines_reveal_01" } ``` Cash out: ```http POST /api/mines/rounds/{roundId}/cashout Content-Type: application/json ``` ```json { "sessionId": "session_abc", "idempotencyKey": "mines_cashout_01" } ``` Restore or inspect a round: ```http GET /api/mines/rounds/{roundId}?sessionId=session_abc ``` The start, reveal, and cashout endpoints require idempotency keys. Duplicate start cannot double-debit, duplicate reveal cannot reveal a second tile, and duplicate cashout cannot double-credit. Only one active Mines round is allowed per session/player/game. Final states are immutable: `cashed_out`, `busted`, `cancelled`, or `error`. ### Blackjack Active Round Blackjack uses one casino debit at round start. Insurance, double, and split actions debit only when those rule actions are available. Hit, stand, and declined insurance do not move wallet funds. The final settled response credits the total payout once, including `amount: 0` for losing rounds, and saves the completed bet. Rules: unlimited decks, Blackjack pays 3:2, insurance pays 2:1, split once, no hitting split aces, double on any first two cards including split hands, and dealer stands on soft 17. Start a round: ```http POST /api/blackjack/rounds Content-Type: application/json ``` ```json { "sessionId": "session_abc", "amount": 10, "idempotencyKey": "blackjack_start_01", "clientSeed": "player-seed" } ``` Take or decline insurance when the dealer shows an Ace: ```http POST /api/blackjack/rounds/{roundId}/insurance Content-Type: application/json ``` ```json { "sessionId": "session_abc", "takeInsurance": true, "idempotencyKey": "blackjack_insurance_01" } ``` Player actions: ```http POST /api/blackjack/rounds/{roundId}/hit POST /api/blackjack/rounds/{roundId}/stand POST /api/blackjack/rounds/{roundId}/double POST /api/blackjack/rounds/{roundId}/split Content-Type: application/json ``` ```json { "sessionId": "session_abc", "idempotencyKey": "blackjack_action_01" } ``` Restore or inspect the active round: ```http GET /api/blackjack/rounds/{roundId}?sessionId=session_abc GET /api/blackjack/rounds/active?sessionId=session_abc ``` Every Blackjack action requires an idempotency key. Duplicate actions return the original response for that action key. Only one active Blackjack round is allowed per session/player/game, and the dealer hole card plus draw sequence remain hidden until the round is settled. ### Round History `GET /api/bets/{roundId}` Returns the stored round record, fairness proof, casino transaction references, and `casinoCallbackAudit` for persisted debit, credit, and rollback callbacks. The callback audit includes method, path, callback URL, idempotency key, attempt count, request body, response body, status code, callback status, and error message when available. Shared secrets, launch tokens, and request signatures are not stored in public audit output. ### Verify Result `POST /api/verify` Recalculates a round from server seed, server seed hash, client seed, nonce, game type, amount, and game parameters. ### Reconciliation `GET /api/reconciliation` Returns debit, credit, rollback, `credit_pending`, and `rollback_pending` records for casino reconciliation. ### Wallet Retry Cron `GET /api/cron/wallet-retries` Retries pending wallet callbacks. This route is intended for Vercel Cron and must include: ```text Authorization: Bearer ``` The Vercel schedule is every 30 minutes. Duplicate cron invocations are safe because every retried credit or rollback reuses the original wallet `X-Idempotency-Key`. Optional query parameters are `operatorId`, `playerRef`, and `limit`. In production, if `operatorId` is omitted, the retry job is scoped to `READINESS_OPERATOR_ID` instead of scanning every operator. ## Error Codes - `INVALID_SESSION`: launch token or session id is invalid. - `INSUFFICIENT_FUNDS`: casino balance cannot cover the bet. - `GAME_DISABLED`: operator has disabled the selected game. - `SESSION_GAME_MISMATCH`: single-game launch session tried to place a bet for another game. - `BET_OUT_OF_RANGE`: bet violates min/max limits. - `MAX_PAYOUT_EXCEEDED`: payout exceeds configured risk limit. - `SIGNATURE_INVALID`: callback signature failed. - `SIGNATURE_REQUIRED`: callback signature headers are missing. - `SIGNATURE_STALE`: callback timestamp is invalid or outside allowed skew. - `SIGNATURE_REPLAYED`: callback nonce has already been used. - `IDEMPOTENCY_REQUIRED`: bet placement did not include an idempotency key. - `IDEMPOTENCY_CONFLICT`: duplicate key has a conflicting payload. - `CASINO_TIMEOUT`: casino callback did not respond inside the timeout. - `CASINO_HTTP_ERROR`: casino callback returned a non-retryable HTTP error. - `CASINO_HTTP_RETRYABLE`: casino callback returned HTTP 429 or 5xx and retries were exhausted. - `CASINO_NETWORK_ERROR`: casino callback could not be reached. - `UNAUTHORIZED`: protected cron/admin route was called without the expected bearer token. - `WALLET_RETRY_OPERATOR_REQUIRED`: production wallet retry was called without `operatorId` and `READINESS_OPERATOR_ID`. ## Retry Rules Money-changing requests must be retried with the same idempotency key. A repeated debit, credit, or rollback with the same idempotency key must return the original response instead of creating a second transaction. Failed credits are never rolled back. Rouge Reels repeats `/wallet/credit` with the original `X-Idempotency-Key` until the casino returns a positive response. Failed or ambiguous debits are reversed through `/wallet/rollback`; if rollback fails, Rouge Reels repeats rollback with the original rollback idempotency key and includes the original debit key in `debitIdempotencyKey`.