API reference

All bodies are JSON (Content-Type: application/json, UTF-8). Field names are camelCase. Production traffic in both directions uses HTTPS only (TLS 1.2+). For the signature headers and algorithm, see Signing.

Direction Endpoint Auth
Operator → ZooZ POST /api/v1/operator/sessions Signed with the operator key. Response signed
Operator → ZooZ GET /api/v1/games Public
Game client → ZooZ POST /api/v1/sessions/exchange Launch code (for reference only, you never call it)
ZooZ → Operator POST {walletUrl}/balance Signed with the wallet key. Your response signed
ZooZ → Operator POST {walletUrl}/debit Same
ZooZ → Operator POST {walletUrl}/credit Same
ZooZ → Operator POST {walletUrl}/rollback Same

#Common types

#Amounts

Property Rule
JSON type number in the currency's major unit: 1.5 = EUR 1.50
Precision Always a whole number of minor units (EUR: cents). Every amount ZooZ sends has at most the currency's decimals of significant precision
Fraction digits on the wire Always the currency's decimal places (EUR: 1.00, 0.20, 2.50), also on retries. Parse amounts as decimals (decimal, BigDecimal, Decimal, a decimal library in JavaScript) and compare them numerically, never as floats
Range debit: > 0. credit: ≥ 0 (0 closes a lost round or bet). rollback: the debit amount (> 0)
Your balance A JSON number in the major unit. Sending it with the currency's decimals (for example 998.00) is recommended. ZooZ truncates anything beyond the currency's decimals

#Identifiers

Field Format Max length to store
transactionId Slots, mines, dice, plinko, crossing: {roundId}-bet, {roundId}-win. Crash: crash-{betId}-bet, crash-{betId}-win (betId is 32 lowercase hex characters). Globally unique 128
roundId Slots, mines, dice, plinko, crossing: 32 lowercase hex characters (a UUID without dashes). Crash: crash-{gameId}-{roundNumber}, shared by all bets in that crash round 128
gameId Catalog id, for example safari-king, sky-parrot, jungle-mines, monkey-dice, jungle-plinko, jungle-jewels, croc-crossing, tropic-treats 64
sessionId Your session id, sent back unchanged. 1–128 characters 128
playerId Your player id (a JSON string), sent back unchanged. 1–128 characters 128
currency ISO 4217, upper case 3

Treat all ids as opaque strings. The formats above are informative. Do not parse them.


#Operator → ZooZ

#POST /api/v1/operator/sessions

Opens a real-money game session for one of your players and returns a one-time game URL.

Request headers: Content-Type: application/json plus the five request signature headers (X-ZooZ-Operator, X-ZooZ-Key-Id, X-ZooZ-Timestamp, X-ZooZ-Nonce, X-ZooZ-Signature), signed with an operator key. The path to sign is /api/v1/operator/sessions, and the query is empty.

Request body:

JSON
{
  "sessionId": "op-sess-7c1e",
  "playerId": "12345",
  "nickname": "ahmet",
  "currency": "EUR",
  "gameId": "safari-king",
  "language": "en",
  "returnUrl": "https://casino.example/lobby"
}
Field Type Required Rules
sessionId string yes Your own game-session id. 1–128 characters. ZooZ sends it back in every wallet call for this game session
playerId string yes Your player id, 1–128 characters. Send a JSON string, even if the id is numeric
nickname string no Up to 64 characters. Stored with the session, reserved for display: not shown yet. Crash feeds show a masked playerId
currency string yes ISO 4217. Must be enabled for your operator account (case-insensitive)
gameId string yes A game id from GET /api/v1/games that is enabled for you (case-insensitive)
language string no Preferred UI language: en, de, pt-BR (language with an optional region; pt_br is accepted too). If it is one of the languages enabled for you it is used; otherwise the same language without region (pt-BR → pt), then your default language. Never an error. The game client receives the result (in-game translations are rolled out game by game)
returnUrl string no Your lobby URL. Accepted and reserved: not applied yet

Unknown fields are ignored.

Response 200 (signed with an operator key, see response signature):

JSON
{
  "gameUrl": "https://games.example/safari-king/?launch=3f9c…e1",
  "launchExpiresAt": "2026-09-24T12:01:00.5160000+00:00"
}
Field Type Meaning
gameUrl string (absolute URL) Load it in an iframe or redirect to it within 60 seconds. It works once
launchExpiresAt string (ISO 8601 date-time with offset) When the launch code expires (60 s after creation)

Errors: HTTP status plus ProblemDetails. Every response except the authentication 401 is signed.

