Reelgate Operator API
The API your casino backend calls to list games, open game sessions and read the ledger.
- Base URL:
https://reelgate.code-matrix.tech/api/v1 - Format: JSON in, JSON out (
Content-Type: application/json) - Credentials: every brand (one casino domain) has its own API key (
rgk_...) and secret. Find them in the portal under Brands. Rotating the secret there invalidates the old one at once.
Signing every request
| Header | Value |
|---|---|
X-Reelgate-Key |
the brand API key |
X-Reelgate-Timestamp |
unix time in seconds; must be within 300 s of our clock |
X-Reelgate-Signature |
hex HMAC-SHA256(secret, <timestamp>\n<METHOD>\n<path?query>\n<raw body>) |
<path?query>is the request target exactly as you send it, starting at/api/v1/.... Encoded characters stay encoded: if you send/api/v1/players/a%3Ab, sign/api/v1/players/a%3Ab.<raw body>is the exact body bytes. A GET has an empty body, so the string ends with\n.- If the brand has an IP allow-list (portal → Brands → Allowed IPs), calls from other addresses get
403 IP_NOT_ALLOWED.
Node.js:
const crypto = require('crypto');
async function reelgate(method, pathWithQuery, body) {
const raw = body ? JSON.stringify(body) : '';
const ts = Math.floor(Date.now() / 1000).toString();
const sig = crypto.createHmac('sha256', process.env.REELGATE_SECRET)
.update(`${ts}\n${method}\n${pathWithQuery}\n${raw}`).digest('hex');
const res = await fetch(`https://reelgate.code-matrix.tech${pathWithQuery}`, {
method,
headers: { 'Content-Type': 'application/json', 'X-Reelgate-Key': process.env.REELGATE_KEY,
'X-Reelgate-Timestamp': ts, 'X-Reelgate-Signature': sig },
body: raw || undefined,
});
return res.json();
}
PHP:
$raw = $body ? json_encode($body) : '';
$ts = (string) time();
$sig = hash_hmac('sha256', "$ts\n$method\n$pathWithQuery\n$raw", $secret);
Errors
Every error has the same shape and a matching HTTP status:
{ "error": { "code": "GAME_DISABLED", "message": "Game is disabled for this brand" } }
| Code | HTTP | Meaning |
|---|---|---|
INVALID_KEY |
401 | unknown API key |
BAD_TIMESTAMP |
401 | timestamp missing or more than 300 s off |
BAD_SIGNATURE |
401 | signature does not match (check path, query and body bytes) |
BRAND_INACTIVE |
403 | the brand is suspended |
IP_NOT_ALLOWED |
403 | source IP not on the brand allow-list |
MISSING_PARAM / INVALID_MODE / INVALID_AMOUNT |
422 | bad input |
NOT_FOUND |
404 | no such player, session, game or round |
GAME_NOT_FOUND / GAME_DISABLED / PROVIDER_DISABLED |
404 / 404 / 403 | the game is not offered to this brand |
CURRENCY_NOT_ALLOWED |
422 | the currency is not enabled on the brand |
CURRENCY_NOT_SUPPORTED |
422 | this game does not run in that currency (see currencies on the game) |
DEMO_DISABLED / DEMO_NOT_AVAILABLE |
403 / 422 | demo is switched off or the game has no demo |
KYB_NOT_APPROVED |
403 | real-money play waits for KYB approval of this brand |
OUT_OF_CREDIT |
403 | the company credit is used up (top up with your account manager) |
WALLET_NOT_CONFIGURED |
403 | seamless brand without a wallet URL |
LAUNCH_FAILED |
502 | the game provider did not open the game; retry or pick another game |
WRONG_WALLET_MODE |
422 | deposit/withdraw called on a seamless brand |
INSUFFICIENT_FUNDS |
422 | transfer brand: not enough player balance |
Endpoints
GET /api/v1/ping
This call checks your keys and shows whether real play is open.
{ "ok": true, "brand": "acme-com", "company": "acme", "wallet_mode": "seamless",
"currencies": ["USD","EUR"], "kyb_status": "approved", "real_play": true, "refusal": null,
"time": "2026-11-06T10:00:00Z" }
refusal names the reason real play is closed: BRAND_INACTIVE, KYB_NOT_APPROVED, OUT_OF_CREDIT or WALLET_NOT_CONFIGURED.
GET /api/v1/providers
This call lists the game studios available to your brand, each with enabled (the brand's effective switch).
GET /api/v1/games
Query parameters:
| Param | |
|---|---|
provider |
provider key from /providers |
q |
search in the name, or an exact game id |
type |
game type (slots, live, table, ...) |
demo=true |
only games with a demo |
live=true |
only live-dealer games |
limit |
1 to 200, default 50 |
cursor |
the next value from the previous page |
Only games enabled for the brand are returned. Each game:
{ "id": 10421, "name": "Book of Gold", "provider": "booming_games", "studio": "Booming Games",
"type": "slots", "category": "video_slots", "thumbnail_url": "https://...",
"has_demo": true, "has_freespins": true, "has_jackpot": false, "is_live": false, "mobile": true,
"rtp": 96.1, "volatility": "high", "max_win_multiplier": 5000,
"currencies": ["USD","EUR"], "languages": ["en","de","pt"],
"settings": { "enabled": true, "demo_enabled": true, "min_bet_usd": null, "max_bet_usd": null, "extra": {} } }
currencies lists every currency the game can run in. If a currency is not on that list, a session launch in it is refused.
GET /api/v1/games/:id
This call returns one game, shaped like the list items above.
POST /api/v1/sessions
This call opens a game for a player and returns the URL to load in an iframe or a new window.
{ "player_id": "u-1842", "game_id": 10421, "currency": "EUR", "mode": "real",
"language": "en", "device": "mobile", "exit_url": "https://acme.com/lobby",
"cashier_url": "https://acme.com/cashier", "nickname": "lucky7", "country": "DE",
"player_ip": "203.0.113.9" }
player_idis your own player id (any string up to 100 characters). The player record is created on first use.modeisreal(money moves through your wallet) ordemo(provider fun mode, no money). The default isreal.currencydefaults to the brand's default currency.
Response 201:
{ "session": { "token": "rgt_...", "url": "https://...", "mode": "real", "status": "open",
"currency": "EUR", "game_id": 10421, "provider": "booming_games", "player_id": "u-1842",
"created_at": "...", "ended_at": null } }
Opening a new session closes the player's previous open session on the same brand, so a player plays one game at a time.
GET /api/v1/sessions/:token and POST /api/v1/sessions/:token/close
The first call reads a session. The second closes it: a closed session refuses new stakes, but wins and refunds from rounds already in play still settle.
Players
POST /api/v1/players: create or update a player (player_id,currency,nickname,country).GET /api/v1/players/:player_id: read a player. On transfer brands the response also shows the balances.POST /api/v1/players/:player_id/depositand.../withdraw: transfer brands only. Body:{ "transaction_id": "<your id>", "amount": "50.00", "currency": "EUR" }.transaction_idis idempotent. Sending it again returns the first result with"duplicate": true.
GET /api/v1/transactions
This is the ledger: every money leg, newest first.
- Filters:
player_id,round_id,tx_id,kind(bet,win,rollback,adjust,transfer_in,transfer_out),status(ok,pending,refused,failed),provider,game_id,currency,from,to(ISO times). - Paging:
limit(up to 500) andcursor.
{ "transactions": [ { "id": 9312, "tx_id": "rg-5521-bet", "kind": "bet", "amount": "1", "currency": "EUR",
"amount_usd": "1.08", "round_id": "...", "ref_tx_id": null, "player_id": "u-1842", "game_id": 10421,
"provider": "booming_games", "status": "ok", "balance_after": "99", "error": null, "latency_ms": 41,
"attempts": 0, "is_test": false, "created_at": "..." } ], "next": "9300" }
GET /api/v1/rounds/:round_id
This call returns every leg of one round, oldest first.
GET /api/v1/reports/ggr?from=&to=&group=day|provider|game
This call returns GGR for the brand, in USD and per currency.
GET /api/v1/billing
This call shows the company credit:
{ "credit_usd": 5000.0, "charged_usd": 812.4, "credit_left_usd": 4187.6, "blocks_at_zero": true }
Free spins
Free spins run at the provider. Players do not pay for the spins. Whatever they win counts as a promo win, and it reaches your wallet as a normal win credit with "promo": true. Free spins only work on brands that are open for real play. You set the casino's caps in the portal under Game settings: free spins on or off, the max spins per player and the max bet per spin. Every grant is checked against those caps.
GET /api/v1/freespins/providers
This call lists the providers that can deliver free spins. Each row has enabled, the number of eligible games, spins_max, days_max and notes.
GET /api/v1/freespins/games?provider=¤cy=&q=
This call lists the games of that provider that take free spins. When you pass currency, each game also shows its bet_steps.
GET /api/v1/freespins/options?currency=EUR&game_ids=10421,10422
Call this before you grant. It returns what you can give for these games together:
{ "currency": "EUR", "provider": "booming_games", "mixed_providers": false,
"games": [ { "id": 10421, "name": "Book of Gold", "ok": true, "reason": null, "bet_steps": ["0.2","0.5","1"], "spin_counts": [] } ],
"bet_steps": ["0.2","0.5"], "bet_steps_all": ["0.2","0.5","1"], "spin_counts": [],
"spins_max": 500, "days_max": 90, "notes": null,
"casino_limits": { "max_spins": 50, "max_bet_usd": 0.6 }, "usd_rate": 1.08 }
betmust be one ofbet_steps. These are the provider's real bet steps that every selected game shares, cut to your max bet per spin.bet_steps_allshows the full list before that cut.- Some providers accept only fixed spin counts. In that case
spin_countslists them, andspinsmust be one of them. - All games in one grant must belong to the same provider.
POST /api/v1/freespins
This call gives free spins to one player.
{ "reference": "fs-u1842-welcome", "player_id": "u-1842", "game_ids": [10421], "spins": 20,
"bet": "0.2", "currency": "EUR", "valid_days": 7 }
reference is your id for the grant, and the call is idempotent by it. Response 201 (or 422 with status: "failed" and error when the provider refuses):
{ "freespins": { "reference": "fs-u1842-welcome", "player_id": "u-1842", "provider": "booming_games",
"game_ids": [10421], "games": [ { "id": 10421, "name": "Book of Gold" } ], "spins": 20, "spins_used": 0,
"spins_left": 20, "bet": "0.2", "currency": "EUR", "total_bet": "4.0", "win_total": "0.0", "win_total_usd": 0.0,
"status": "active", "error": null, "expires_at": "...", "created_by": "api:acme-com", "created_at": "...", "finished_at": null } }
status is one of active, finished, expired, cancelled or failed.
GET /api/v1/freespins?player_id=&status=&cursor=lists the grants, newest first, withnext_cursor.GET /api/v1/freespins/:referencereads one grant, including the spins used and the wins so far.POST /api/v1/freespins/:reference/cancelstops the unplayed spins at the provider.
Campaigns
A campaign is one offer that many players receive: the same games, spins, bet and validity for everyone.
POST /api/v1/campaignstakes{ reference, name, game_ids, spins, bet, currency, valid_days, ends_at, max_players, notes, players: ["u-1","u-2"] }. It returns thecampaignand oneresultsrow per player, with that player'sstatusanderror.GET /api/v1/campaignsandGET /api/v1/campaigns/:referencereturn the campaign withstats:players,active,finished,failed,cancelled,spins_given,spins_usedandpromo_wins_usd.POST /api/v1/campaigns/:reference/playerswith{ "players": [...] }adds players. A player who is already in the campaign is skipped. A request can carry up to 500 players.POST /api/v1/campaigns/:reference/cancelcancels every unplayed grant.
Provider campaigns (tournaments, prize drops)
Some providers run their own promotions: tournaments, prize drops, missions and cash drops, often monthly and on a set of their games. The provider pays these prizes, not you. A prize reaches your wallet as a normal win credit with "promo": false and three extra fields:
{ "action": "win", "amount": "25.00", "promo": false,
"prize_type": "tournament", "prize_campaign": "November Spins Race", "prize_funded_by": "provider" }
prize_type is one of tournament, prize_drop, mission, cash_drop, jackpot_drop, cashback, bonus_card or promo. prize_campaign is the campaign name when the provider sends one (it can be null). Credit the win like any other.
Whether your players take part is the Provider promotions switch in the portal (Settings, per company or brand). Pragmatic Play, Funky Games, Wicked Games and Arrows Edge (jackpots) are told on every session, so a change applies from the next game launch. For other providers we set it at the provider. The portal's Promotions → Provider campaigns tab lists what Pragmatic is running now for your currencies and every prize paid to your players, per campaign.
Error codes: INVALID_SPINS, INVALID_BET, BET_ABOVE_LIMIT, INVALID_DAYS, GAME_NOT_FOUND, GAME_NOT_ELIGIBLE, MIXED_PROVIDERS, PROVIDER_NOT_SUPPORTED, CURRENCY_NOT_ALLOWED, DUPLICATE_REFERENCE, INVALID_REFERENCE, TOO_MANY_PLAYERS, PLAYER_TEST, MISSING_PARAM.
How billing works
- You prepay credit.
- Every month we charge a share of the GGR your brands make on each provider (bets minus wins minus refunds; a negative month charges nothing).
- The share per provider is agreed in your contract. A price change applies only from its start date, so past charges never move.
- Promo wins (free-spin wins) are shown apart. GGR after promo = GGR before promo − promo wins. Your contract sets, per provider, whether we bill on GGR before promo (promo wins are charged, the default) or after promo (promo wins are deducted). The portal's Promotions page and Billing invoice show both numbers.
- Provider prizes (tournaments, prize drops, see Provider campaigns) are on their own line too. By default they lower the GGR like any win (
deduct). Your contract can instead say we bill on the GGR before them (charge). - When
blocks_at_zerois true and the credit runs out, real-money launches are refused withOUT_OF_CREDIT. Demo play continues.