Casino API · Overview

Casino API · v1

Casino API

The Casino API gives you the providers and games you can offer, and a link that opens a game for one of your players. The player's money is kept in one of two ways. With the transfer wallet, HZURA keeps a casino balance for each player and you move money in and out. With the seamless wallet, the money stays in your own wallet and HZURA asks your wallet for every bet and win.

Overview

You never send an account id: your API key already says who you are. Send only the documented JSON fields; any other field is refused. Launch and wallet requests can be at most 8 KiB.

This API does not create bonuses, jackpots, free spins or promotions. When a game gives a free round or a prize, it is paid like any other bet or win. There is no route to look up a single transaction later.

MethodPathWhat it does
GET/api/v1/casino/providersThe providers you can offer, with a logo for each.
GET/api/v1/casino/gamesOne page of the games you can offer, most popular first.
POST/api/v1/casino/games/{gameId}/launchA link that opens one game for one player.
POST/api/v1/casino/walletTransfer wallet only: check, add, take out or undo money.

Providers

  • API key required
  • Not rate limited

Endpoint

GET/api/v1/casino/providers

The providers you can offer.

Use this before the games list when you want a provider menu. Each id is the provider filter on the games list, written exactly the same, including upper and lower case.

You only see providers that are available to your account. To add or remove one, ask HZURA. No providers is still a success: data is []. The list is complete in one response. There is no page number.

image is a HZURA URL for the provider logo. Show that URL as returned. It is empty until the logo has been stored. Do not add query parameters to it.

Authentication

Your API key, sent from a server on your IP allow-list. Casino always needs a key. See Authentication.

Request

This endpoint does not take parameters or a request body.

Response

200Every provider you can offer.

JSON
{
  "data": [
    {
      "id": "provider-1",
      "name": "Provider",
      "image": "https://pub.example.r2.dev/casino/providers/provider-1/0123456789abcdef.avif"
    }
  ]
}

Example request

curl
curl -sS "https://api.hzura.com/api/v1/casino/providers" \
  -H "Authorization: Bearer YOUR_API_KEY"

Errors

HTTPerrorWhen
403ip_not_allowedAccess refused: check your key and server IP address.
403casino_access_disabledCasino is switched off for your account.
503security_config_unavailableAccess could not be checked right now. Try again shortly.
503casino_catalog_unavailableThe provider list is not available right now. Try again shortly.
503redis_unavailableTemporarily unavailable, or took too long. Try again shortly.

Games

  • API key required
  • Not rate limited

Endpoint

GET/api/v1/casino/games

The games you can offer, most popular first, one page at a time.

You only see games that are live and made available to your account, most popular first. popularity_rank is that place: 0 is the most popular, and null means it has not been ranked yet. Filters and pages keep that order. To add or remove a game provider for your account, ask HZURA.

No games is still a success: data is [] and total is 0. A page past the end also gives an empty data, with the real total.

images is a list of { "type", "url" }. Each url is a HZURA URL. Use it as returned. supported_devices, client_types, supported_currencies, languages, volatility, features and themes describe the game. Some can be empty or missing, so treat them as optional.

Authentication

Your API key, sent from a server on your IP allow-list. Casino always needs a key. See Authentication.

Request

Parameters

FieldTypeInRequiredDescription
providerstringqueryNoOnly games from this provider. Use an id from GET /api/v1/casino/providers, written exactly the same, including upper and lower case. Anything else is invalid_provider. At most 200 characters.
categorystringqueryNoOnly games in this category. Use an id from categories, written exactly the same. Anything else is invalid_category. At most 200 characters.
searchstringqueryNoText to find in the game name or description. Upper and lower case do not matter. At most 100 characters.
pageintegerqueryNoPage number, 1 or more. Default 1.
limitintegerqueryNoGames per page, from 1 to 100. Default 50.
currencystringqueryNoOnly games that support this currency. It must be one of your account's currencies; upper or lower case both work. At most 16 characters. Without it, your account's main currency is used, if one is set.
languagestringqueryNoOnly games available in this language, such as en-US (en_US works too). A two-letter code like en does not match. At most 32 characters.
devicestringqueryNoOnly games for this device, matched against supported_devices or client_types, such as desktop or mobile. Upper and lower case do not matter. At most 64 characters.