HTTP code When
401 INVALID_SIGNATURE Unknown operator or key id, bad signature, missing nonce, body larger than 64 KiB, or source IP not allowed
401 STALE_TIMESTAMP X-ZooZ-Timestamp is missing or more than 300 s away from ZooZ's clock
401 REPLAY The nonce was already used
403 OPERATOR_DISABLED Your operator account is disabled
400 INVALID_REQUEST sessionId or playerId is missing or too long, or nickname is too long
404 GAME_NOT_FOUND Unknown or missing gameId (or the game has no web client configured)
403 GAME_NOT_ALLOWED The game exists but is not enabled for you
400 UNSUPPORTED_CURRENCY Unknown or missing currency, or one not enabled for you
400 (see note) Body is not valid JSON, or a field has the wrong JSON type (for example a numeric playerId)
500 INTERNAL_ERROR Unexpected server error. Safe to retry with a new nonce

Note: a body that cannot be parsed is rejected with HTTP 400. Depending on the server configuration, the body is either ProblemDetails with INVALID_REQUEST or empty. Treat a 400 without a code as INVALID_REQUEST.

The 401 body is not ProblemDetails. It is exactly {"status":"error","code":"…"}.

#GET /api/v1/games

Public, unsigned. It lists the games in the ZooZ catalog. Games that are not enabled for your account are also listed, and launching them returns GAME_NOT_ALLOWED.

Response 200:

JSON
[
  { "id": "safari-king", "type": "slot", "name": "Safari King" },
  { "id": "sky-parrot", "type": "crash", "name": "Sky Parrot" },
  { "id": "jungle-mines", "type": "mines", "name": "Jungle Mines" },
  { "id": "monkey-dice", "type": "dice", "name": "Monkey Dice" },
  { "id": "jungle-plinko", "type": "plinko", "name": "Jungle Plinko" },
  { "id": "jungle-jewels", "type": "slot", "name": "Jungle Jewels" },
  { "id": "croc-crossing", "type": "crossing", "name": "Croc Crossing" },
  { "id": "tropic-treats", "type": "slot", "name": "Tropic Treats" }
]
Field Type Values
id string Use it as gameId
type string slot, crash, mines, dice
name string Display name

#POST /api/v1/operator/conformance/runs

Signed with your operator keys, like /operator/sessions. ZooZ runs its wallet conformance test against your configured wallet URL and returns the report. About 25 signed calls are made: correct ones, and deliberately wrong ones that your wallet must refuse (wrong or missing signature, unknown key id, timestamps 10 minutes off, body changed after signing, replayed nonce, a session of another player or currency, repeated debits and credits, rollbacks before and after a debit, a debit larger than the balance, a negative amount).

The test moves real money on the session you give (debits, credits, rollbacks). A conformant wallet ends where it started. Use a staging test player with at least 3× amount. At most one run at a time and 10 runs per hour.

Request:

JSON
{ "sessionId": "your-test-session", "playerId": "test-player-1", "currency": "EUR", "amount": 1.00 }
Field Type Notes
sessionId string A live session of your test player, as you would send it to /operator/sessions
playerId string The player of that session
currency string The session's currency
amount number Optional, default 1.00, max 100

Response 200 (signed): passed, warnings, failed, skipped, startBalance, endBalance and checks, one entry per check:

JSON
{
  "id": "debit.idempotent",
  "category": "Debit",
  "title": "Same debit again (new nonce)",
  "expected": "status ok, same operatorTransactionId, balance still 49.00",
  "outcome": "pass",
  "actual": "HTTP 200, status ok, balance 49.00, signed",
  "httpStatus": 200,
  "elapsedMs": 38,
  "request": "POST /wallet/debit\n{…}",
  "response": "HTTP 200\n{…}"
}

outcome is pass, warn (accepted by ZooZ, but not what the contract asks for, for example the wrong error code), fail (must be fixed before go-live) or skipped. Go live only with failed: 0.


#Game client → ZooZ (for reference)

#POST /api/v1/sessions/exchange

Called by the game client with the launch parameter of the gameUrl. You never call it. It is described here so that you know why a gameUrl works only once.

JSON
{ "launch": "3f9c…e1" }

Response 200: { "token": "…", "expiresAt": "2026-09-24T20:00:00+00:00" }. The token is sent as Authorization: Bearer on the game endpoints. It expires after 8 hours of inactivity by default.

HTTP code When
401 SESSION_NOT_FOUND The code is unknown, already used, or older than 60 s

