Rivo API Documentation
Integrate 18,000+ games from 148 suppliers through a single seamless-wallet API.
Introduction
Rivo API is a game aggregation platform. With one integration you get slots, live casino, fish, crash, card, sports and mini games from 148 suppliers (PG Soft, JILI, Pragmatic Play, Evolution, Hacksaw, Play'n GO, Relax Gaming, Habanero, Spribe, NetEnt, Red Tiger and more) — more than 18,000 games in total.
The integration uses a seamless wallet: the player's money always stays in your system. When a player spins, we call your wallet callback URL with the bet and win amounts and you reply with the new balance. There are no deposits or withdrawals to a provider wallet, no reconciliation and no float.
How it works
- 1Your backend calls
POST /api/v1/game/launchwith the player's username and agame_uid. - 2We ask your wallet callback for the player's current balance (
action: "balance"). - 3We open a session at the game supplier with that balance and return a
game_launch_url. You show it to the player (redirect or iframe). - 4Every round the supplier reports the bet and the win. We forward them to your callback in one request (
action: "bet"); you debit/credit the player and return the resulting balance. - 5All rounds are stored and available in the dashboard and through
GET /api/v1/transactions.
"1.50" = 1.50 USD), never in cents.Base URL
https://bo2.rivobit.comAll endpoints are served over HTTPS and accept/return JSON (Content-Type: application/json).
Quick Start
- 1Create an API key in the dashboard under API Keys. Keep it secret — it identifies your merchant account.
- 2Set your wallet callback URL under Settings → Callback URL and copy the callback secret shown below it. Implement the endpoint as described in Wallet Callback.
- 3Fetch the catalog:
GET /api/v1/providersandGET /api/v1/gamesgive you supplier codes andgame_uids to build your lobby. - 4Launch a game:
POST /api/v1/game/launchwith a username andgame_uid. Redirect the player to the returned URL. - 5Verify: watch Callback Log and Transactions in the dashboard while you play. Every request we sent to your wallet — and what you answered — is listed there.
game/launch for a new username registers the player automatically. player/create is optional and mainly useful for validation.Authentication & Conventions
Every request to /api/v1/* must carry your API key as a Bearer token:
Authorization: Bearer <YOUR_API_KEY>
Content-Type: application/jsonAPI keys are created and revoked in the dashboard. A revoked (inactive) key returns 401. Keys are bound to your merchant account, its default currency and its callback URL.
Response envelope
Successful responses return success: true and the payload under data:
{
"success": true,
"data": { ... }
}Errors return an HTTP error status, success: false and a message. When the game supplier rejected the request the upstream result code is included as provider_code (see Error Codes):
{
"success": false,
"error": "Human readable message",
"details": "Optional extra information",
"provider_code": 10008 // only when the upstream provider rejected the call
}Usernames
A player username is 1–16 characters, letters, digits and underscore (^[a-zA-Z0-9_]{1,16}$). It is the identifier you use in your own system; we map it to an internal member_account at the supplier, so the same username can safely exist at different merchants.
Currencies
A player has one currency, fixed at first launch. Launching the same player with a different currency is rejected (400). Each game lists the currencies it supports; see Currencies & Languages.
Rate limits
Catalog endpoints are served from our cache and can be called freely; we recommend caching the game list on your side and refreshing it once a day. Launch requests are limited to 60 per minute per API key — contact support if you need more.
Create Player
/api/v1/player/createRegisters a player under your merchant account.Players are created lazily on first launch, so this call is optional. Use it to validate a username, to pin the player's currency before the first launch, or to pre-register accounts in bulk.
| Parameter | Type | Description |
|---|---|---|
usernamerequired | string | Your identifier for the player. 1–16 chars, a-z A-Z 0-9 _. |
currencyoptional | string | ISO 4217 code. Defaults to your account currency. Cannot be changed later. |
POST /api/v1/player/create
{
"username": "player001",
"currency": "USD" // optional, defaults to your account currency
}{
"success": true,
"data": {
"username": "player001",
"member_account": "ab12cd_66f1a2_player001",
"currency": "USD",
"status": "active",
"created": true,
"createdAt": "2026-09-18T09:06:48.634Z"
}
}created is false when the player already existed (the call is idempotent). member_account is the internal account name used at the supplier; it also appears in the callback payload.
curl -X POST https://bo2.rivobit.com/api/v1/player/create \
-H "Authorization: Bearer YOUR_API_KEY" \
-H "Content-Type: application/json" \
-d '{"username":"player001","currency":"USD"}'Launch Game
/api/v1/game/launchReturns a URL that opens the game for a player (seamless wallet).Before returning the URL we call your wallet callback with action: "balance" to read the player's current balance. Make sure your callback is reachable, otherwise the launch fails with 502.
| Parameter | Type | Description |
|---|---|---|
usernamerequired | string | Player username. Created automatically if unknown. |
game_uidrequired | string | Game identifier from GET /api/v1/games. |
currencyoptional | string | Player currency. Defaults to your account currency; must match the player's currency and be supported by the game. |
langoptional | string | Game UI language, e.g. en, tr, pt, zh. Default en. See Languages. |
home_urloptional | string | URL the player returns to when closing the game. Must not contain `?`. |
platformoptional | string | web (default) or h5 for the mobile build of the game. |
POST /api/v1/game/launch
{
"username": "player001",
"game_uid": "1189baca156e1bbbecc3b26651a63565",
"currency": "USD",
"lang": "en",
"home_url": "https://www.yourcasino.com/lobby",
"platform": "web"
}{
"success": true,
"data": {
"game_launch_url": "https://play.example-gamehost.com/launch?session=0cb1394b-8d40-4fa9-92d0-936a08d36697",
"game_uid": "1189baca156e1bbbecc3b26651a63565",
"game_name": "Mahjong Ways",
"provider": "PG",
"username": "player001",
"currency": "USD",
"balance": 500
}
}balance is what your callback reported and what the game opens with. Show game_launch_url to the player by redirecting or inside an iframe:
<iframe
src="https://play.example-gamehost.com/launch?session=0cb1394b-…"
allow="fullscreen; autoplay"
style="width:100%;height:100%;border:0"
></iframe>curl -X POST https://bo2.rivobit.com/api/v1/game/launch \
-H "Authorization: Bearer YOUR_API_KEY" \
-H "Content-Type: application/json" \
-d '{
"username": "player001",
"game_uid": "1189baca156e1bbbecc3b26651a63565",
"currency": "USD",
"lang": "en",
"home_url": "https://www.yourcasino.com/lobby",
"platform": "web"
}'Common errors
| HTTP | error | Cause |
|---|---|---|
| 400 | Callback URL is not configured | Set it in Settings first. |
| 400 | Game does not support currency X | Check currencies of the game. |
| 400 | Player currency is X, cannot launch with Y | A player keeps the currency of its first launch. |
| 400 | home_url cannot contain ? | Use a path without a query string. |
| 404 | Game not found | Unknown or removed game_uid. Refresh your catalog. |
| 502 | Merchant callback failed / timed out | Your wallet did not answer within 10 s or returned an invalid body. |
| 502 | provider_code 10030 | Supplier rate limit, retry after a short delay. |
Wallet Callback
The wallet callback is an HTTPS endpoint on your server. We send it a JSON POST for every balance check and every game round. It is the only integration point where money moves, so implement it carefully: signature verification, idempotency and atomic balance updates are mandatory.
Request
POST https://www.yourcasino.com/api/rivo/wallet
Content-Type: application/json
X-Signature: 3f1c…e9a2 // HMAC-SHA256(rawBody, callbackSecret), hex
X-Timestamp: 1789722409049| Parameter | Type | Description |
|---|---|---|
actionrequired | string | balance — return the balance only. bet — settle one round (bet + win) and return the new balance. |
usernamerequired | string | The username you used at launch. |
member_accountrequired | string | Internal account name at the supplier (for logging). |
currencyrequired | string | Player currency, e.g. USD. |
timestamprequired | number | Unix time in milliseconds when we sent the request. |
transaction_idoptional | string | bet only. Unique UUID of the round settlement. Idempotency key. |
round_idoptional | string | bet only. Supplier round id (may be empty for some suppliers). |
game_uidoptional | string | bet only. The game being played. |
provideroptional | string | bet only. Supplier code, e.g. PG. |
bet_amountoptional | string | bet only. Stake, 2 decimals. Negative = refund of an earlier stake. |
win_amountoptional | string | bet only. Payout, 2 decimals. Negative = reversal of an earlier win. |
dataoptional | object|null | bet only. Extra event detail for sports suppliers, otherwise null. |
free_spin_bonusoptional | string|null | bet only. bonus_code of the free-round campaign when this round was a free spin, otherwise absent/null. |
{
"action": "balance",
"username": "player001",
"member_account": "ab12cd_66f1a2_player001",
"currency": "USD",
"timestamp": 1789722408767
}{
"action": "bet",
"username": "player001",
"member_account": "ab12cd_66f1a2_player001",
"currency": "USD",
"transaction_id": "4bb09882-3c85-4bc4-9d42-b4f93a1b4ec5",
"round_id": "round-1",
"game_uid": "1189baca156e1bbbecc3b26651a63565",
"provider": "PG",
"bet_amount": "1.00",
"win_amount": "2.00",
"data": null,
"timestamp": 1789722409049
}Response
Reply with HTTP 200 and a JSON body. The balance is the player's balance after the operation, in main currency units:
{
"error": 0,
"balance": 501.00
}| error | Meaning | What we do |
|---|---|---|
| 0 | OK — the operation was applied | The round is confirmed to the supplier with your balance. |
| 1 | Insufficient funds | The round is rejected; the supplier shows an "insufficient balance" message. |
| 2 | Player not found / other error | The round is rejected. |
{
"error": 1, // insufficient funds -> the round is rejected
"balance": 0.50
}bet request carries the stake and the win of the same round in one message. Apply balance = balance − bet_amount + win_amount as one atomic operation. Never process them as two separate steps that could partially fail.Amount format
Amounts are decimal strings with two decimals ("100.55"). Parse them as exact decimals, never as binary floating point. A supplier that has not yet been configured for two decimals may occasionally send more precision (up to four decimals, e.g. "100.5555"); we forward that value exactly as we settled it, so your books always match ours. Always parse the string you receive instead of assuming a fixed length.
Round lifecycle
Every bet request is one independent financial event. transaction_id identifies it and is the only idempotency key; round_id merely groups related events and may repeat across several requests. Never deduplicate on round_id.
One round can produce one or many events, depending on the supplier and even on the individual game: a single request carrying stake and win together; a stake-only request that is already the complete result of a losing round (no follow-up arrives); several wins for one round; free-spin wins with bet_amount 0; several stakes in one round on some live tables; and 3 or more events for sports bets.
Apply each accepted request exactly once as balance = balance − bet_amount + win_amount, with the signs exactly as received. Negative amounts are cancellations and may be partial (for example bet_amount: "-40.00" against an earlier stake of 100.00), and the same round may receive several of them, each with its own transaction_id.
Signature verification
Every request carries X-Signature: the hex-encoded HMAC-SHA256 of the raw request body (bytes exactly as received, before any JSON parsing) using your callback secret from Settings. Reject requests whose signature does not match — use a constant-time comparison.
const crypto = require('crypto');
const signature = crypto.createHmac('sha256', CALLBACK_SECRET).update(rawBody).digest('hex');
const valid = crypto.timingSafeEqual(Buffer.from(signature), Buffer.from(req.headers['x-signature'] || ''));Idempotency & retries
- If your endpoint does not answer within 10 seconds or returns a non-JSON body, the round is treated as failed and the supplier retries it with the same `transaction_id`.
- Store every processed
transaction_id. When you see it again, do not apply the amounts a second time — returnerror: 0and the balance you had after the original processing. - Never rely on
round_idfor idempotency: some suppliers send severalbetmessages per round (e.g. free spins, bonus rounds), each with its owntransaction_id.
Refunds & reversals
When a supplier cancels a round it sends a bet with a negative bet_amount (returning the stake) and/or a negative win_amount (taking back a payout). Apply the same formula; a negative win may legitimately make the balance go down. If the balance would become negative, still apply it and return the (negative) balance — do not reject reversals.
Reference implementations
// Express example
const crypto = require('crypto');
const express = require('express');
const app = express();
const CALLBACK_SECRET = process.env.RIVO_CALLBACK_SECRET;
app.post('/api/rivo/wallet', express.json({
verify: (req, _res, buf) => { req.rawBody = buf.toString(); }
}), async (req, res) => {
// 1. verify the signature over the RAW body
const expected = crypto.createHmac('sha256', CALLBACK_SECRET).update(req.rawBody).digest('hex');
if (expected !== req.headers['x-signature']) {
return res.status(401).json({ error: 2, balance: 0 });
}
const { action, username, transaction_id, bet_amount, win_amount } = req.body;
const player = await db.players.findOne({ username });
if (!player) return res.json({ error: 2, balance: 0 });
if (action === 'balance') {
return res.json({ error: 0, balance: player.balance });
}
// 2. idempotency: same transaction_id -> same answer, no double booking
const existing = await db.walletTx.findOne({ id: transaction_id });
if (existing) return res.json({ error: 0, balance: existing.balanceAfter });
// 3. apply bet + win atomically
const delta = parseFloat(win_amount) - parseFloat(bet_amount);
if (player.balance + delta < 0) {
return res.json({ error: 1, balance: player.balance });
}
const updated = await db.players.findOneAndUpdate(
{ _id: player._id, balance: { $gte: -delta } },
{ $inc: { balance: delta } },
{ new: true }
);
await db.walletTx.insertOne({ id: transaction_id, balanceAfter: updated.balance, ...req.body });
res.json({ error: 0, balance: updated.balance });
});Checklist
- HTTPS endpoint, reachable from the internet, answers in < 2 s (hard limit 10 s).
- Verifies
X-Signatureover the raw body. - Handles
balanceandbet; unknown actions →error: 2. - Applies bet and win atomically with a row lock / conditional update.
- Persists
transaction_idand returns the same answer on retries. - Returns amounts as numbers or numeric strings with at most 2 decimals.
- Logs every request — the dashboard Callback Log shows our side, keep yours too.
Free Spins
A free-round campaign grants one player a fixed number of rounds on one game, paid for by you. Use it for welcome bonuses, retention campaigns or compensation. The player simply opens the game (via the normal launch flow) and the free rounds are offered inside the game.
/api/v1/freespins/createGrants a free-round campaign to a player.| Parameter | Type | Description |
|---|---|---|
usernamerequired | string | Player username. Created automatically if unknown. |
game_uidrequired | string | Game that supports free rounds (slots from most suppliers). |
roundsrequired | number | Number of free rounds, 1–1000. |
total_betrequired | string | Total stake value of all rounds in the player currency. Bet per round = total_bet / rounds; use a value that matches one of the game's bet levels. |
bonus_coderequired | string | Your identifier for the campaign, unique per merchant (A-Z a-z 0-9 _ -, max 64). Echoed as free_spin_bonus in wallet callbacks. |
currencyoptional | string | Player currency. Defaults to your account currency. |
start_atoptional | string | ISO 8601 or unix timestamp. Defaults to now. |
expires_atoptional | string | ISO 8601 or unix timestamp. Defaults to start + 7 days, max 90 days. |
POST /api/v1/freespins/create
{
"username": "player001",
"game_uid": "1189baca156e1bbbecc3b26651a63565",
"rounds": 10,
"total_bet": "2.00", // 10 rounds × 0.20
"bonus_code": "WELCOME10",
"currency": "USD",
"start_at": "2026-09-18T00:00:00Z",
"expires_at": "2026-09-25T00:00:00Z"
}{
"success": true,
"data": {
"id": "66f1…",
"bonus_code": "WELCOME10",
"username": "player001",
"game_uid": "1189baca156e1bbbecc3b26651a63565",
"game_name": "Mahjong Ways",
"provider": "PG",
"currency": "USD",
"rounds": 10,
"total_bet": 2,
"bet_per_round": 0.2,
"start_at": "2026-09-18T00:00:00.000Z",
"expires_at": "2026-09-25T00:00:00.000Z",
"status": "active",
"created_at": "2026-09-18T09:06:48.634Z"
}
}curl -X POST https://bo2.rivobit.com/api/v1/freespins/create \
-H "Authorization: Bearer YOUR_API_KEY" \
-H "Content-Type: application/json" \
-d '{"username":"player001","game_uid":"1189baca156e1bbbecc3b26651a63565","rounds":10,"total_bet":"2.00","bonus_code":"WELCOME10","expires_at":"2026-09-25T00:00:00Z"}'Free spins in the wallet callback
Every round played from a campaign reaches your wallet callback as a normal bet with free_spin_bonus set to the bonus_code. bet_amount is 0.00 (the stake is covered by the campaign) and win_amount is the real payout you must credit:
{
"action": "bet",
"username": "player001",
"member_account": "ab12cd_66f1a2_player001",
"currency": "USD",
"transaction_id": "9d3f0b21-…",
"round_id": "round-77",
"game_uid": "1189baca156e1bbbecc3b26651a63565",
"provider": "PG",
"bet_amount": "0.00", // stake is covered by the campaign
"win_amount": "3.40",
"free_spin_bonus": "WELCOME10", // bonus_code of the campaign
"data": null,
"timestamp": 1789722409049
}win_amount only. Free-spin wins count towards GGR like any other win, so the campaign cost is reflected in your invoice.Listing campaigns
/api/v1/freespinsReturns your campaigns, newest first. Filter by `username` or `bonus_code`.GET /api/v1/freespins?username=player001&bonus_code=WELCOME10&page=1&limit=100Common errors
| HTTP | error | |
|---|---|---|
| 400 | bonus_code already used | Use a unique code per campaign. |
| 400 | Game does not support currency X | Check the game's currencies. |
| 404 | Game not found | Unknown game_uid. |
| 502 | provider_code 10008 / 10022 | The game does not support free rounds or the bet level is invalid — adjust total_bet. |
Event Webhooks
Besides the synchronous wallet callback, you can receive asynchronous event webhooks at a separate URL (Settings → Event webhooks). Events are written to a durable outbox together with the underlying record and delivered by a worker with retries, so a failed delivery never affects the transaction itself.
Event types
round.settled— a real-player round your wallet accepted (same fields asGET /api/v1/transactions).player.created— a new player was registered under your account.freespin.created— a free-round campaign was granted.credit.changed— your prepaid credit changed (deposit confirmed or manual adjustment).
Delivery & signature
POST https://www.yourcasino.com/rivo/events
Content-Type: application/json
X-Event-Id: 6f1c2d3e-…
X-Event-Type: round.settled
X-Signature: 3f1c…e9a2 // HMAC-SHA256(rawBody, callbackSecret), hex
X-Timestamp: 1789722409049{
"event_id": "6f1c2d3e-9a4b-4c1d-8e2f-0a1b2c3d4e5f",
"type": "round.settled",
"created_at": "2026-09-18T09:06:49.077Z",
"attempt": 1,
"data": {
"transaction_id": "4bb09882-3c85-4bc4-9d42-b4f93a1b4ec5",
"username": "player001",
"provider": "PG",
"game_uid": "1189baca156e1bbbecc3b26651a63565",
"game_name": "Mahjong Ways",
"round_id": "round-1",
"bet_amount": 1,
"win_amount": 2,
"currency": "USD",
"balance_after": 501,
"free_spin_bonus": null,
"status": "completed",
"created_at": "2026-09-18T09:06:49.077Z"
}
}Every delivery is a JSON POST signed exactly like the wallet callback: X-Signature = hex HMAC-SHA256 of the raw body with your callback secret. Reply with any 2xx status within 10 seconds. Non-2xx or timeouts are retried with backoff (1 min, 5 min, 15 min, 1 h, 3 h, 6 h, 12 h, 24 h — 8 attempts), after which the event is marked dead and can be redelivered from the dashboard. Deduplicate on event_id: the same event may be delivered more than once.
Providers
/api/v1/providersLists all active game suppliers.Each supplier has a code (used as provider in games and transactions), a display name, the currencies and languages it supports and the number of games currently in the catalog.
{
"success": true,
"data": [
{
"code": "PG",
"name": "PGSoft",
"currencies": ["USD", "EUR", "TRY", "BRL", "..."],
"languages": ["en", "tr", "pt", "es", "ru", "zh", "..."],
"game_count": 171
},
{ "code": "JL", "name": "JILI", "currencies": ["..."], "languages": ["..."], "game_count": 343 }
]
}The catalog is refreshed from the suppliers every 24 hours. Suppliers that are temporarily disabled are not returned.
Game List
/api/v1/gamesReturns the game catalog with filters and pagination.| Parameter | Type | Description |
|---|---|---|
provideroptional | string | Supplier code, e.g. PG, JL, PP, EVOASIA. |
categoryoptional | string | One or more canonical categories, comma separated: slots, live_casino, table, card, poker, fish, crash, arcade, lottery, bingo, keno, scratch, sports, esports, virtual_sports, other. |
subcategoryoptional | string | Game family when we recognise it, e.g. baccarat, roulette, mines. |
tagoptional | string | One or more tags, comma separated; all must match, e.g. megaways,jackpot. |
languageoptional | string | Only games available in this language. |
sortoptional | string | catalog (default), updated (incremental), popularity, new. |
cursoroptional | string | With sort=updated: stable cursor paging, use next_cursor from the previous response. |
include_removedoptional | boolean | true also returns games that left the catalogue (status: "removed"). |
fieldsoptional | string | full (default) or compact. |
typeoptional | string | Game type: Slot, Fish, Casino Live, Crash, Table, Card, Sports, Arcade, Bingo, Mini, … |
currencyoptional | string | Only games that support this currency. |
searchoptional | string | Case-insensitive match on the game name. |
pageoptional | number | Page number, default 1. |
limitoptional | number | Page size, default 500, max 5000. |
GET /api/v1/games?provider=PG&type=Slot¤cy=USD&search=mahjong&page=1&limit=500{
"success": true,
"total": 2,
"count": 2,
"page": 1,
"total_pages": 1,
"has_more": false,
"next_cursor": null,
"data": [
{
"game_uid": "1189baca156e1bbbecc3b26651a63565",
"game_name": "Mahjong Ways",
"slug": "mahjong-ways",
"provider": "PG", "provider_name": "PGSoft",
"category": "slots", "subcategory": "mahjong", "tags": ["asia"],
"game_type": "Slot",
"image": null,
"languages": ["en", "zh", "th", "..."],
"currencies": ["USD", "EUR", "..."],
"popularity": 87,
"status": "active",
"updated_at": "2026-09-23T11:58:42.117Z"
}
]
}image / logo URLs. Use Catalog & Assets to import the whole lobby in one call.Catalog & Assets
Everything you need to build a lobby under your own brand comes from one endpoint: providers with logos, categories with icons and every game with its thumbnail. Artwork is hosted on our domain and carries a cache-busting ?v= query; you can hot-link the URLs or mirror the files to your CDN.
/api/v1/catalogFull catalogue export (providers, categories, games) for the currencies you support.| Parameter | Type | Description |
|---|---|---|
currencyoptional | string | Only games playable in this currency. Omit for the full list. |
GET /api/v1/catalog?currency=USD{
"success": true,
"generated_at": "2026-09-23T12:00:00.000Z",
"next_since": "2026-09-23T11:58:42.117Z",
"catalog_checksum": "8f4b1c…",
"counts": { "providers": 148, "categories": 12, "games": 18422 },
"taxonomy": [ { "code": "slots", "labels": { "en": "Slots", "tr": "Slot", "…": "…" } } ],
"data": {
"providers": [
{ "code": "PG", "name": "PGSoft", "logo": "https://api.rivoapi.com/assets/providers/PG.png?v=9f8e7d6c",
"game_count": 171, "currencies": ["USD", "..."], "languages": ["en", "..."], "last_synced_at": "2026-09-23T03:00:00.000Z" }
],
"categories": [ { "code": "slots", "name": "Slots", "game_count": 12410, "provider_count": 96, "image": null, "subcategories": [] } ],
"games": [
{
"game_uid": "1189baca156e1bbbecc3b26651a63565",
"game_name": "Mahjong Ways",
"slug": "mahjong-ways",
"provider": "PG", "provider_name": "PGSoft",
"category": "slots", "subcategory": "mahjong", "tags": ["asia"],
"game_type": "Slot",
"image": "https://api.rivoapi.com/assets/games/1189baca156e1bbbecc3b26651a63565.webp?v=5e6f7a8b",
"images": { "default": "…", "square": null, "portrait": null, "long": null },
"languages": ["en", "zh", "th", "..."], "currencies": ["USD", "EUR", "..."],
"mobile": true, "is_new": false, "has_jackpot": false,
"rtp": null, "volatility": null, "reels": null, "paylines": null, "released_at": null,
"popularity": 87, "rounds_30d": 15321,
"classification": { "source": "feed", "upstream_type": "Slot" },
"status": "active", "updated_at": "2026-09-23T11:58:42.117Z"
}
]
}
}// 1. first import: everything at once
const res = await fetch('https://bo2.rivobit.com/api/v1/catalog', {
headers: { 'Authorization': 'Bearer YOUR_API_KEY' }
});
const full = await res.json();
for (const p of full.data.providers) await db.providers.upsert({ code: p.code, name: p.name, logo: p.logo });
for (const c of full.data.categories) await db.categories.upsert({ code: c.code, labels: c.labels, icon: c.image });
for (const g of full.data.games) await db.games.upsert({ uid: g.game_uid, name: g.game_name, provider: g.provider, category: g.category, tags: g.tags, image: g.image });
await db.state.set('catalog_since', full.next_since);
// 2. every few minutes: only what changed
let since = await db.state.get('catalog_since');
for (;;) {
const r = await fetch(`https://bo2.rivobit.com/api/v1/catalog/changes?since=${encodeURIComponent(since)}`, {
headers: { 'Authorization': 'Bearer YOUR_API_KEY' }
});
const delta = await r.json();
for (const g of delta.upserted) await db.games.upsert({ uid: g.game_uid, name: g.game_name, provider: g.provider, category: g.category, tags: g.tags, image: g.image });
for (const g of delta.removed) await db.games.delete(g.game_uid);
since = delta.next_since;
await db.state.set('catalog_since', since);
if (!delta.has_more) break;
}Categories
/api/v1/categoriesCategories are canonical codes you can rely on (slots, live_casino, table, card, poker, fish, crash, arcade, lottery, bingo, keno, scratch, sports, esports, virtual_sports, other), each with game/provider counts, an optional icon, recognised subcategories and display labels in six languages:
{
"success": true,
"count": 16,
"data": [
{
"code": "slots",
"name": "Slots",
"labels": { "en": "Slots", "tr": "Slot", "zh": "电子", "pt": "Slots", "es": "Tragamonedas", "ru": "Слоты" },
"game_count": 12410, "provider_count": 96,
"image": "https://api.rivoapi.com/assets/categorys/slots.svg?v=1a2b3c4d",
"subcategories": []
},
{
"code": "live_casino",
"name": "Live casino",
"labels": { "en": "Live casino", "tr": "Canlı casino", "zh": "真人视讯", "pt": "Cassino ao vivo", "es": "Casino en vivo", "ru": "Живое казино" },
"game_count": 1385, "provider_count": 22, "image": null,
"subcategories": [ { "code": "baccarat", "game_count": 412, "image": null }, { "code": "roulette", "game_count": 233, "image": null } ]
}
]
}Incremental import
/api/v1/catalog/changes?since=Import the full catalogue once, then only ask for what changed. Every response carries next_since; store it and send it back on the next call. upserted are games to insert or update by game_uid, removed are games to delete. Keep calling while has_more is true.
{
"success": true,
"since": "2026-09-22T12:00:00.000Z",
"next_since": "2026-09-23T03:00:11.204Z",
"has_more": false,
"counts": { "upserted": 2, "removed": 1 },
"upserted": [
{ "game_uid": "new_game_1", "game_name": "Fortune Tiger", "provider": "PG", "category": "slots", "tags": ["asia"], "status": "active", "updated_at": "2026-09-23T03:00:11.204Z" }
],
"removed": [
{ "game_uid": "old_game_9", "provider": "PG", "removed_at": "2026-09-23T03:00:09.880Z" }
]
}Checking for drift
/api/v1/catalog/manifestThe manifest gives a checksum per supplier. Compare it with your copy and re-pull only the suppliers that moved — it costs one small request per day.
{
"success": true,
"generated_at": "2026-09-23T12:00:00.000Z",
"provider_count": 148,
"game_count": 18422,
"catalog_checksum": "8f4b1c…",
"providers": [
{ "provider": "PG", "game_count": 171, "removed_count": 2, "last_change_at": "2026-09-23T03:00:00.000Z", "checksum": "5d2a…" }
]
}More endpoints
/api/v1/tags/api/v1/games/{game_uid}Tag vocabulary with counts, and a single game with every field we hold:
- Key everything on
game_uid— names, images and categories can change, the identifier never does. - Send
If-None-Matchwith the ETag of your last response: nothing changed →304, no body. - Games removed upstream arrive once in
removedwithstatus: "removed"; delete or hide them. rtp,volatility,reels,paylinesandreleased_atarenullunless the operator filled them in — the supplier feed does not contain them and we never invent a value.popularity(0-100) androunds_30dare measured on our platform over the last 30 days.- Read the category and tag vocabulary from the API instead of hard-coding it.
Import strategy
- Fetch
/api/v1/catalogonce a day (or after we announce new providers) and upsert bygame_uid/ providercode/ categorycode— identifiers are stable. - Treat
image/logoas nullable: artwork is being completed continuously; show a placeholder whennull. - Cache image files if you prefer to serve them yourself; the URL changes (
?v=) whenever we replace an asset. - Only providers enabled for your account are returned, so the export is already filtered for your lobby.
Transactions
/api/v1/transactionsYour settled rounds, as recorded from wallet callbacks.| Parameter | Type | Description |
|---|---|---|
usernameoptional | string | Filter by player. |
fromoptional | string | ISO 8601 start date/time (inclusive). |
tooptional | string | ISO 8601 end date/time (inclusive). |
pageoptional | number | Page number, default 1. |
limitoptional | number | Page size, default 100, max 1000. |
GET /api/v1/transactions?username=player001&from=2026-09-01T00:00:00Z&to=2026-09-30T23:59:59Z&page=1&limit=100{
"success": true,
"total": 1,
"page": 1,
"total_pages": 1,
"data": [
{
"transaction_id": "4bb09882-3c85-4bc4-9d42-b4f93a1b4ec5",
"username": "player001",
"provider": "PG",
"game_uid": "1189baca156e1bbbecc3b26651a63565",
"game_name": "Mahjong Ways",
"round_id": "round-1",
"bet_amount": 1,
"win_amount": 2,
"currency": "USD",
"balance_after": 501,
"status": "completed",
"created_at": "2026-09-18T09:06:49.077Z"
}
]
}status is completed for rounds your wallet accepted (error: 0) and failed for rounds you rejected. Use this endpoint to reconcile against your own wallet ledger; the two must match transaction by transaction.
GGR & invoicing
Your account operates in one currency (chosen at sign-up, immutable once activity starts); every bet, win, balance and fee is in that currency — there is no conversion. For each supplier you have a provider balance: player wins are credited to it, and later bets are covered from it first. Only the uncovered part of a bet is charged: uncovered × GGR rate is deducted from your prepaid balance immediately. Effective betting capacity per supplier = provider balance + balance ÷ rate. See Credit & capacity in the dashboard or GET /api/v1/credit. Demo-wallet rounds are never billed.
Error Codes
HTTP status codes
| Status | Meaning |
|---|---|
| 200 | OK |
| 400 | Bad request — missing/invalid parameter, disabled game, unsupported currency, callback URL not configured |
| 401 | Unauthorized — missing, invalid or inactive API key |
| 403 | Forbidden — merchant or player is blocked |
| 404 | Not found — unknown game_uid |
| 429 | Too many requests |
| 502 | Upstream provider error (see provider_code) or your wallet callback failed |
| 500 | Internal error |
Provider result codes
When a request is rejected by the game supplier, the response contains provider_code with one of the following upstream codes:
| Code | Message |
|---|---|
| 0 | Success |
| 10002 | Agency not exist |
| 10004 | payload error |
| 10005 | System error |
| 10008 | The game does not exist |
| 10011 | Player currencies do not match |
| 10012 | Player name already exists, please change player name |
| 10013 | Currency is not supported |
| 10014 | PlayerName is incorrect |
| 10015 | Player account, limited to a-z and 0-9 |
| 10016 | The account has been frozen. Please contact the administrator |
| 10017 | Manufacturer does not exist |
| 10018 | This line does not support the current currency |
| 10020 | The carrier does not configure a currency |
| 10022 | Incorrect parameters |
| 10023 | The player name must be at least 3 characters long |
| 10024 | Wallet mode does not match |
| 10025 | Insufficient wallet balance |
| 10026 | Transfer failed |
| 10027 | The transfer order already exists |
| 10028 | Start and end date cannot be empty |
| 10029 | The start and end dates must be the same day |
| 10030 | Too many requests, please try again later |
| 10031 | Only data within the last 60 days can be queried |
| 10032 | End date must be greater than start date |
| 10033 | home_url cannot contain ? |
| 10034 | System Scheduled Maintenance. |
Wallet callback error codes (your response)
| error | Meaning |
|---|---|
| 0 | Success |
| 1 | Insufficient funds — round rejected |
| 2 | Player not found or any other failure — round rejected |
Currencies & Languages
Languages (lang)
Not every supplier supports every language; each game exposes its own languages array. When a language is not supported the game falls back to English.
| Code | Language |
|---|---|
| en | English |
| zh | Chinese |
| th | Thai |
| vi | Vietnamese |
| id | Indonesian |
| ja | Japanese |
| ko | Korean |
| pt | Portuguese |
| es | Spanish |
| tr | Turkish |
| ru | Russian |
| de | German |
| fr | French |
| it | Italian |
| nl | Dutch |
| pl | Polish |
| ro | Romanian |
| sv | Swedish |
| fi | Finnish |
| no | Norwegian |
| da | Danish |
| my | Burmese |
| ur | Urdu |
| hi | Hindi |
| bn | Bengali |
| ta | Tamil |
| ms | Malay |
| km | Khmer |
| lo | Lao |
| ar | Arabic |
| fa | Persian |
| tl | Filipino |
| uk | Ukrainian |
Currencies
Most suppliers support the currencies below (ISO 4217); check the currencies array of a game before launching it. USDT is treated as a fiat-equivalent currency.
USD, EUR, GBP, CHF, JPY, CNY, AUD, CAD, NZD, USDT, TRY, RUB, UAH, PLN, CZK, HUF, RON, BGN, SEK, NOK, DKK, ISK, RSD, MKD, BAM, ALL, MDL, GEL, AMD, AZN, BYN, KZT, KGS, UZS, TJS, TMT, INR, PKR, BDT, LKR, NPR, BTN, MVR, IDR, MYR, SGD, THB, VND, PHP, KRW, HKD, TWD, MMK, KHR, LAK, MNT, BND, MOP, AFN, AED, SAR, QAR, KWD, BHD, OMR, JOD, LBP, IQD, IRR, ILS, SYP, YER, EGP, MAD, DZD, TND, LYD, NGN, GHS, KES, UGX, TZS, RWF, ETB, ZAR, ZMW, MWK, MZN, AOA, BWP, NAD, XOF, XAF, CDF, GNF, SLL, LRD, GMD, MUR, SCR, MGA, SDG, SOS, DJF, BIF, LSL, SZL, CVE, BRL, MXN, ARS, CLP, COP, PEN, UYU, PYG, BOB, VES, GTQ, HNL, NIO, CRC, PAB, DOP, CUP, JMD, TTD, HTG, BBD, BSD, BZD, GYD, SRD, XCD, AWG, ANG, KYD, BMD, FJD, PGK, WST, TOP, VUV, SBD, XPFTesting
- The dashboard Games → Launch button opens any game with an internal demo wallet (1,000 USD) so you can try games before your callback is ready. Demo rounds are marked
DEMOin Transactions and never invoiced. - Use a public HTTPS tunnel (ngrok, cloudflared) to expose a local callback during development.
- Every wallet request we make, its signature, your reply and the latency are shown in Callback Log.
Support
Integration support is available 24/7 through your account manager. Please include the transaction_id, username and timestamps when reporting an issue.