Response

200One page of games.

JSON
{
  "data": [
    {
      "id": "DS-example",
      "name": "Example",
      "provider_id": "provider-1",
      "provider_name": "Provider",
      "category": "CASINO/SLOT",
      "categories": [{ "id": "SLOT", "name": "Slot" }],
      "description": null,
      "images": [],
      "supported_devices": ["desktop", "mobile"],
      "client_types": ["HTML5"],
      "supported_currencies": [{ "id": "EUR", "name": "Euro" }],
      "languages": [{ "id": "en_US", "name": "English" }],
      "demo_support": true,
      "free_round_support": false,
      "rtp": "96.5",
      "volatility": null,
      "features": [],
      "themes": [],
      "release_date": null,
      "popularity_rank": 0
    }
  ],
  "pagination": { "page": 1, "limit": 1, "total": 1, "total_pages": 1 }
}

Example request

curl
curl -sS "https://api.hzura.com/api/v1/casino/games?limit=1" \
  -H "Authorization: Bearer YOUR_API_KEY"

Errors

HTTPerrorWhen
400invalid_queryA filter is empty or too long.
400invalid_pagepage is not a whole number of 1 or more.
400invalid_limitlimit is not between 1 and 100.
400unsupported_currencyThis currency is not one of your account's currencies.
400invalid_providerNo provider has this id.
400invalid_categoryNo category has this id.
403ip_not_allowedAccess refused: check your key and server IP address.
403casino_access_disabledCasino is switched off for your account.
503security_config_unavailableAccess could not be checked right now. Try again shortly.
503casino_catalog_unavailableThe games list is not available right now. Try again shortly.
503redis_unavailableTemporarily unavailable, or took too long. Try again shortly.

Launch Game

  • API key required
  • Not rate limited

Endpoint

POST/api/v1/casino/games/{gameId}/launch

Get a link that opens one game for one player.

gameId is a game's id from the games list. It is 1 to 128 characters: it starts with a letter or digit, then letters, digits, ., _ or -.

The answer is just url. Open it for the player, for example in a new tab or a frame. After the game, the player returns to return_url when you send one. Leave it out and HZURA uses the page set up for your account.

A real launch starts a game session for this player that lasts 6 hours. New bets need a session that has not ended. Wins and refunds for a round that started in time are still paid after it ends. A demo launch is play money: it never touches a wallet.

Authentication

Your API key, sent from a server on your IP allow-list. Casino always needs a key. See Authentication.

Request

Headers

FieldTypeRequiredDescription
Content-Type—Yesapplication/json. A body that is not valid JSON, or any other content type, is 400 invalid_launch.
Authorization—YesBearer YOUR_API_KEY, or send the key in X-Api-Key.

Parameters

FieldTypeInRequiredDescription
gameIdstringpathYesA game id from the games list.

Body

FieldTypeRequiredDescription
player_idstringYesYour own id for this player. 1 to 64 characters: starts with a letter or digit, then letters, digits, ., _ or -. The first successful launch registers the player.
currencystringNoOptional. Three letters, upper or lower case. It must be the player's currency: your account's main currency on the first launch, and the same currency after that.
languagestringNoOptional. Only lets the launch work if the game has this language. At most 16 characters. It does not change the language the game opens in; see below.
devicestringNoOptional. desktop or mobile, upper or lower case.
modestringNoOptional. real (the default) or demo, upper or lower case. demo only works when the game has demo_support.
display_namestringNoOptional. The name the player sees inside the game. At most 50 characters, no control characters. Used for this launch only.
return_urlstringNoOptional. An http or https page the player returns to after the game. Leave it out, or send it blank, and HZURA uses the page set up for your account. Do not put a username or password in the URL. returnUrl is the same field.

Response

200The game link.

JSON
{
  "url": "https://games.example/launch/session"
}

Example request

