Casino API · v1
Casino API
The HZURA Casino API lists the games a customer may show, returns a launch URL for one game, and applies BALANCE, DEBIT, CREDIT, and ROLLBACK on that player's wallet. Customers authenticate with an API key, and casino access must be enabled for that customer.
Overview
These routes do not accept customer_id, a provider player id, provider credentials, or a return URL. Unknown JSON fields are rejected. Launch and wallet bodies are limited to 8 KiB.
Bonuses, jackpots, free spins, and promotions are not part of this API. There is no public transaction-status route.
| Method | Path | Purpose |
|---|---|---|
| GET | /api/v1/casino/games | One page of visible games. |
| POST | /api/v1/casino/games/{gameId}/launch | Launch URL for one game. |
| POST | /api/v1/casino/wallet | Balance, debit, credit, or rollback. |
Games
- API key required
- Not rate limited
Endpoint
/api/v1/casino/gamesGames this customer may see.
Inactive games are hidden. Inactive or removed providers are hidden. Reviewed providers are visible unless this customer has turned that provider off. New providers stay hidden until this customer turns them on.
An empty data array with total 0 is a successful page. A page past the end returns an empty data array and the real total.
images, supported_devices, client_types, supported_currencies, languages, volatility, features, and themes are the catalog values for that game. Treat unused keys as optional.
Authentication
Customer API key and a calling IP inside that customer's CIDR allow-list. Legacy IP-only access is not enough.
Request
Parameters
| Field | Type | In | Required | Description |
|---|---|---|---|---|
| provider | string | query | No | Exact catalog provider id. A different id, including different case, is invalid_provider. Max 200 characters. |
| category | string | query | No | Exact catalog category id. A different id, including different case, is invalid_category. Max 200 characters. |
| search | string | query | No | Case-insensitive match against the game name or description. Max 100 characters. |
| page | integer | query | No | Integer greater than or equal to 1. Default 1. |
| limit | integer | query | No | Integer from 1 to 100. Default 50. |
| currency | string | query | No | One of the customer's allowed currencies. Matching ignores case. Max 16 characters. When omitted, the customer's default currency is the visibility filter. If no default is configured, omitting currency does not filter by currency. |
| language | string | query | No | Keeps games that list this language. en-US and en_US match. A two-letter code does not match an ll_RR locale. Max 32 characters. |
| device | string | query | No | Case-insensitive match against supported_devices or client_types. Max 64 characters. Not limited to desktop and mobile. |
Response
200One page of games.
{
"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
}
],
"pagination": { "page": 1, "limit": 1, "total": 1, "total_pages": 1 }
}Example request
curl -sS "https://api.hzura.com/api/v1/casino/games?limit=1" \
-H "Authorization: Bearer YOUR_API_KEY"Errors
| HTTP | error | When |
|---|---|---|
| 400 | invalid_query | A filter is empty or too long. |
| 400 | invalid_page | page is not a positive integer. |
| 400 | invalid_limit | limit is outside 1–100. |
| 400 | unsupported_currency | Currency is not on the customer allow-list. |
| 400 | invalid_provider | Provider id is not in the catalog. |
| 400 | invalid_category | Category id is not in the catalog. |
| 403 | ip_not_allowed | No customer was admitted. |
| 403 | casino_access_disabled | Casino is off for this customer. |
| 503 | security_config_unavailable | API-key policy could not be read. |
| 503 | casino_catalog_unavailable | The catalog could not be loaded. |
| 503 | redis_unavailable | The catalog store could not be read, including a timeout. |
Launch Game
- API key required
- Not rate limited
Endpoint
/api/v1/casino/games/{gameId}/launchReturn a launch URL for one visible game.
gameId is data[].id from the catalog. It is 1 to 128 characters, must start with an ASCII letter or digit, and may then contain ASCII letters, digits, ., _, or -.
The response is only url. It does not include tokens or the upstream body. The return URL is server configuration. Sending return_url or returnUrl is invalid_launch.
Authentication
Customer API key and a calling IP inside that customer's CIDR allow-list. Legacy IP-only access is not enough.
Request
Headers
| Field | Type | Required | Description |
|---|---|---|---|
| Content-Type | — | Yes | application/json. A body the parser rejects, including a non-JSON content type, is 400 invalid_launch. |
| Authorization | — | Yes | Bearer customer API key, or X-Api-Key. |
Parameters
| Field | Type | In | Required | Description |
|---|---|---|---|---|
| gameId | string | path | Yes | Catalog game id. |
Body
| Field | Type | Required | Description |
|---|---|---|---|
| player_id | string | Yes | The customer's own player id. 1–64 characters. Starts with a letter or digit, then letters, digits, ., _, or -. The first accepted launch stores it. |
| currency | string | No | Optional. Three letters. Must match the currency already stored for this player, or the customer's default currency on the first launch. Comparison ignores case. |
| language | string | No | Optional catalog filter, max 16 characters. It does not select the language sent on launch. See Language below. |
| device | string | No | Optional. desktop or mobile, any ASCII case. Sent as lowercase. It is not saved on the player. |
| mode | string | No | Optional. real or demo, any ASCII case. Defaults to real. demo requires demo_support on the game. |
Response
200Launch URL.
{
"url": "https://games.example/launch/session"
}Example request
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"}'{
"player_id": "player-1",
"currency": "EUR",
"language": "en-US",
"device": "desktop",
"mode": "real"
}Errors
| HTTP | error | When |
|---|---|---|
| 400 | invalid_launch | Body, game id, or unknown field is not usable. |
| 400 | unsupported_currency | Currency does not match the established player or customer currency. |
| 400 | player_not_configured | Country, currency, or launch locale cannot be resolved. A new player is not created. |
| 403 | ip_not_allowed | No customer was admitted. |
| 403 | casino_access_disabled | Casino is off for this customer. |
| 403 | game_not_available | The game is not launchable for this request, including demo without demo_support. |
| 404 | game_not_found | The game id is not in the catalog. |
| 413 | payload_too_large | Body is over 8 KiB. |
| 502 | launch_authentication_failed | Provider authentication failed. |
| 502 | launch_failed | Provider rejected the launch or returned no URL. |
| 503 | security_config_unavailable | API-key policy could not be read. |
| 503 | launch_not_configured | Launch is not configured. |
| 503 | redis_unavailable | The catalog store could not be read, including a timeout. |
| 503 | player_store_unavailable | The player store could not be used. |
| 503 | casino_catalog_unavailable | The catalog could not be loaded. |
| 504 | launch_unavailable | The provider did not answer. Safe to retry the same launch. |
language on the body only filters which game can be launched, the same way the games query does. The locale sent to the provider is the locale stored on the player: the customer's configured locale, or the server locale when the customer has none. en-US and en_US are the same locale and are sent as en_US. A two-letter code such as en is not a launch language. If no ll_RR locale can be resolved, launch is rejected and a new player is not created.
When device is omitted, desktop is used if the game lists desktop or lists no device. Otherwise mobile is used when the game lists it. If neither can be chosen, the game is game_not_available.
Country and currency are taken from the customer's casino configuration on the first launch, then stored on the player. A later launch cannot switch them. currency in the body must match that stored value, ignoring case. The value persisted is uppercase.
Players
There is no separate create-player route. The first launch that passes validation stores player_id for this customer. Later launches and every wallet call reuse that id.
The same player_id at another customer is a different player. One customer cannot read or move another customer's player.
The wallet does not create a player. Call launch before BALANCE, DEBIT, CREDIT, or ROLLBACK. A launch that fails after the player row is stored still leaves that mapping in place. Repeat the launch with the same player_id.
Wallet
One route covers every wallet operation. operation is exactly BALANCE, DEBIT, CREDIT, or ROLLBACK. The balance is the HZURA wallet for that player. BALANCE does not call the game provider and creates the wallet at zero on the first read if the player exists and no wallet row exists yet.
Success responses always include operation, status, currency, and balance. balance is decimal text with trailing zeros removed, so 5.00 is "5" and 12.50 is "12.5". transaction_id is present on transfer results and is the caller's idempotency key, not an internal row id. BALANCE omits it.
status is success for a completed read or transfer. Replaying an original transaction that has since been reversed returns rolled_back.
- API key required
- Not rate limited
Endpoint
/api/v1/casino/walletRead a balance or apply a debit, credit, or rollback.
Authentication
Customer API key and a calling IP inside that customer's CIDR allow-list. Legacy IP-only access is not enough.
Request
Headers
| Field | Type | Required | Description |
|---|---|---|---|
| Content-Type | — | Yes | application/json. A body the parser rejects, including a non-JSON content type, is 400 invalid_wallet. |
Body
| Field | Type | Required | Description |
|---|---|---|---|
| player_id | string | Yes | The external player id used at launch. 1–64 characters, same character set as launch. The wallet does not create a player. |
| operation | string | Yes | BALANCE, DEBIT, CREDIT, or ROLLBACK. Any other text is invalid_wallet. |
| transaction_id | string | No | Required for DEBIT, CREDIT, and ROLLBACK. Idempotency key, unique per customer, including across players. 1–128 characters, same character set as player_id. |
| amount | string or number | No | Required for DEBIT, CREDIT, and ROLLBACK. A decimal string is preferred. A JSON number is accepted and then parsed from its text. Greater than zero, at most 8 fractional digits, no sign, no exponent, whole part at most 12 digits, and the text at most 21 characters. A whole part longer than one digit cannot start with 0. |
| currency | string | No | Optional. When present it must be exactly three uppercase letters and equal the player's stored currency. eur is invalid_wallet. A different currency is unsupported_currency. |
| reference_transaction_id | string | No | Required for ROLLBACK. The transaction_id of a successful DEBIT or CREDIT for the same player. 1–128 characters. |
Response
200Balance, or a completed transfer.
{
"operation": "DEBIT",
"status": "success",
"currency": "EUR",
"balance": "7.5",
"transaction_id": "bet-1001"
}Errors
| HTTP | error | When |
|---|---|---|
| 400 | invalid_wallet | Body, amount, operation, currency text, or rollback amount does not match. |
| 400 | unsupported_currency | Uppercase currency does not equal the player's currency. |
| 403 | ip_not_allowed | No customer was admitted. |
| 404 | player_not_found | This customer has no mapping for player_id. |
| 404 | wallet_transaction_not_found | Rollback reference is unknown for this player. |
| 409 | insufficient_balance | The amount cannot be reserved. |
| 409 | wallet_idempotency_conflict | Same key, different request. |
| 409 | wallet_transaction_pending | That key is still pending. |
| 409 | wallet_already_rolled_back | The original transaction was already reversed. |
| 409 | wallet_transaction_not_reversible | Pending, failed, or not a debit/credit. |
| 413 | payload_too_large | Body is over 8 KiB. |
| 502 | wallet_provider_failed | The provider rejected the operation. |
| 503 | security_config_unavailable | API-key policy could not be read. |
| 503 | wallet_not_configured | The wallet or provider client is not configured. |
| 503 | wallet_store_unavailable | The ledger could not be used. |
| 504 | wallet_provider_timeout | The provider did not answer. The row stays pending. |
BALANCE
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"}'{
"operation": "BALANCE",
"status": "success",
"currency": "EUR",
"balance": "0"
}DEBIT
The balance is reduced before the provider is called. If the balance cannot cover the amount, the response is insufficient_balance and the provider is not called.
On provider acceptance the reduced balance stands. A definite provider rejection releases that reservation and returns wallet_provider_failed. A timeout leaves the debit pending and keeps the reserved amount.
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":"bet-1001","amount":"5.00","currency":"EUR"}'If the wallet held 12.5 before this debit, a successful response looks like this. amount "5.00" is stored and returned in balance without trailing zeros.
{
"operation": "DEBIT",
"status": "success",
"currency": "EUR",
"balance": "7.5",
"transaction_id": "bet-1001"
}CREDIT
The balance increases only after the provider accepts the credit. A rejection or a timeout does not credit the wallet. A timeout still leaves the transaction_id pending.
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":"win-1001","amount":"12.50","currency":"EUR"}'{
"operation": "CREDIT",
"status": "success",
"currency": "EUR",
"balance": "12.5",
"transaction_id": "win-1001"
}ROLLBACK
Rollback reverses one successful DEBIT or CREDIT for the same player, currency, and amount. transaction_id is the rollback's own idempotency key. reference_transaction_id is the original debit or credit. The amount must equal the original amount; a mismatch is 400 invalid_wallet.
A debit is restored only after the provider accepts the rollback. A credit is reserved first and restored to the player only if the provider rejects the rollback. If that reservation cannot be taken, the response is insufficient_balance.
A second rollback of the same original transaction, under a new key, is wallet_already_rolled_back. Repeating the rollback key with the same body returns the first rollback result. Pending and failed originals cannot be rolled back (wallet_transaction_not_reversible). An unknown reference is wallet_transaction_not_found.
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":"bet-1001","amount":"5.00","currency":"EUR"}'{
"operation": "ROLLBACK",
"status": "success",
"currency": "EUR",
"balance": "12.5",
"transaction_id": "rb-1001"
}Idempotency and pending
transaction_id is unique per customer. Repeating the same key with the same player, operation, amount, and currency returns the original result and does not call the provider again. For a rollback, the reference and amount must match as well.
| Repeat | Result |
|---|---|
| Same key and same request | Original result. The provider is not called again. |
| Same key, different body | 409 wallet_idempotency_conflict |
| Same key while the first call is pending | 409 wallet_transaction_pending. The provider is not called again. |
| Same key after a definite rejection | The original error, such as insufficient_balance or wallet_provider_failed. |
504 wallet_provider_timeout means the provider did not answer and the outcome is unknown. The transaction stays pending. A pending debit keeps the reserved amount. A pending credit is not applied. Repeating the same transaction_id does not move the balance again.
Do not start a second debit or credit for the same bet with a new transaction_id while the first one is pending. That would be a different transaction. Rollback of a pending transaction is rejected.
This API does not look up a provider status for that row. Customers cannot settle a pending transaction through a public route. A timeout is not success.