The code is consumed atomically. If two tabs race, exactly one of them wins. The game endpoints behind the token (/api/v1/game/*, /api/v1/crash/*) are internal to the game clients and not part of this contract.


#ZooZ → Operator: seamless wallet

ZooZ calls POST {walletUrl}/{action}. For example, with the wallet URL https://api.casino.example/zooz/wallet, a debit goes to https://api.casino.example/zooz/wallet/debit, and the signed path is /zooz/wallet/debit.

Request headers (all four actions):

Header Value
Content-Type application/json
User-Agent ZooZ-RGS/1.0 (+https://zoozgames.com)
X-ZooZ-Operator Your operator id
X-ZooZ-Key-Id The ZooZ wallet key that signed the call
X-ZooZ-Timestamp Unix seconds
X-ZooZ-Nonce UUIDv4, new for every call and every retry
X-ZooZ-Signature v1= + hex HMAC-SHA256 (Signing)

Your response (all four actions): HTTP 200 with these headers, signed with a wallet key:

Header Value
Content-Type application/json
X-ZooZ-Key-Id Id of the wallet key that you signed with (normally the request's key id)
X-ZooZ-Timestamp Unix seconds, now
X-ZooZ-Signature v1= + hex HMAC-SHA256 over v1\nRESPONSE\n{requestNonce}\n{timestamp}\n{hex(sha256(body))}

Success body:

JSON
{ "status": "ok", "balance": 998.00, "operatorTransactionId": "tx-9876" }

Business error body:

JSON
{ "status": "error", "code": "INSUFFICIENT_FUNDS", "balance": 0.50 }
Field Type Required Meaning
status string yes ok or error
balance number yes on ok, recommended on error The player's balance after this call, in the major unit. For a replayed call, the current balance
code string on error One of the wallet codes
operatorTransactionId string recommended on ok Your id for the ledger entry. It must be the same on replays. ZooZ currently does not store it, but it helps reconciliation

Any non-200 status, a missing or invalid signature, a response timestamp outside ±300 s or a malformed body means outcome unknown. See the integration guide. For request authentication failures, answer 401 with {"status":"error","code":"INVALID_SIGNATURE"} (or STALE_TIMESTAMP / REPLAY).

#POST {walletUrl}/balance

When: when a game is opened, and whenever ZooZ needs a fresh balance (for example after a round whose credit is still pending).

JSON
{ "sessionId": "op-sess-7c1e", "playerId": "12345", "currency": "EUR" }
Field Type Meaning
sessionId string Your session id from the launch
playerId string Your player id
currency string The session currency

Respond ok with balance. For an unknown session, a mismatched player or currency, or an expired session, respond error with SESSION_INVALID. ZooZ then tells the player that the wallet is unavailable. Nothing is retried automatically.

#POST {walletUrl}/debit

When: a bet is placed (one per round, or one per crash bet). No outcome exists yet.

JSON
{
  "transactionId": "01997a3c5e2b7c1d9f00aa11bb22cc33-bet",
  "roundId": "01997a3c5e2b7c1d9f00aa11bb22cc33",
  "gameId": "safari-king",
  "sessionId": "op-sess-7c1e",
  "playerId": "12345",
  "currency": "EUR",
  "amount": 1.00
}
Field Type Meaning
transactionId string Idempotency key of this debit
roundId string Round (crash: the shared crash round)
gameId string Game id
sessionId string Your session id. It must be a live session
playerId string Must match the session
currency string Must match the session
amount number The bet, > 0. For a bonus buy (a slot feature bought directly, e.g. Jungle Jewels at 100× the bet) it is the price, bet × cost; for an ante bet spin (e.g. Tropic Treats, 1.25× the bet for a higher free spins chance) it is bet × 1.25. It never exceeds your max stake (if you set one) for the currency

Take amount atomically if the balance allows it. Otherwise answer INSUFFICIENT_FUNDS and move nothing. If this transactionId was already rolled back (or tombstoned), answer TRANSACTION_ROLLED_BACK. ZooZ never retries a debit. A second debit with the same id is a duplicate delivery, so answer it with the first response.

#POST {walletUrl}/credit

When: the round (or crash bet) is settled. It is sent for losses too, with amount: 0. It may arrive long after the session expired, and it may be retried.

Body: the same fields as the debit, with transactionId ending in -win and amount ≥ 0 (the total win of the round, including all free spins).

Add amount atomically and answer ok. Accept an expired session (the session must exist, and the player and currency must match). If you have a debit for this round, it must belong to the same player (in crash: this player's debit in that round). Any error makes ZooZ retry later, so do not refuse a valid credit.

#POST {walletUrl}/rollback

When: a debit must be reversed, because its outcome was unknown, the game failed before an outcome existed, or (in crash) the bet was cancelled or voided.

JSON
{
  "transactionId": "01997a3c5e2b7c1d9f00aa11bb22cc33-bet",
  "roundId": "01997a3c5e2b7c1d9f00aa11bb22cc33",
  "gameId": "safari-king",
  "sessionId": "op-sess-7c1e",
  "playerId": "12345",
  "currency": "EUR",
  "amount": 1.00
}

transactionId is the id of the debit being reversed. amount is the debit amount. When you refund, use the amount you stored for the debit. Accept an expired session.

Situation Answer
Debit found and not yet refunded Refund it and answer ok
Already rolled back The first answer (ok)
Debit was refused earlier ok, nothing to refund
Debit never arrived Store a tombstone and answer ok (TRANSACTION_NOT_FOUND is also accepted as success)

#Wallet error codes

code Meaning Typical call
INSUFFICIENT_FUNDS Balance is lower than the bet debit
PLAYER_NOT_FOUND Player does not exist debit, balance
PLAYER_BLOCKED Player may not play (self-exclusion, compliance) debit, balance
SESSION_INVALID sessionId is unknown, has expired (debit and balance only), or does not match playerId or currency any
WRONG_CURRENCY Currency not allowed for this player debit
LIMIT_EXCEEDED Responsible-gaming or bet limit reached debit
TRANSACTION_ROLLED_BACK This debit was already rolled back or tombstoned debit
TRANSACTION_NOT_FOUND (rollback only) the original debit never arrived. ZooZ treats this as success rollback

How ZooZ reacts to each code on each call is described in the integration guide.


#Errors (ProblemDetails)

ZooZ endpoints (except the authentication 401) return errors as RFC 7807 ProblemDetails (Content-Type: application/problem+json) with a stable code extension. Build your logic on code and the HTTP status. title repeats the code, and detail is human-readable and may change.

JSON
{
  "type": "https://tools.ietf.org/html/rfc9110#section-15.5.1",
  "title": "UNSUPPORTED_CURRENCY",
  "status": 400,
  "detail": "Currency 'TRY' is not enabled for this operator.",
  "code": "UNSUPPORTED_CURRENCY",
  "traceId": "00-4bf92f3577b34da6a3ce929d0e0e4736-00f067aa0ba902b7-01"
}

Quote traceId (when present) in support requests.

#Code catalogue

code HTTP Seen by Meaning
INVALID_SIGNATURE 401 Operator Authentication failed (key, signature, nonce, body size or IP). Plain JSON body, not ProblemDetails
STALE_TIMESTAMP 401 Operator Timestamp outside ±300 s. Plain JSON body
REPLAY 401 Operator Nonce already used. Plain JSON body
OPERATOR_DISABLED 403 Operator, game client Account disabled. Open sessions can finish a running round, but no new bet is taken
GAME_NOT_ALLOWED 403 Operator, game client Game not enabled for you (also for new bets in a session opened before the game was disabled)
GAME_NOT_FOUND 404 Operator, game client Unknown game
UNSUPPORTED_CURRENCY 400 Operator, game client Currency unknown or not enabled
INVALID_REQUEST 400 Operator, game client Missing or invalid field
INTERNAL_ERROR 500 Everyone Unexpected error
SESSION_NOT_FOUND 401 Game client Launch code or session token unknown, used or expired
SESSION_EXPIRED 401 Game client Game session expired. Open the game again with a new launch
INVALID_BET 400 Game client Bet not allowed for the currency, outside your limits (min/max bet, max win per round, max stake for a bonus buy), or changed mid-round. Checked before your wallet is called
INVALID_ACTION 400 Game client Action not valid in the current game state
INSUFFICIENT_FUNDS 402 Game client Your wallet answered INSUFFICIENT_FUNDS
CONCURRENT_REQUEST 409 Game client Another request for the same player and game is in progress
WALLET_UNAVAILABLE 503 Game client Your wallet was unreachable or refused the bet. No bet was placed
SETTLEMENT_PENDING 503 Game client A previous win is still being paid (credit retry pending)
BETTING_CLOSED 409 Game client (crash) Betting phase is over. A debited bet is rolled back
BET_REJECTED 400 Game client (crash) The table refused the bet. A debited bet is rolled back
CASHOUT_REJECTED 409 Game client (crash) Too late: the round already crashed
TABLE_UNAVAILABLE 503 Game client (crash) Crash table not running. A debited bet is rolled back

The game client shows these codes to the player. They are listed here so that your support team can match player reports to wallet traffic. For example, a spike of WALLET_UNAVAILABLE usually means your wallet is slow or failing signature checks.