curl
curl -sS -X POST "https://api.hzura.com/api/v1/casino/games/DS-example/launch" \
  -H "Authorization: Bearer YOUR_API_KEY" \
  -H "Content-Type: application/json" \
  -d '{"player_id":"player-1","currency":"EUR","device":"desktop","mode":"real","display_name":"Alex"}'

Errors

HTTPerrorWhen
400invalid_launchThe body, the game id, display_name, or an unknown field cannot be used.
400unsupported_currencyThe currency is not this player's currency.
400player_not_configuredCasino is not fully set up for your account yet (country, currency or language). The player is not registered. Contact HZURA.
403ip_not_allowedAccess refused: check your key and server IP address.
403casino_access_disabledCasino is switched off for your account.
403game_not_availableThis game cannot be opened this way, for example demo on a game without demo_support.
404game_not_foundNo game has this id.
413payload_too_largeThe body is over 8 KiB.
502launch_authentication_failedThe game could not be opened right now. Try again shortly.
502launch_failedThe game refused to open, or gave no link.
503security_config_unavailableAccess could not be checked right now. Try again shortly.
503launch_not_configuredGame launch is not available right now. Contact HZURA if it continues.
503redis_unavailableTemporarily unavailable, or took too long. Try again shortly.
503player_store_unavailablePlayers could not be looked up right now. Try again shortly.
503wallet_not_configuredReal-money launch is not ready for your account. On the seamless wallet, your wallet URL or signing secret is not set up yet.
503wallet_store_unavailableThe game session could not be started. Try again shortly.
503casino_catalog_unavailableThe games list is not available right now. Try again shortly.
504launch_unavailableThe game took too long to answer. You can safely send the same launch again.

Language: the game opens in your account's language, set up with HZURA (or HZURA's default if you have none). language in the body is only a check that the game has that language, like the games list filter. en-US and en_US mean the same; a two-letter code like en is not enough.

Device: without device, the game opens for desktop when it supports desktop (or lists no device), otherwise for mobile. If it supports neither, you get game_not_available.

Country and currency: on the first launch the player gets your account's country and main currency. They stay the same for that player from then on.

Players

There is no separate "create player" step. The first successful launch registers your player_id. Every later launch and wallet request uses that same id.

Your players are yours only. The same player_id in another account is a different player, and no one else can see or move your players' money.

The wallet does not register players. Launch a game for a player before using the transfer wallet for them. If a launch fails after the player was registered, just launch again with the same player_id.

On the seamless wallet, HZURA always sends your own player_id to your wallet.

Wallet Modes

Your account uses one wallet type, chosen with HZURA. All of your players use it for every game.

Wallet typeWhere the money isWhat you build
TransferHZURA keeps a casino balance for each player.Move money in and out with POST /api/v1/casino/wallet. Bets and wins change that balance on their own; you are not called.
SeamlessIn your own wallet.One HTTPS address that HZURA calls for every balance check, bet, win and refund. See Seamless Wallet below. POST /api/v1/casino/wallet answers wallet_mode_mismatch.

To switch from transfer to seamless, every player's casino balance must first be taken out. The seamless wallet needs your wallet address and a shared signing secret before real-money games can open. Demo games work with both.

Wallet

This route is the transfer wallet. It moves money between your platform and the player's casino balance at HZURA. On the seamless wallet it answers 409 wallet_mode_mismatch.

operation is exactly BALANCE, DEBIT, CREDIT or ROLLBACK. CREDIT adds money to the player's casino balance. DEBIT takes money out of it, back to you. ROLLBACK undoes one of your own DEBIT or CREDIT requests. BALANCE shows the balance.

While the player plays, bets and wins change the same balance, so it can be different every time you check. Each request either completes fully or is refused. There is never a "pending" result.

A successful answer always has operation, status, currency and balance. balance is a number written as text, without extra zeros: 5.00 is "5" and 12.50 is "12.5". Money requests also return your transaction_id. BALANCE does not.

status is success. If you repeat a request that was later undone, it is rolled_back.

