Build and launch operators
under your aggregator account
An aggregator is a parent tenant that owns multiple operators. You can create operators, configure them, and mint launch tokens for players — without the platform admin involved in day-to-day operations.
How it works
Two services are involved. The Backoffice API manages operator creation and configuration. The Games API mints launch tokens and handles the actual game session. Both authenticate via your aggregator API key.
Get provisioned by an admin
A platform super-admin creates your aggregator account and sends you your first API key. This is a one-time operation — the plaintext key is shown only once and never stored by the platform.
a1b2c3d4e5f6.<secret> — a 12-character prefix followed by a dot and a random secret. Store this securely; it cannot be recovered. Use key rotation if you lose it.
All Backoffice API requests use the header:
X-Aggregator-Key: a1b2c3d4e5f6.your-secret-here
Verify your credentials work by calling the me endpoint:
{
"aggregator": {
"id": "agg-uuid",
"code": "acme-games",
"name": "Acme Games Ltd",
"status": "active"
}
}
Create an operator
Each operator is a white-label tenant — a distinct brand with its own wallet endpoint, JWT secret, and frontend URL. Operators you create are automatically linked to your aggregator account.
// Request body { "code": "mybrand", // unique slug, lowercase a-z 0-9 hyphens "name": "My Brand Casino", "walletBaseUrl": "https://wallet.mybrand.com", "gameFrontendBaseUrl": "https://play.mybrand.com", "walletTimeoutMs": 5000 // optional, default 5000 }
{
"operator": {
"id": "op-uuid",
"code": "mybrand",
"jwtAlg": "HS256",
"jwtSharedSecret": "hex-secret-shown-once", // ← store this!
"walletHmacSecret": "hex-secret-shown-once", // ← store this!
"walletBaseUrl": "https://wallet.mybrand.com",
"gameFrontendBaseUrl": "https://play.mybrand.com",
"status": "active"
}
}
jwtSharedSecret and walletHmacSecret are returned exactly once. Store them in your secrets manager immediately. They cannot be retrieved again — only rotated via the platform admin.
| Field | Type | Description | |
|---|---|---|---|
| code | string | required | Unique operator slug. Pattern: ^[a-z0-9][a-z0-9-]*[a-z0-9]$. Used as iss in launch JWTs. |
| name | string | required | Display name for the operator. |
| walletBaseUrl | string | required | Base URL of your wallet service (receives Bet/Win/Rollback calls). |
| gameFrontendBaseUrl | string | required | URL players land on after launch. The platform appends ?game=, currency=, etc. |
| walletTimeoutMs | integer | optional | Wallet call timeout in ms. Default: 5000. |
Configure the operator
Set the currencies, enabled games, and bet limits. The platform enforces this config on every bet — a currency or game not in the config will be rejected.
// Request body { "enabledGames": ["crash", "dice", "blackjack", "baccarat"], "currencies": { "USDT": { "minBet": "0.1", "maxBet": "500" }, "BTC": { "minBet": "0.000005", "maxBet": "0.01" } }, "featureFlags": { "autoplay": true, "turbo": true, "chat": false, "soundDefault": true }, "demoStartingBalance": 1000 }
dice limbo keno wheel plinko mines hilo crash roulette blackjack baccarat coinflip russian_roulette dice_fight multiplayer_blackjack
Mint a launch token for a player
When one of your players wants to play, your server mints a short-lived launch token on their behalf. The token is a signed HS256 JWT — the platform verifies it and starts the session.
// Request body { "sub": "player-123", // your internal player ID "currency": "USDT", "game": "crash", "language": "en", // optional "country": "US", // optional "ttlSeconds": 300, // optional, max 600, default 600 "returnUrl": "https://play.mybrand.com/lobby" // optional }
{
"token": "eyJhbGciOiJIUzI1NiJ9...",
"launchUrl": "/launch?token=eyJ...&game=crash¤cy=USDT",
"expiresIn": 300,
"jti": "uuid-nonce" // single-use nonce, burned at /launch
}
| Field | Type | Description | |
|---|---|---|---|
| sub | string | required | Your player's ID. This is the value your wallet will receive in Bet/Win/Rollback calls. |
| currency | string | required | ISO currency code. Must be enabled in the operator's config. |
| game | string | required | Game ID the player should land on. Must be in enabledGames. |
| language | string | optional | BCP 47 language tag (en, fa, tr, …). Falls back to operator default. |
| country | string | optional | ISO 3166-1 alpha-2 country code. Passed to the wallet for geo-compliance checks. |
| ttlSeconds | integer | optional | Token lifetime in seconds. Hard ceiling: 600. Default: 600. Shorter values reduce replay exposure. |
| returnUrl | string | optional | URL the game UI shows as the "exit to lobby" link. Usually your lobby page. |
Redirect the player
Take the launchUrl from the mint response and redirect the player's browser to it. The platform will verify the token, call your wallet's /auth endpoint, set a session cookie, and redirect the player to the game frontend.
// 1. Mint the token (server-side) const res = await fetch( `https://api.betterplay.com/v1/aggregator/operators/mybrand/launch-tokens`, { method: 'POST', headers: { 'X-Aggregator-Key': process.env.AGGREGATOR_API_KEY, 'Content-Type': 'application/json', }, body: JSON.stringify({ sub: req.user.id, currency: 'USDT', game: 'crash', ttlSeconds: 120, returnUrl: 'https://play.mybrand.com/lobby', }), } ); const { launchUrl } = await res.json(); // 2. Redirect the player reply.redirect(`https://api.betterplay.com${launchUrl}`);
jti nonce) is burned the moment the player hits /launch. A second redirect with the same token will be rejected. Always mint a fresh token per session attempt.
Full request flow
What happens when a player clicks "Play" on your lobby:
Player clicks "Play"
Your frontend sends a request to your own backend — e.g. POST /start-game.
Your backend mints a token
Your server calls POST /v1/aggregator/operators/mybrand/launch-tokens with the player's ID, currency, and game. Games API returns a signed JWT and a launchUrl.
Redirect to the platform
Your backend redirects the player's browser to https://api.betterplay.com{launchUrl}.
Platform verifies the token
GET /launch?token=… verifies the HS256 signature using the operator's shared secret, checks the jti nonce has not been used, and burns it.
Wallet auth
The platform calls POST {walletBaseUrl}/auth on your wallet to confirm the player exists, retrieve their balance and currency, and check for freezes.
Session established — player lands on the game
A session cookie is set and the player is redirected to gameFrontendBaseUrl?game=crash¤cy=USDT&return_url=…. They are now authenticated and can bet.
Endpoint reference
All Backoffice API endpoints require X-Aggregator-Key. All Games API endpoints require the same header.
| Method | Path | Service | Description |
|---|---|---|---|
| GET | /api/v1/bo/aggregator/me | Backoffice | Return your aggregator profile. |
| POST | /api/v1/bo/aggregator/operators | Backoffice | Create an operator owned by your aggregator. |
| PUT | /api/v1/bo/aggregator/operators/:id/config | Backoffice | Upsert game/currency/feature config for an operator. |
| GET | /api/v1/bo/aggregator/operators/:id | Backoffice | Read an operator's details. |
| GET | /api/v1/bo/aggregator/operators | Backoffice | List all operators owned by your aggregator. |
| GET | /api/v1/bo/aggregator/operators/:id/config | Backoffice | Read the current config for an operator. |
| POST | /api/v1/bo/aggregator/api-keys/rotate | Backoffice | Issue a new API key. Old keys continue to work until explicitly revoked. |
| POST | /v1/aggregator/operators/:code/launch-tokens | Games API | Mint a single-use HS256 launch token for a player. |
| GET | /v1/aggregator/operators | Games API | List all operators owned by your aggregator. |
API key rotation
Rotate your aggregator API key without downtime. The new key is issued immediately; switch over your services, then the old key expires naturally.
// Request body (optional) { "label": "rotated 2025-Q3" } // Response { "apiKey": "newprefix.new-secret-shown-once" }
Error codes
| HTTP | Condition | Fix |
|---|---|---|
| 401 | Missing or invalid X-Aggregator-Key |
Check the header value and key format (prefix.secret). |
| 401 | Aggregator is disabled | Contact the platform admin. |
| 404 | Operator not found or not owned by your aggregator | Verify the operator code/ID and that it was created under your aggregator. |
| 409 | Operator code already exists | Choose a different code — codes are globally unique. |
| 422 | Operator uses RS256 — minting not supported | Aggregator minting is HS256 only. RS256 operators must self-mint. |
| 422 | Operator is disabled | Enable the operator first via POST /bo/operators/:id/enable. |