Rivo API

Rivo API Documentation

Integrate 18,000+ games from 148 suppliers through a single seamless-wallet API.

Getting Started

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

  1. 1Your backend calls POST /api/v1/game/launch with the player's username and a game_uid.
  2. 2We ask your wallet callback for the player's current balance (action: "balance").
  3. 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).
  4. 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.
  5. 5All rounds are stored and available in the dashboard and through GET /api/v1/transactions.
Amounts are always in main currency units with two decimals (e.g. "1.50" = 1.50 USD), never in cents.

Base URL

text
https://bo2.rivobit.com

All endpoints are served over HTTPS and accept/return JSON (Content-Type: application/json).

Getting Started

Quick Start

  1. 1Create an API key in the dashboard under API Keys. Keep it secret — it identifies your merchant account.
  2. 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.
  3. 3Fetch the catalog: GET /api/v1/providers and GET /api/v1/games give you supplier codes and game_uids to build your lobby.
  4. 4Launch a game: POST /api/v1/game/launch with a username and game_uid. Redirect the player to the returned URL.
  5. 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.
You do not need to create players in advance: the first game/launch for a new username registers the player automatically. player/create is optional and mainly useful for validation.
Getting Started

Authentication & Conventions

Every request to /api/v1/* must carry your API key as a Bearer token:

http
Authorization: Bearer <YOUR_API_KEY>
Content-Type: application/json

API 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:

json
{
  "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):

json
{
  "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.

Players

Create Player

POST/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.

ParameterTypeDescription
username
required
stringYour identifier for the player. 1–16 chars, a-z A-Z 0-9 _.
currency
optional
stringISO 4217 code. Defaults to your account currency. Cannot be changed later.
Request
POST /api/v1/player/create
{
  "username": "player001",
  "currency": "USD"          // optional, defaults to your account currency
}
Response
{
  "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
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"}'
Games

Launch Game

POST/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.

ParameterTypeDescription
username
required
stringPlayer username. Created automatically if unknown.
game_uid
required
stringGame identifier from GET /api/v1/games.
currency
optional
stringPlayer currency. Defaults to your account currency; must match the player's currency and be supported by the game.
lang
optional
stringGame UI language, e.g. en, tr, pt, zh. Default en. See Languages.
home_url
optional
stringURL the player returns to when closing the game. Must not contain `?`.
platform
optional
stringweb (default) or h5 for the mobile build of the game.
Request
POST /api/v1/game/launch
{
  "username": "player001",
  "game_uid": "1189baca156e1bbbecc3b26651a63565",
  "currency": "USD",
  "lang": "en",
  "home_url": "https://www.yourcasino.com/lobby",
  "platform": "web"
}
Response
{
  "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:

html
<iframe
  src="https://play.example-gamehost.com/launch?session=0cb1394b-…"
  allow="fullscreen; autoplay"
  style="width:100%;height:100%;border:0"
></iframe>
cURL
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

HTTPerrorCause
400Callback URL is not configuredSet it in Settings first.
400Game does not support currency XCheck currencies of the game.
400Player currency is X, cannot launch with YA player keeps the currency of its first launch.
400home_url cannot contain ?Use a path without a query string.
404Game not foundUnknown or removed game_uid. Refresh your catalog.
502Merchant callback failed / timed outYour wallet did not answer within 10 s or returned an invalid body.
502provider_code 10030Supplier rate limit, retry after a short delay.
Launch URLs are single-use sessions. Request a fresh URL every time the player opens a game; do not cache them.
Wallet Callback

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

http
POST https://www.yourcasino.com/api/rivo/wallet
Content-Type: application/json
X-Signature: 3f1c…e9a2          // HMAC-SHA256(rawBody, callbackSecret), hex
X-Timestamp: 1789722409049
ParameterTypeDescription
action
required
stringbalance — return the balance only. bet — settle one round (bet + win) and return the new balance.
username
required
stringThe username you used at launch.
member_account
required
stringInternal account name at the supplier (for logging).
currency
required
stringPlayer currency, e.g. USD.
timestamp
required
numberUnix time in milliseconds when we sent the request.
transaction_id
optional
stringbet only. Unique UUID of the round settlement. Idempotency key.
round_id
optional
stringbet only. Supplier round id (may be empty for some suppliers).
game_uid
optional
stringbet only. The game being played.
provider
optional
stringbet only. Supplier code, e.g. PG.
bet_amount
optional
stringbet only. Stake, 2 decimals. Negative = refund of an earlier stake.
win_amount
optional
stringbet only. Payout, 2 decimals. Negative = reversal of an earlier win.
data
optional
object|nullbet only. Extra event detail for sports suppliers, otherwise null.
free_spin_bonus
optional
string|nullbet only. bonus_code of the free-round campaign when this round was a free spin, otherwise absent/null.
action: balance
{
  "action": "balance",
  "username": "player001",
  "member_account": "ab12cd_66f1a2_player001",
  "currency": "USD",
  "timestamp": 1789722408767
}
action: bet
{
  "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:

json
{
  "error": 0,
  "balance": 501.00
}
errorMeaningWhat we do
0OK — the operation was appliedThe round is confirmed to the supplier with your balance.
1Insufficient fundsThe round is rejected; the supplier shows an "insufficient balance" message.
2Player not found / other errorThe round is rejected.
Rejecting a bet
{
  "error": 1,          // insufficient funds -> the round is rejected
  "balance": 0.50
}
A 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.

Node.js
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 — return error: 0 and the balance you had after the original processing.
  • Never rely on round_id for idempotency: some suppliers send several bet messages per round (e.g. free spins, bonus rounds), each with its own transaction_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

Node.js (Express)
// 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-Signature over the raw body.
  • Handles balance and bet; unknown actions → error: 2.
  • Applies bet and win atomically with a row lock / conditional update.
  • Persists transaction_id and 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

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.

POST/api/v1/freespins/createGrants a free-round campaign to a player.
ParameterTypeDescription
username
required
stringPlayer username. Created automatically if unknown.
game_uid
required
stringGame that supports free rounds (slots from most suppliers).
rounds
required
numberNumber of free rounds, 1–1000.
total_bet
required
stringTotal 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_code
required
stringYour identifier for the campaign, unique per merchant (A-Z a-z 0-9 _ -, max 64). Echoed as free_spin_bonus in wallet callbacks.
currency
optional
stringPlayer currency. Defaults to your account currency.
start_at
optional
stringISO 8601 or unix timestamp. Defaults to now.
expires_at
optional
stringISO 8601 or unix timestamp. Defaults to start + 7 days, max 90 days.
Request
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"
}
Response
{
  "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
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 (free spin)
{
  "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
}
Do not deduct anything for free-spin rounds — credit win_amount only. Free-spin wins count towards GGR like any other win, so the campaign cost is reflected in your invoice.

Listing campaigns

GET/api/v1/freespinsReturns your campaigns, newest first. Filter by `username` or `bonus_code`.
http
GET /api/v1/freespins?username=player001&bonus_code=WELCOME10&page=1&limit=100

Common errors

HTTPerror
400bonus_code already usedUse a unique code per campaign.
400Game does not support currency XCheck the game's currencies.
404Game not foundUnknown game_uid.
502provider_code 10008 / 10022The game does not support free rounds or the bet level is invalid — adjust total_bet.
Webhooks

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 as GET /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

http
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
round.settled
{
  "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.

Webhooks are informational. Never use them to move player funds — the wallet callback is the only place where balances change.
Games

Providers

GET/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.

Response
{
  "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.

Games

Game List

GET/api/v1/gamesReturns the game catalog with filters and pagination.
ParameterTypeDescription
provider
optional
stringSupplier code, e.g. PG, JL, PP, EVOASIA.
category
optional
stringOne or more canonical categories, comma separated: slots, live_casino, table, card, poker, fish, crash, arcade, lottery, bingo, keno, scratch, sports, esports, virtual_sports, other.
subcategory
optional
stringGame family when we recognise it, e.g. baccarat, roulette, mines.
tag
optional
stringOne or more tags, comma separated; all must match, e.g. megaways,jackpot.
language
optional
stringOnly games available in this language.
sort
optional
stringcatalog (default), updated (incremental), popularity, new.
cursor
optional
stringWith sort=updated: stable cursor paging, use next_cursor from the previous response.
include_removed
optional
booleantrue also returns games that left the catalogue (status: "removed").
fields
optional
stringfull (default) or compact.
type
optional
stringGame type: Slot, Fish, Casino Live, Crash, Table, Card, Sports, Arcade, Bingo, Mini, …
currency
optional
stringOnly games that support this currency.
search
optional
stringCase-insensitive match on the game name.
page
optional
numberPage number, default 1.
limit
optional
numberPage size, default 500, max 5000.
Request
GET /api/v1/games?provider=PG&type=Slot&currency=USD&search=mahjong&page=1&limit=500
Response
{
  "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"
    }
  ]
}
Thumbnails, provider logos and category icons are served from our own domain and returned as absolute image / logo URLs. Use Catalog & Assets to import the whole lobby in one call.
Games

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.

GET/api/v1/catalogFull catalogue export (providers, categories, games) for the currencies you support.
ParameterTypeDescription
currency
optional
stringOnly games playable in this currency. Omit for the full list.
http
GET /api/v1/catalog?currency=USD
json
{
  "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"
      }
    ]
  }
}
Node.js
// 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

GET/api/v1/categories

Categories 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:

json
{
  "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

GET/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.

json
{
  "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

GET/api/v1/catalog/manifest

The 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.

json
{
  "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

GET/api/v1/tags
GET/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-Match with the ETag of your last response: nothing changed → 304, no body.
  • Games removed upstream arrive once in removed with status: "removed"; delete or hide them.
  • rtp, volatility, reels, paylines and released_at are null unless the operator filled them in — the supplier feed does not contain them and we never invent a value.
  • popularity (0-100) and rounds_30d are 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/catalog once a day (or after we announce new providers) and upsert by game_uid / provider code / category code — identifiers are stable.
  • Treat image / logo as nullable: artwork is being completed continuously; show a placeholder when null.
  • 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.
Provider and category names are supplied as display names; you may translate or rename them freely in your frontend.
Reports

Transactions

GET/api/v1/transactionsYour settled rounds, as recorded from wallet callbacks.
ParameterTypeDescription
username
optional
stringFilter by player.
from
optional
stringISO 8601 start date/time (inclusive).
to
optional
stringISO 8601 end date/time (inclusive).
page
optional
numberPage number, default 1.
limit
optional
numberPage size, default 100, max 1000.
Request
GET /api/v1/transactions?username=player001&from=2026-09-01T00:00:00Z&to=2026-09-30T23:59:59Z&page=1&limit=100
Response
{
  "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.

Reference

Error Codes

HTTP status codes

StatusMeaning
200OK
400Bad request — missing/invalid parameter, disabled game, unsupported currency, callback URL not configured
401Unauthorized — missing, invalid or inactive API key
403Forbidden — merchant or player is blocked
404Not found — unknown game_uid
429Too many requests
502Upstream provider error (see provider_code) or your wallet callback failed
500Internal 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:

CodeMessage
0Success
10002Agency not exist
10004payload error
10005System error
10008The game does not exist
10011Player currencies do not match
10012Player name already exists, please change player name
10013Currency is not supported
10014PlayerName is incorrect
10015Player account, limited to a-z and 0-9
10016The account has been frozen. Please contact the administrator
10017Manufacturer does not exist
10018This line does not support the current currency
10020The carrier does not configure a currency
10022Incorrect parameters
10023The player name must be at least 3 characters long
10024Wallet mode does not match
10025Insufficient wallet balance
10026Transfer failed
10027The transfer order already exists
10028Start and end date cannot be empty
10029The start and end dates must be the same day
10030Too many requests, please try again later
10031Only data within the last 60 days can be queried
10032End date must be greater than start date
10033home_url cannot contain ?
10034System Scheduled Maintenance.

Wallet callback error codes (your response)

errorMeaning
0Success
1Insufficient funds — round rejected
2Player not found or any other failure — round rejected
Reference

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.

CodeLanguage
enEnglish
zhChinese
thThai
viVietnamese
idIndonesian
jaJapanese
koKorean
ptPortuguese
esSpanish
trTurkish
ruRussian
deGerman
frFrench
itItalian
nlDutch
plPolish
roRomanian
svSwedish
fiFinnish
noNorwegian
daDanish
myBurmese
urUrdu
hiHindi
bnBengali
taTamil
msMalay
kmKhmer
loLao
arArabic
faPersian
tlFilipino
ukUkrainian

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.

text
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, XPF

Testing

  • 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 DEMO in 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.