If casino is switched off for your account, CREDIT is refused with casino_access_disabled. BALANCE, DEBIT and ROLLBACK keep working, so players' money can still be taken out.

  • API key required
  • Not rate limited

Endpoint

POST/api/v1/casino/wallet

Check, add, take out or undo money on a player's casino balance.

Authentication

Your API key, sent from a server on your IP allow-list. Casino always needs a key. See Authentication.

Request

Headers

FieldTypeRequiredDescription
Content-Type—Yesapplication/json. A body that is not valid JSON, or any other content type, is 400 invalid_wallet.

Body

FieldTypeRequiredDescription
player_idstringYesThe player_id you used at launch. Same rules as at launch. The wallet does not register players.
operationstringYesBALANCE, DEBIT, CREDIT or ROLLBACK, in capitals. Anything else is invalid_wallet.
transaction_idstringNoNeeded for DEBIT, CREDIT and ROLLBACK. Your own unique id for this request; never reuse it for a different request, even for another player. 1 to 128 characters, same rules as player_id.
amountstring or numberNoNeeded for DEBIT, CREDIT and ROLLBACK. Best sent as text, such as "12.50". It must be above zero, with no sign and no e notation, at most 12 digits before the point and 8 after, and at most 21 characters. "05" is not allowed.
currencystringNoOptional. If you send it, it must be the player's currency in three capital letters. eur is invalid_wallet; a different currency is unsupported_currency.
reference_transaction_idstringNoNeeded for ROLLBACK. The transaction_id of the successful DEBIT or CREDIT you want to undo, for the same player. 1 to 128 characters.

Response

200The balance, or a finished money request.

JSON
{
  "operation": "DEBIT",
  "status": "success",
  "currency": "EUR",
  "balance": "7.5",
  "transaction_id": "withdraw-1001"
}

Errors

HTTPerrorWhen
400invalid_walletThe body, amount, operation or currency is not valid, or a rollback amount does not match.
400unsupported_currencyThe currency is not this player's currency.
403ip_not_allowedAccess refused: check your key and server IP address.
403casino_access_disabledCREDIT while casino is switched off, or casino is not set up for your account.
404player_not_foundNo player with this player_id. Launch a game for them first.
404wallet_transaction_not_foundThe transaction to undo was not found for this player.
409insufficient_balanceNot enough balance for this DEBIT, or to undo this CREDIT.
409wallet_mode_mismatchYour account uses the seamless wallet.
409wallet_idempotency_conflictThis transaction_id was already used for a different request.
409wallet_already_rolled_backThat transaction was already undone.
409wallet_transaction_not_reversibleThat transaction failed, or cannot be undone.
413payload_too_largeThe body is over 8 KiB.
503security_config_unavailableAccess could not be checked right now. Try again shortly.
503wallet_not_configuredThe wallet is not ready for your account. Contact HZURA.
503wallet_store_unavailableTemporarily unavailable. Repeat with the same transaction_id.

BALANCE

Shows the player's casino balance, after every bet and win so far. A player who has never had money starts at 0.

curl
curl -sS -X POST "https://api.hzura.com/api/v1/casino/wallet" \
  -H "Authorization: Bearer YOUR_API_KEY" \
  -H "Content-Type: application/json" \
  -d '{"player_id":"player-1","operation":"BALANCE"}'
JSON response
{
  "operation": "BALANCE",
  "status": "success",
  "currency": "EUR",
  "balance": "0"
}

DEBIT

Takes amount out of the player's casino balance, back to you. Add it to the player's account on your platform once the answer arrives.

If the balance is too low, you get insufficient_balance and nothing moves. That transaction_id stays refused; to try a smaller amount, use a new transaction_id.

curl
curl -sS -X POST "https://api.hzura.com/api/v1/casino/wallet" \
  -H "Authorization: Bearer YOUR_API_KEY" \
  -H "Content-Type: application/json" \
  -d '{"player_id":"player-1","operation":"DEBIT","transaction_id":"withdraw-1001","amount":"5.00","currency":"EUR"}'

If the balance was 12.5, the answer is:

