# 155.io API Documentation > Complete API reference for integrating 155.io games into your platform. ## Overview 155.io provides a betting game platform (marble racing, duck racing, coin flip, etc.). Integration consists of two parts: 1. **155.io API** — Endpoints we provide for fetching games and generating player URLs 2. **Operator API** — Endpoints you implement for balance, bets, rollbacks, and wins ## Environments | Environment | Base URL | |-------------|----------| | Staging | `https://api.stagingmarbles.io` | | Production | `https://api.marbles.xyz` | ## Key Concepts ### Partner ID vs Operator ID - `partnerId` — You choose this. It identifies your website or casino brand (e.g., `"my-casino"`, `"my-casino-asia"`). You can use different partner IDs to distinguish between platforms or regions under the same operator. - `operatorId` — Provided by 155.io. Links to your operator configuration (liability limits, game settings, etc.). ### Currency Precision All amounts use **5-digit precision** and are **64-bit integers** (`int64` / `long`): ``` $10.00 = 1000000 $1.00 = 100000 $0.01 = 1000 ``` Use `long` (Java) / `int64` (Go/Protobuf) / `bigint` (JS) — not `int` (32-bit), which would overflow at ~$21,474 with 5-digit precision. --- # Security ## Request Signing All API requests must be signed using RSA-SHA256. Sign the request body with your private key, base64-encode the signature, and include it in the `X-Marbles-Signature` header. All responses from 155.io are also signed — verify them using 155.io's public key. ### TypeScript Example ```typescript import { createSign, createVerify } from 'node:crypto' // Sign outgoing request function sign(message: string, privateKey: string): string { return createSign('RSA-SHA256') .update(message) .sign(privateKey, 'base64') } // Verify incoming response function isValid(message: string, signature: string, publicKey: string): boolean { return createVerify('RSA-SHA256') .update(message) .verify(publicKey, signature, 'base64') } ``` ### Python Example ```python from cryptography.hazmat.primitives import hashes, serialization from cryptography.hazmat.primitives.asymmetric import padding import base64 def sign(message: str, private_key_pem: str) -> str: private_key = serialization.load_pem_private_key(private_key_pem.encode(), password=None) signature = private_key.sign(message.encode(), padding.PKCS1v15(), hashes.SHA256()) return base64.b64encode(signature).decode() def is_valid(message: str, signature: str, public_key_pem: str) -> bool: public_key = serialization.load_pem_public_key(public_key_pem.encode()) try: public_key.verify(base64.b64decode(signature), message.encode(), padding.PKCS1v15(), hashes.SHA256()) return True except Exception: return False ``` ### PHP Example ```php function sign(string $message, string $privateKey): string { openssl_sign($message, $signature, $privateKey, OPENSSL_ALGO_SHA256); return base64_encode($signature); } function isValid(string $message, string $signature, string $publicKey): bool { return openssl_verify($message, base64_decode($signature), $publicKey, OPENSSL_ALGO_SHA256) === 1; } ``` ### Java Example ```java Signature sig = Signature.getInstance("SHA256withRSA"); sig.initSign(privateKey); sig.update(message.getBytes()); String signature = Base64.getEncoder().encodeToString(sig.sign()); ``` ### C# Example ```csharp byte[] data = Encoding.UTF8.GetBytes(message); byte[] signature = privateKey.SignData(data, HashAlgorithmName.SHA256, RSASignaturePadding.Pkcs1); string signatureBase64 = Convert.ToBase64String(signature); ``` ## Invalid Signature Response If signature validation fails on our end: ```json HTTP/1.1 400 Bad Request { "status": "INVALID_SIGNATURE" } ``` ## Timeouts We set a **5-second timeout** on all requests. The goal is sub-second communication. ## Idempotency All requests must be processed idempotently. If a request with the same `transactionId` is received multiple times, return the same response. Only `requestId` changes between retries — `transactionId` stays the same. If the same `transactionId` is received with a different payload, return `DUPLICATE_TRANSACTION_ERROR`. --- # 155.io API (Endpoints We Provide) ## GET Games Fetch available games for your catalogue. We recommend fetching at least once a day. ``` POST /game/games ``` ### Request ```json POST /game/games HTTP/1.1 Host: api.marbles.xyz X-Marbles-Signature: Content-Type: application/json { "operatorId": "your-operator-id" } ``` | Parameter | Type | Required | Description | |-----------|------|----------|-------------| | `operatorId` | string | Yes | Your operator identifier (provided by 155.io) | ### Success Response ```json HTTP/1.1 200 OK X-Marbles-Signature: [ { "id": "2469f711-2da6-46b7-8648-3313dfdc5bb5", "type": "Game", "name": "Marble Plinko - Classic", "enabled": true, "assets": [ { "type": "background", "extension": "webp", "url": "https://..." }, { "type": "thumbnail", "extension": "webp", "url": "https://..." } ], "blockedCountries": ["AU", "AE", "GB", "US"], "items": ["uuid-1", "uuid-2"], "allowedBetTypes": ["PickWinner", "StraightForecast", "ReverseForecast"], "createdAt": "2024-12-13T04:39:03Z", "updatedAt": "2025-01-29T13:52:47Z" } ] ``` | Field | Type | Description | |-------|------|-------------| | `id` | string | Unique game identifier (UUID). Use this when launching games. | | `type` | string | Always `"Game"` | | `name` | string | Display name of the game | | `enabled` | boolean | Whether the game is currently available. Only show games where `enabled: true`. | | `assets` | array | Game assets (`background`, `thumbnail`) for your catalogue | | `blockedCountries` | array | ISO 3166-1 alpha-2 country codes where the game is blocked | | `items` | array | UUIDs of selectable betting options in this game (marbles, ducks, outcomes, etc.) | | `allowedBetTypes` | array | Bet types available for this game | | `createdAt` | string | ISO 8601 creation timestamp | | `updatedAt` | string | ISO 8601 last modified timestamp | ### Error Responses | Status | Description | |--------|-------------| | `UNKNOWN_OPERATOR` | No operator exists for the provided `operatorId` | | `INVALID_SIGNATURE` | Request signature validation failed | ## Get Game URL Generate a game URL with a one-time login code for a player. Use in an iframe or redirect mobile players directly. ``` POST /game/game/url ``` ### Request ```json POST /game/game/url HTTP/1.1 Host: api.marbles.xyz X-Marbles-Signature: Content-Type: application/json { "clientSessionId": "some-session-id", "clientPlayerId": "some-player-id", "operatorId": "your-operator-id", "partnerId": "my-casino", "gameId": "2469f711-2da6-46b7-8648-3313dfdc5bb5", "username": "unique-username", "currency": "USD", "platform": "MOBILE", "displayName": "Player Display Name", "lobbyUrl": "https://mylobby.url", "depositUrl": "https://mydeposit.url", "language": "en", "country": "US" } ``` | Parameter | Type | Required | Description | |-----------|------|----------|-------------| | `clientSessionId` | string | Yes | Your session identifier for this player | | `clientPlayerId` | string | Yes | Your unique player identifier | | `operatorId` | string | Yes | Your operator identifier (provided by 155.io) | | `partnerId` | string | Yes | Your chosen identifier for the website/casino (you define this) | | `gameId` | string | Yes | Game UUID from `/game/games` | | `username` | string | Yes | Unique username for the player | | `currency` | string | Yes | ISO 4217 currency code | | `platform` | string | Yes | `"MOBILE"` or `"DESKTOP"` | | `displayName` | string | No | Non-unique display name shown in game | | `lobbyUrl` | string | No | URL to redirect player back to lobby | | `depositUrl` | string | No | URL for player to make deposits | | `language` | string | No | ISO 639-1 language code (default: `en`) | | `country` | string | No | ISO 3166-1 alpha-2 country code | ### Success Response ```json HTTP/1.1 200 OK X-Marbles-Signature: { "url": "https://game.marbles.xyz?otl=ABCDE...XYZ&country=US&language=en" } ``` ### Fun Money Mode Generate a demo link with fun money by omitting `clientSessionId` and `clientPlayerId` and setting `currency` to `XXX`. No callbacks will be sent to your server. ```json { "operatorId": "your-operator-id", "partnerId": "my-casino", "gameId": "game-uuid", "username": "demo-player", "currency": "XXX", "platform": "DESKTOP" } ``` --- # Operator API (Endpoints You Implement) These endpoints must be implemented on your server. 155.io calls them to manage player balances and transactions. **Important rules:** - All responses must use **HTTP 200**, including error responses. Non-200 HTTP status codes trigger retries or rollbacks on our side. - All responses must be signed with `X-Marbles-Signature`. - All endpoints must be idempotent. - Currently, it's always 1 bet per round per player. - All fields listed below are always present in our requests, except freebet fields (`isFree`, `rewardUuid`) which are only included for freebet transactions. ## POST /balance Called to retrieve the player's current balance. We never store player balances. ### Request ```json POST /balance HTTP/1.1 Host: your.game.api X-Marbles-Signature: Content-Type: application/json { "clientSessionId": "06mnrpyv2qd9jbwhoniyimxsy", "clientPlayerId": "02mnrpyv2qd9jbwhoniyimxsy", "requestId": "4ec4a295-cd84-46df-b225-4b72bd84892c" } ``` | Field | Type | Description | |-------|------|-------------| | `clientSessionId` | string | The player's session identifier | | `clientPlayerId` | string | The player's unique identifier | | `requestId` | string | Unique request identifier (UUID) | ### Success Response ```json HTTP/1.1 200 OK X-Marbles-Signature: { "status": "SUCCESS", "requestId": "4ec4a295-cd84-46df-b225-4b72bd84892c", "clientPlayerId": "02mnrpyv2qd9jbwhoniyimxsy", "currency": "USD", "balance": 1000000 } ``` | Field | Type | Description | |-------|------|-------------| | `status` | string | `"SUCCESS"` | | `requestId` | string | Echo back the request ID | | `clientPlayerId` | string | Echo back the player ID | | `currency` | string | ISO 4217 currency code | | `balance` | int64 | Balance with 5-digit precision ($10.00 = 1000000) | ### Error Statuses | Status | Description | |--------|-------------| | `SESSION_EXPIRED_ERROR` | Player session has expired | | `PLAYER_NOT_FOUND_ERROR` | Player does not exist | | `SESSION_NOT_FOUND_ERROR` | Session does not exist | | `UNKNOWN_ERROR` | Any other error | ## POST /bet Called when a player places a bet. Deduct the bet amount from the player's balance. ### Request ```json POST /bet HTTP/1.1 Host: your.game.api X-Marbles-Signature: Content-Type: application/json { "requestId": "8df0475e-5069-483a-8205-f6089997abc9", "transactionId": "ea0240f5-483d-434b-a8d4-04dabf61cde3", "clientSessionId": "0k3cz83bb3h2vn53ocnc7pxw9", "clientPlayerId": "02mnrpyv2qd9jbwhoniyimxsy", "roundId": "17cc81fd-df13-4ca4-857d-de0f766dc372", "gameId": "f1c0b104-f29d-44a9-ae93-e8afcbe3feb9", "amount": 1000000, "currency": "USD", "meta": { "betType": "PickWinner", "selection": ["item-uuid-1", "item-uuid-2"] } } ``` | Field | Type | Description | |-------|------|-------------| | `requestId` | string | Unique request identifier (UUID) | | `transactionId` | string | Unique transaction identifier for this bet | | `clientSessionId` | string | The player's session identifier | | `clientPlayerId` | string | The player's unique identifier | | `roundId` | string | The game round identifier | | `gameId` | string | The game identifier | | `amount` | int64 | Bet amount with 5-digit precision | | `currency` | string | ISO 4217 currency code | | `isFree` | boolean | Only present for freebets. `true` if this is a freebet. | | `rewardUuid` | string | Only present for freebets. The reward identifier. | | `meta` | object | Bet metadata | | `meta.betType` | string | Type of bet placed | | `meta.selection` | array | Selected items for the bet | ### Freebet Request For freebets, `amount` is `0`, `isFree` is `true`, and `rewardUuid` identifies the promotion. Do not deduct any balance. ```json { "amount": 0, "currency": "USD", "isFree": true, "rewardUuid": "promo-winter-2025-001" } ``` ### Success Response ```json HTTP/1.1 200 OK X-Marbles-Signature: { "status": "SUCCESS", "requestId": "8df0475e-5069-483a-8205-f6089997abc9", "clientPlayerId": "02mnrpyv2qd9jbwhoniyimxsy", "currency": "USD", "balance": 1000000 } ``` ### Error Statuses | Status | Description | |--------|-------------| | `INSUFFICIENT_BALANCE_ERROR` | Player does not have enough balance | | `DUPLICATE_TRANSACTION_ERROR` | Transaction ID already exists with different payload | | `BONUS_ERROR` | Bet does not pass bonus rules | | `BET_LIMIT_REACHED_ERROR` | Player has reached their betting limit | | `VALIDATION_ERROR` | Missing or invalid mandatory fields | | `UNKNOWN_ERROR` | Bet could not be registered | ## POST /win Called to register the result of a bet. For wins, add the amount to the player's balance. For losses, the amount is `0`. ### Request ```json POST /win HTTP/1.1 Host: your.game.api X-Marbles-Signature: Content-Type: application/json { "requestId": "8df0475e-5069-483a-8205-f6089997abc9", "transactionId": "9ea48131-3a0f-4067-94d0-3212e7e25abb", "referenceTransactionId": "ea0240f5-483d-434b-a8d4-04dabf61cde3", "clientSessionId": "0k3cz83bb3h2vn53ocnc7pxw9", "clientPlayerId": "02mnrpyv2qd9jbwhoniyimxsy", "roundId": "17cc81fd-df13-4ca4-857d-de0f766dc372", "gameId": "f1c0b104-f29d-44a9-ae93-e8afcbe3feb9", "amount": 1000000, "currency": "USD", "roundClosed": true } ``` | Field | Type | Description | |-------|------|-------------| | `requestId` | string | Unique request identifier (UUID) | | `transactionId` | string | Unique transaction identifier for this win | | `referenceTransactionId` | string | The original bet transaction ID | | `clientSessionId` | string | The player's session identifier | | `clientPlayerId` | string | The player's unique identifier | | `roundId` | string | The game round identifier | | `gameId` | string | The game identifier | | `amount` | int64 | Win amount (0 for losses) with 5-digit precision | | `currency` | string | ISO 4217 currency code | | `isFree` | boolean | Only present for freebets. `true` if the original bet was a freebet. | | `roundClosed` | boolean | `true` if this is the final transaction for the round | ### Win vs Loss - **Win**: `amount > 0` — Add this amount to the player's balance - **Loss**: `amount = 0` — No balance change needed (bet was already deducted) ### Freebet Wins When `isFree: true` and `amount > 0`, credit the amount as real money. Freebet winnings are real money. ### Round Lifecycle - `roundClosed: true` — This is the final transaction. No more transactions for this round. - `roundClosed: false` — More transactions may follow (rare, for future multi-bet support). ### Success Response ```json HTTP/1.1 200 OK X-Marbles-Signature: { "status": "SUCCESS", "requestId": "8df0475e-5069-483a-8205-f6089997abc9", "clientPlayerId": "02mnrpyv2qd9jbwhoniyimxsy", "currency": "USD", "balance": 1000000 } ``` ### Error Statuses | Status | Description | |--------|-------------| | `DUPLICATE_TRANSACTION_ERROR` | Transaction ID already exists with different payload | | `VALIDATION_ERROR` | Missing or invalid mandatory fields | | `UNKNOWN_ERROR` | Win could not be registered | ## POST /rollback Called when a bet should be voided (round cancelled). Refund the bet amount to the player's balance. We only send bet rollbacks — there are no win rollbacks. ### Request ```json POST /rollback HTTP/1.1 Host: your.game.api X-Marbles-Signature: Content-Type: application/json { "requestId": "8df0475e-5069-483a-8205-f6089997abc9", "transactionId": "9ea48131-3a0f-4067-94d0-3212e7e25abb", "referenceTransactionId": "ea0240f5-483d-434b-a8d4-04dabf61cde3", "clientSessionId": "0k3cz83bb3h2vn53ocnc7pxw9", "clientPlayerId": "02mnrpyv2qd9jbwhoniyimxsy", "roundId": "17cc81fd-df13-4ca4-857d-de0f766dc372", "gameId": "f1c0b104-f29d-44a9-ae93-e8afcbe3feb9", "roundClosed": true } ``` | Field | Type | Description | |-------|------|-------------| | `requestId` | string | Unique request identifier (UUID) | | `transactionId` | string | Unique transaction identifier for this rollback | | `referenceTransactionId` | string | The original bet transaction ID being rolled back | | `clientSessionId` | string | The player's session identifier | | `clientPlayerId` | string | The player's unique identifier | | `roundId` | string | The game round identifier | | `gameId` | string | The game identifier | | `isFree` | boolean | Only present for freebets. `true` if the original bet was a freebet. | | `roundClosed` | boolean | `true` if this is the final transaction for the round | ### Freebet Rollbacks When `isFree: true`, no balance refund is needed (the original freebet had `amount: 0`). Mark the freebet as cancelled in your records. ### Success Response ```json HTTP/1.1 200 OK X-Marbles-Signature: { "status": "SUCCESS", "requestId": "8df0475e-5069-483a-8205-f6089997abc9", "clientPlayerId": "02mnrpyv2qd9jbwhoniyimxsy", "currency": "USD", "balance": 1000000 } ``` ### Error Statuses | Status | Description | |--------|-------------| | `DUPLICATE_TRANSACTION_ERROR` | Transaction ID already exists with different payload | | `VALIDATION_ERROR` | Missing or invalid mandatory fields | | `UNKNOWN_ERROR` | Rollback could not be processed | --- # Idempotency Details Our system can send idempotent retries for both `/bet` and `/win`. The `transactionId` stays the same across retries — only `requestId` changes each time. Expected behavior: - If you receive a `transactionId` you've already successfully processed, return `SUCCESS` with the current balance (treat it as a no-op, don't deduct/credit again) - If you receive the same `transactionId` but with a different payload, return `DUPLICATE_TRANSACTION_ERROR` --- # Transaction Flow ## Normal Round 1. Player places a bet → 155.io calls `POST /bet` 2. Round completes → 155.io calls `POST /win` (amount > 0 for win, amount = 0 for loss) 3. `roundClosed: true` on the final transaction ## Cancelled Round 1. Player places a bet → 155.io calls `POST /bet` 2. Round is cancelled → 155.io calls `POST /rollback` (refund the original bet) 3. `roundClosed: true` on the rollback ## Freebet Round 1. Player uses a freebet → 155.io calls `POST /bet` with `amount: 0`, `isFree: true` 2. Round completes → 155.io calls `POST /win` with `isFree: true` (if won, `amount` is real money to credit) --- # Bet Types | Bet Type | Description | Selection Size | |----------|-------------|----------------| | `PickWinner` | Pick one or more items — win if any comes first | 1+ items | | `StraightForecast` | Pick 2 items to finish 1st and 2nd in exact order | 2 items | | `ReverseForecast` | Pick 2 items to finish 1st and 2nd in any order | 2 items | | `Tricast` | Pick 3 items to finish 1st, 2nd, 3rd in exact order | 3 items | | `CombinationTricast` | Pick 3 items to finish in top 3 in any order | 3 items | Not all games support all bet types. Check the `allowedBetTypes` field in the Get Games response. --- # Liability The liability amount controls the maximum bet a player can place: ``` Max Bet = Liability Amount / Payout Multiplier ``` Example with $1,000,000 liability: | Bet Type | Multiplier | Max Bet | |----------|------------|---------| | Pick 1 of 8 marbles | 7.55x | ~$132,450 | | Pick 3 exact order | 316.98x | ~$3,155 | --- # Freebets & Rewards ## Granting Freebets (Direct Integration) ``` POST /v1/free-bets/rewards ``` ```json { "clientPlayerId": "player-123", "operatorId": "your-operator-id", "clientRewardId": "promo-winter-2025-001", "amount": 500000, "currency": "USD", "quantity": 3, "expiresAt": "2025-12-31T23:59:59Z", "gameIds": ["game-uuid-1", "game-uuid-2"] } ``` | Field | Type | Required | Description | |-------|------|----------|-------------| | `clientPlayerId` | string | Yes | Player's unique identifier | | `operatorId` | string | Yes | Your operator ID | | `clientRewardId` | string | Yes | Your unique identifier for tracking this reward | | `amount` | integer | Yes | Value per freebet with 5-digit precision | | `currency` | string | Yes | ISO 4217 currency code | | `quantity` | integer | No | Number of freebets (default: 1, max: 10) | | `expiresAt` | string | Yes | ISO 8601 expiration datetime | | `startTime` | string | No | ISO 8601 datetime when freebets become available | | `gameIds` | array | No | Restrict to specific games (omit for all games) | Authentication: Same `X-Marbles-Signature` header used for all other endpoints. No additional authorization needed. Each `clientRewardId` is tied to one player. For 100 players, make 100 API calls. The `clientRewardId` is returned in bet transactions as `rewardUuid` for tracking. ### Freebet Statuses | Status | Description | |--------|-------------| | `claimable` | Available for the player to use | | `claimed` | Player has selected but not yet placed a bet | | `used` | Freebet was used to place a bet | | `expired` | Freebet expired before being used | | `cancelled` | Freebet was cancelled by operator | ### Cancelling Freebets ``` POST /v1/free-bets/rewards/cancel ``` ```json { "clientRewardId": "promo-winter-2025-001", "operatorId": "your-operator-id", "reason": "Player requested cancellation" } ``` Rules: - Only freebets with status `claimable` or `claimed` can be cancelled - If any freebet from the reward has been `used`, the entire reward cannot be cancelled ### Freebet Flow 1. You grant a freebet via the API 2. Player launches a game and sees the freebet in their inventory 3. Player taps the freebet and selects "Use now" (manual activation required) 4. Player places a bet (freebet applied, `amount: 0`) 5. You receive `/bet` with `isFree: true`, `amount: 0` 6. Round completes — if player wins, you receive `/win` with `isFree: true`, `amount: ` 7. Credit winnings as real money --- # Supported Currencies ## Fiat AED, ARS, AUD, AZN, BDT, BRL, CAD, CLP, CNY, COP, DKK, EGP, EUR, GBP, GEL, GHS, GTQ, HKD, HNL, IDR, ILS, INR, JPY, KES, KGS, KRW, KZT, LKR, MXN, MYR, NGN, NOK, NZD, PEN, PHP, PKR, PLN, RUB, SEK, SGD, THB, TND, TRY, TWD, TZS, UAH, UGX, USD, UZS, VND, ZAR ## Crypto BTC (BTC, mBTC, sat), ETH (ETH, mETH, Gwei), LTC (LTC, mLTC, litoshi), USDT, USDC, XRP (XRP, mXRP), TRX (TRX, mTRX), DOGE (DOGE, mDOGE), ADA (ADA, mADA), SOL (SOL, mSOL), BNB (BNB, mBNB), TON (TON, mTON), SHIB, PYUSD ## Special | Code | Description | |------|-------------| | XXX | Fun money / demo mode | | GLD | GoldCoin (virtual currency, 1:10000 to USD) | | SS1 | SweepCoin (sweepstakes currency, 1:1 to USD) | | UFC | Alternative fun currency | | USD1 | USD-denominated fun currency | ## Kilo Variants kIDR (IDR in thousands), kVND (VND in thousands) --- # Supported Languages | Code | Language | |------|----------| | en | English | | da | Danish | | tr | Turkish | | th | Thai | | fr | French | | ru | Russian | | ja | Japanese | | es | Spanish | | pt | Portuguese | | de | German | | zh | Chinese | | ko | Korean | | vi | Vietnamese | | ar | Arabic | | hi | Hindi | | en-soc | English (SweepStakes) | Default: `en` (English) --- # Error Codes Reference ## Security Errors | Status | Description | |--------|-------------| | `INVALID_SIGNATURE` | Request signature validation failed | ## System Errors | Status | Description | |--------|-------------| | `EXTERNAL_SERVICE_TIMEOUT_ERROR` | Request to external service timed out | ## 155.io API Errors ### /game/games | Status | Description | |--------|-------------| | `UNKNOWN_OPERATOR` | No operator for the provided `operatorId` | ### /v1/free-bets/rewards | Status | Description | |--------|-------------| | `VALIDATION_ERROR` | Invalid request | | `FREEBETS_NOT_ENABLED` | Freebets not enabled for operator | | `PLAYER_NOT_FOUND` | Player does not exist | | `REWARD_ALREADY_EXISTS` | Reward with this `clientRewardId` already exists | | `UNKNOWN_ERROR` | Internal server error | ## Operator API Error Statuses All operator API responses must use HTTP 200. These are statuses you return in the JSON body. ### /balance `SESSION_EXPIRED_ERROR`, `PLAYER_NOT_FOUND_ERROR`, `SESSION_NOT_FOUND_ERROR`, `UNKNOWN_ERROR` ### /bet `INSUFFICIENT_BALANCE_ERROR`, `DUPLICATE_TRANSACTION_ERROR`, `BONUS_ERROR`, `BET_LIMIT_REACHED_ERROR`, `VALIDATION_ERROR`, `UNKNOWN_ERROR` ### /win `DUPLICATE_TRANSACTION_ERROR`, `VALIDATION_ERROR`, `UNKNOWN_ERROR` ### /rollback `DUPLICATE_TRANSACTION_ERROR`, `VALIDATION_ERROR`, `UNKNOWN_ERROR` Any unrecognized status is treated as `UNKNOWN_ERROR`. ### Error Response Format ```json { "status": "ERROR_CODE_HERE", "requestId": "original-request-id", "clientPlayerId": "player-id" } ``` --- # Testing ## Staging Environment | | URL | |---|---| | API | `https://api.stagingmarbles.io` | | Game Status | https://status.155.io (select "Staging") | ## Track Availability Not all tracks run continuously on staging. Some are reserved for internal testing (rollback scenarios, edge cases). Check https://status.155.io to see which tracks are live. If a track appears offline, this is intentional. ## Testing Steps 1. Generate a game URL on staging, open in browser 2. Place a test bet — verify `/bet` is received 3. Complete the game — verify `/win` or `/rollback` is called 4. Verify balances update correctly ## Go-Live Readiness Contact our team to schedule final verification. We need: - A staging URL and login credentials to access your platform - We run test rounds covering win, loss, and rollback - We verify callbacks against internal logs --- # Features ## Lightning Rounds Random boosted rounds with increased multipliers. ## Streak Multipliers Win and loss streak bonuses for enhanced engagement. ## Player Levels XP-based progression with permanent multiplier boosts. ## Daily Check-in Streak-based rewards and win multipliers. All multiplier boosts stack additively. Feature availability may vary depending on integration type.