JSON
{
  "operation": "DEBIT",
  "status": "success",
  "currency": "EUR",
  "balance": "7.5",
  "transaction_id": "withdraw-1001"
}

CREDIT

Adds amount to the player's casino balance. Take the money from the player's account on your platform first. The player can bet with it as soon as the answer arrives.

curl
curl -sS -X POST "https://api.hzura.com/api/v1/casino/wallet" \
  -H "Authorization: Bearer YOUR_API_KEY" \
  -H "Content-Type: application/json" \
  -d '{"player_id":"player-1","operation":"CREDIT","transaction_id":"deposit-1001","amount":"12.50","currency":"EUR"}'
JSON response
{
  "operation": "CREDIT",
  "status": "success",
  "currency": "EUR",
  "balance": "12.5",
  "transaction_id": "deposit-1001"
}

ROLLBACK

Undoes one of your successful DEBIT or CREDIT requests for the same player. transaction_id is a new id for the undo itself. reference_transaction_id is the request you are undoing. amount and currency must be the same as in that request; a different amount is 400 invalid_wallet.

Undoing a DEBIT puts the money back on the casino balance. Undoing a CREDIT takes it out again. If the player has already spent part of it, you get insufficient_balance.

Each request can be undone once; a second undo is wallet_already_rolled_back. A refused request cannot be undone (wallet_transaction_not_reversible), and an unknown one is wallet_transaction_not_found. You can only undo your own money requests, not bets or wins from games.

curl
curl -sS -X POST "https://api.hzura.com/api/v1/casino/wallet" \
  -H "Authorization: Bearer YOUR_API_KEY" \
  -H "Content-Type: application/json" \
  -d '{"player_id":"player-1","operation":"ROLLBACK","transaction_id":"rb-1001","reference_transaction_id":"withdraw-1001","amount":"5.00","currency":"EUR"}'
JSON response
{
  "operation": "ROLLBACK",
  "status": "success",
  "currency": "EUR",
  "balance": "12.5",
  "transaction_id": "rb-1001"
}

Safe retries

Your transaction_id makes retries safe. If you send the same request again with the same transaction_id, you get the first answer back and money does not move twice. So after a network error or a 503, simply send the same request again.

You send againYou get
The same transaction_id and the same requestThe first answer. Money moves only once.
The same transaction_id with anything changed in the request409 wallet_idempotency_conflict
The same transaction_id after a refusalThe same refusal, such as insufficient_balance.

Seamless Wallet

With the seamless wallet, the player's money stays with you. You give HZURA one HTTPS address. HZURA sends it a JSON POST for every balance check, bet, win and refund in a game. Your answer is passed to the game, so the player always sees your balance.

Games only wait a short time, so your wallet must answer within 3.5 seconds, and accept the connection within 1 second. Redirects are not followed. An answer bigger than 16 KiB counts as no answer.

Every request is signed with a secret that only you and HZURA know. Refuse any request whose signature does not match.

HeaderValue
Content-Typeapplication/json
X-Hzura-TimestampThe time HZURA sent the request, in Unix seconds.
X-Hzura-SignatureHMAC-SHA256 of {X-Hzura-Timestamp}.{raw body} with your signing secret, in lowercase hex.
Verify the signature (Node.js)
import crypto from "node:crypto";

// rawBody is the exact request body bytes, before JSON parsing.
export function isFromHzura(headers, rawBody, secret) {
  const timestamp = headers["x-hzura-timestamp"];
  const signature = headers["x-hzura-signature"];
  if (!timestamp || !signature) return false;
  if (Math.abs(Date.now() / 1000 - Number(timestamp)) > 300) return false;
  const expected = crypto
    .createHmac("sha256", secret)
    .update(`${timestamp}.${rawBody}`)
    .digest("hex");
  return (
    signature.length === expected.length &&
    crypto.timingSafeEqual(Buffer.from(signature), Buffer.from(expected))
  );
}

Request

The fields have the same names as on POST /api/v1/casino/wallet. Fields that do not apply are left out. New optional fields may be added later, so ignore any field you do not know.

FieldSent onMeaning
operationAlwaysBALANCE, DEBIT (take money), CREDIT (give money) or ROLLBACK (refund a DEBIT).
player_idAlwaysYour player id, the one you sent at launch.
currencyAlwaysThe player's currency, such as EUR.
transaction_idDEBIT, CREDIT, ROLLBACKA unique id for this money movement. Use it to make sure you apply it only once.
reference_transaction_idROLLBACKThe transaction_id of the DEBIT to refund.
amountDEBIT, CREDIT, ROLLBACKA number written as text. Two decimals for normal currencies, up to eight for BTC, ETH, XRP, LTC, BCH, BNB and BUSD. It can be "0.00" for a free-round DEBIT or a losing CREDIT.
reasonDEBIT, CREDIT, ROLLBACKBET on DEBIT. WIN or REWARD on CREDIT; REWARD is a promotion or tournament prize. REFUND on ROLLBACK.
round_idWhen knownThe game round. One round can have several debits and credits.
game_idWhen knownThe game's id, the gameId used at launch.
round_completedWhen knowntrue when the round is finished.
bonusBonus roundsAn object with type (FREE_ROUND or FEATURE_TRIGGER), and sometimes promo_code and bet_amount (what the free round's bet is worth).
DEBIT request
{
  "operation": "DEBIT",
  "player_id": "player-1",
  "currency": "EUR",
  "transaction_id": "7316495845",
  "amount": "10.00",
  "reason": "BET",
  "round_id": "1nnrtsy56",
  "game_id": "TK-froggrog",
  "round_completed": false
}
ROLLBACK request
{
  "operation": "ROLLBACK",
  "player_id": "player-1",
  "currency": "EUR",
  "transaction_id": "7316495900",
  "reference_transaction_id": "7316495845",
  "amount": "10.00",
  "reason": "REFUND",
  "round_id": "1nnrtsy56",
  "game_id": "TK-froggrog",
  "round_completed": true
}

Response

When it worked, answer 200 with the player's balance after this request. balance is text or a number, and never below zero. You can also send your own transaction_id for this movement (1 to 128 letters, digits, -, _, . or :); it is shown to the game as the reference. Leave it out on BALANCE.

200 response
{
  "balance": "90.00",
  "transaction_id": "your-txn-1001"
}

To refuse, answer with a 4xx status and one of the error codes below.

Refusal
{
  "error": "insufficient_balance",
  "message": "Balance is too low"
}
`error`Use it whenWhat the player sees on a bet
insufficient_balanceThe balance is too low for the bet.Not enough money.
player_blockedThe player is not allowed to play.The account is blocked.
player_not_foundYou do not know this player_id.The bet is refused.
invalid_requestThe request cannot be applied, for example a DEBIT that was already refunded by a ROLLBACK.The bet is refused.

A refused DEBIT stays refused. If the same transaction_id comes again, HZURA gives the same refusal itself and does not call you.

Do not refuse a CREDIT or a ROLLBACK: the win or refund has already happened in the game. A refusal counts as no answer, and the same transaction_id is sent again later.

No answer within 3.5 seconds, a 5xx, a redirect, an unknown error code, or an answer that cannot be read all mean "unknown". Nothing is recorded, and the same transaction_id is sent again, or a ROLLBACK follows for that bet.

Rules

  • Apply each transaction_id only once. If you already applied it, answer 200 with the balance and the same transaction_id you gave the first time. Do not move money again.
  • A ROLLBACK refunds the DEBIT named in reference_transaction_id. If you never applied that debit, or already refunded it, answer 200 with the current balance and move no money.
  • A ROLLBACK can arrive before its DEBIT. Remember the refunded id, and if that DEBIT arrives later, refuse it with invalid_request.
  • A CREDIT or ROLLBACK can arrive after the game session ended, or after you blocked the player. Apply it anyway.
  • BALANCE moves no money. It is sent when a game opens, and whenever the game needs the balance.
  • Every attempt is signed again with a new X-Hzura-Timestamp, so a repeated transaction_id comes with a different signature.