Appixa API

Overview

Appixa is a B2B iGaming aggregator. One integration gives your platform access to every game provider we carry — slots, live casino, crash games and sportsbook — through a single wallet API.

Base URL

https://appixa-api.com

All requests and responses are JSON. All endpoints require TLS. Timestamps are ISO 8601 UTC.

How a game round works

  1. Your platform calls POST /api/game/launch with a player ID and a game ID.
  2. Appixa returns a launch URL. You open it in an iframe or redirect the player to it.
  3. The game provider sends bet/win events to Appixa.
  4. Appixa moves the money — from its own ledger (transfer wallet) or by calling your wallet (seamless wallet).
  5. Appixa notifies you of every movement, then deducts the GGR fee from your prepaid balance.
Prepaid billing. Your account carries a prepaid balance. The GGR fee is deducted live as play happens, calculated on net GGR (total bets − total wins) for the period — not on turnover. When the balance reaches zero, game launches are refused with GGR balance low.

Authentication

Every request carries your API key as a bearer token.

Authorization: Bearer <your_api_key>
Content-Type: application/json
Keep the key server-side. It authorises wallet operations. Never expose it in browser or mobile client code. Rotate it from the client panel if it leaks.

Optional IP allow-list

You can restrict your API key to specific source IPs. While an allow-list is set, requests from any other address are rejected with 403 IP not whitelisted.

Rate limit

500 requests per minute per IP. Request bodies are capped at 2 MB.

Wallet models

Pick one at onboarding. It determines who holds the player's money.

Transfer wallet

Appixa holds the player balance. You move funds in with /player/deposit. Appixa notifies you of each debit and credit via your webhook.

Seamless wallet

You hold the player balance. Appixa calls your endpoints in real time for every balance read, debit and credit. No pre-funding of players needed.

 TransferSeamless
Player balance held byAppixaYou
You must implementOne webhook receiverThree endpoints (balance, debit, credit)
Player fundingPOST /player/depositNot required
Latency sensitivityLowHigh — respond under 8s

Quick start

A working launch in three calls.

1 · Confirm your account is reachable

curl https://appixa-api.com/health
{ "status": "ok", "time": "2026-09-30T10:00:00.000Z" }

2 · Find a game

curl -H "Authorization: Bearer $APPIXA_KEY" \
  "https://appixa-api.com/api/game/list?limit=5"

3 · Launch it

curl -X POST https://appixa-api.com/api/game/launch \
  -H "Authorization: Bearer $APPIXA_KEY" \
  -H "Content-Type: application/json" \
  -d '{"ext_player_id":"player_001","game_uid":"KAGG_Zorro"}'

Open the returned url in an iframe. That is the whole integration for transfer wallet.

Game list

GET/api/game/list

Returns the games enabled for your account. Use it to build your lobby and to cache game_uid values.

Query parameters

NameTypeRequiredDescription
pageintegeroptionalPage number, 1-based. Default 1.
pageSizeintegeroptionalRows per page. Default 50.
providerstringoptionalFilter by studio code, e.g. PGS, EVO.
searchstringoptionalSubstring match on the game name.

Response 200

{
  "Code": 0,
  "Value": {
    "Total": 12724,
    "Games": [
      {
        "Code":         "KAGG_Zorro",
        "Name":         "The Mask of Zorro",
        "sub_provider": "KAGG",
        "ProviderCode": "KAGG",
        "ProviderName": "KA Gaming",
        "Thumbnails": [
          { "Language": "en", "ImagePath": "https://cdn.example.com/zorro.png" }
        ],
        "synced_env":   "production"
      }
    ]
  }
}
Code: 0 means success. Value.Games[].Code is the game_uid you pass to /api/game/launch. Each entry also carries any extra metadata the studio publishes, so treat the object as open-ended and read only the fields you need.
Total is the filtered count, not the page size — use it to drive pagination with page and pageSize.

Launch game

POST/api/game/launch

Creates a game session and returns the URL to load for the player. Call it every time a player opens a game — URLs are single-session and expire.

Body parameters

NameTypeRequiredDescription
ext_player_idstringrequiredYour player identifier. Unique within your account, created automatically on first use. Must be 1–40 lowercase alphanumeric characters (a-z, 0-9) — no underscores, hyphens, dots or uppercase.
game_uidstringrequiredGame identifier from /api/game/list.
currencystringoptionalISO 4217 code. Must be in your allowed currencies. Defaults to your account currency.

Request

curl -X POST https://appixa-api.com/api/game/launch \
  -H "Authorization: Bearer $APPIXA_KEY" \
  -H "Content-Type: application/json" \
  -d '{
    "ext_player_id": "player_001",
    "game_uid": "KAGG_Zorro",
    "currency": "TRY"
  }'

Response 200

The response carries either url or html, depending on the game. Handle both.

// Casino, slots, crash — the usual case
{ "url": "https://launch.provider.com/game?token=eyJhbGciOi..." }

// Sportsbook (LuckySports) — a pre-rendered document
{ "html": "<!DOCTYPE html><html>..." }
Do not assume url is always present. Launching the sportsbook returns html instead, and reading url there gives undefined — a blank game window. Branch on whichever key is present.

Embedding

const r = await fetch('https://appixa-api.com/api/game/launch', {
  method: 'POST',
  headers: { 'Authorization': 'Bearer ' + KEY, 'Content-Type': 'application/json' },
  body: JSON.stringify({ ext_player_id: 'player_001', game_uid: 'KAGG_Zorro' })
});
const data = await r.json();

const frame = document.getElementById('game');
if (data.url)       frame.src = data.url;
else if (data.html) frame.srcdoc = data.html;
else               /* show the error in data.error */;
<iframe id="game"
        style="width:100%;height:100%;border:0"
        allow="autoplay; fullscreen"
        allowfullscreen></iframe>
Balance gate. If your prepaid balance is at or below zero this endpoint returns 403 GGR balance low before the session is created. Top up to resume — it takes effect immediately, with no restart or re-integration.

Errors

StatusErrorCause
400ext_player_id is requiredField missing or empty.
400game_uid is requiredField missing or empty.
400ext_player_id must be 1-40 lowercase alphanumeric …Player id contains characters the provider rejects.
401Invalid API keyKey unknown or malformed.
403GGR balance lowPrepaid balance exhausted.
403Client account is suspendedAccount not active.
404Game not foundgame_uid unknown or not enabled for you.

Player balance

GET/api/game/player/balance

Reads a player's current balance. Transfer wallet only — under seamless wallet the balance lives on your side.

Query parameters

NameTypeRequiredDescription
ext_player_idstringrequiredYour player identifier.
curl -H "Authorization: Bearer $APPIXA_KEY" \
  "https://appixa-api.com/api/game/player/balance?ext_player_id=player_001"

Response 200

{
  "ext_player_id": "player_001",
  "balance":       "1250.00",
  "currency":      "TRY"
}
Balance is a decimal string, not a float — parse it with a decimal type to avoid rounding drift.

Player deposit

POST/api/game/player/deposit

Moves funds into a player's Appixa wallet. Transfer wallet only.

Body parameters

NameTypeRequiredDescription
ext_player_idstringrequiredYour player identifier.
amountnumberrequiredPositive amount to credit.
txn_idstringoptionalYour unique transaction reference, used as the idempotency key. Technically optional — but see the warning below. Always send it.
currencystringoptionalDefaults to the player's currency.
curl -X POST https://appixa-api.com/api/game/player/deposit \
  -H "Authorization: Bearer $APPIXA_KEY" \
  -H "Content-Type: application/json" \
  -d '{
    "ext_player_id": "player_001",
    "amount": 500.00,
    "txn_id": "dep_20260930_0001",
    "currency": "TRY"
  }'

Response 200

{
  "success": true,
  "balance": "1750.00",
  "txn_id":  "dep_20260930_0001"
}
Idempotent when you send txn_id. Re-sending the same txn_id does not credit twice. The repeat response carries "duplicate": true and the balance from the original call — safe to retry on timeout.
Always send txn_id. If you omit it the server generates a random one, so every retry looks like a brand-new deposit and credits the player again. A timeout you retry without txn_id will double the money. Use your own ledger reference and keep it stable across retries.

Providers

GET/api/game/providers

Lists every game studio with its game count. Use it to build lobby filters, then pass a code as the provider parameter on /api/game/list.

Response 200

{
  "Code": 0,
  "Value": [
    { "code": "PGS", "name": "PGS", "aggregator": "ELITEHUB", "gameCount": 172 },
    { "code": "EVO", "name": "EVO", "aggregator": "ELITEHUB", "gameCount": 210 }
  ]
}
This endpoint needs no API key. It returns the platform-wide catalogue, not your enabled subset — counts here can exceed what /api/game/list returns for your account.

Seamless wallet

Under the seamless model you keep the player balance and expose three endpoints. Appixa calls them synchronously during play, so they must be fast and always answer in the documented shape.

You register one base URL. Appixa appends the action:

POST {your_seamless_url}/balance
POST {your_seamless_url}/debit
POST {your_seamless_url}/credit

Request signing

Every call carries an HMAC-SHA256 signature over the raw request body.

Content-Type:        application/json
X-Appixa-Timestamp: 1790000000
X-Appixa-Signature: 9f2c...   // hex

signature = HMAC_SHA256(seamless_secret, timestamp + raw_body)
Verify against the raw body. Sign the exact bytes received, before JSON parsing. Re-serialising the parsed object changes key order and whitespace and the signature will not match. Reject requests whose timestamp is more than 300 seconds old.

Verification example (Node.js)

const crypto = require('crypto');

// Mount with a raw body parser so `req.body` is a Buffer:
// app.use('/wallet', express.raw({ type: 'application/json' }));

function verify(req) {
  const ts   = req.headers['x-appixa-timestamp'];
  const sig  = req.headers['x-appixa-signature'];
  const raw  = req.body.toString('utf8');

  if (Math.abs(Date.now()/1000 - Number(ts)) > 300) return false;

  const want = crypto.createHmac('sha256', SEAMLESS_SECRET)
                     .update(ts + raw).digest('hex');

  return crypto.timingSafeEqual(Buffer.from(sig), Buffer.from(want));
}

POST /balance

// Appixa sends
{ "ext_player_id": "player_001", "currency": "TRY" }

// You return
{ "status": "success", "balance": 1250.00 }

POST /debit

// Appixa sends
{
  "ext_player_id": "player_001",
  "txn_id":        "EH-8891273",
  "amount":        10.00,
  "currency":      "TRY",
  "game_uid":      "KAGG_Zorro",
  "round_id":      "R-55231",
  "type":          "bet",
  "provider_id":   1
}

// You return
{
  "status":         "success",
  "balance_before": 1250.00,
  "balance":        1240.00
}

// Not enough funds — return code 2001
{ "status": "error", "code": 2001, "message": "Insufficient balance" }

POST /credit

Same shape as /debit. type is usually win, refund or rollback.

Idempotency is your responsibility. Providers retry. Store txn_id with a unique constraint and, on a repeat, return the original result instead of applying the movement again. Answer within 8 seconds — Appixa treats a slower reply as a failure.
A force: true field may appear on /debit. It marks a rollback that must succeed even if it drives the balance negative. Do not reject it for insufficient funds.

Webhooks

Under the transfer model Appixa notifies your callback_url after every confirmed movement. Set the URL from the client panel or POST /api/client/callback-url.

// POST {your_callback_url}
{
  "event":          "debit",
  "txn_id":         "3f6a1c2e-...",
  "player_id":      4192,
  "ext_player_id":  "player_001",
  "amount":         10.00,
  "balance_before": 1250.00,
  "balance_after":  1240.00,
  "type":           "bet",
  "game_uid":       "KAGG_Zorro",
  "round_id":       "R-55231",
  "currency":       "TRY",
  "provider_id":    1
}
FieldMeaning
eventdebit (money left the player) or credit (money arrived).
txn_idAppixa's ledger reference. Unique and stable.
typebet, win, refund, rollback, reserve, release, cancel and related states.
round_idGroups every transaction in one game round. Use it to reconcile.

Respond 200 quickly. Do your processing asynchronously.

Webhook delivery is best-effort today. There is no automatic retry, so treat webhooks as a convenience feed rather than your source of truth. Reconcile against your own records, and use round_id to detect anything you missed.

Currencies

Currencies you can be onboarded on today. Your account is limited to the subset agreed at onboarding — sending any other code is rejected.

AUD  BDT  BND  BRL  EUR  INR  MYR
NPR  PHP  SGD  THB  TRY  USD
Not every provider supports every currency. For TRY, the studios JILI, ACEWIN and MARBLEX are unavailable. Their games are filtered out of your game list; launching one directly returns 404 Game not found.
Other currencies on request. CNY, HKD, IDR, KES, MMK and VND are contracted with our supplier but are not yet provisionable — ask your account manager before building against them. PKR and KWD appear in some panel dropdowns but have no live casino credentials; do not use them.

GGR fees are settled in USD, converted at the daily reference rate on the day the fee is charged.

Error codes

Errors return the matching HTTP status with a JSON body.

{ "error": "GGR balance low" }

HTTP status meanings

StatusMeaningRetry?
200Success.—
400Malformed request. A field is missing or invalid.No — fix the request.
401Authentication failed.No — check the key.
403Authenticated but not permitted.No — resolve the cause.
404Resource does not exist.No.
429Rate limit exceeded (500 req/min per IP).Yes, with backoff.
500Server or upstream provider error.Yes, with backoff.

Full error reference

StatusMessageWhat it meansHow to fix
400ext_player_id is requiredPlayer identifier missing.Send a non-empty ext_player_id.
400game_uid is requiredGame identifier missing.Take a game_uid from /api/game/list.
400currency is requiredCurrency missing where it cannot be inferred.Send an ISO 4217 code you are enabled for.
400ext_player_id must be 1-40 lowercase alphanumeric characters (a-z, 0-9) for this providerPlayer id contains underscores, hyphens, dots or uppercase.Normalise your player ids to lowercase letters and digits only, e.g. player_001 → player001. Keep the mapping stable — it identifies the wallet.
400amount must be a positive numberDeposit amount is zero, negative or not numeric.Send a positive number.
400Player ID already existsext_player_id already registered.Use the existing player; creation is not needed.
400callback_url requiredNo URL supplied when setting the webhook.Send an absolute HTTPS URL.
400Currency <CUR> is not supported by <Provider>That studio does not operate in the player's currency.Launch a game from a studio that supports the currency. See Currencies.
400LuckySports not availableSportsbook not enabled for your account.Contact your account manager.
401API key requiredAuthorization header absent.Send Authorization: Bearer <key>.
401Invalid API keyKey not recognised.Check the key; regenerate if leaked.
401Invalid credentialsWrong panel email or password.Re-enter credentials.
401Session expired. Please login again.Panel JWT expired.Log in again.
403GGR balance lowPrepaid balance is at or below zero.Top up. Launches resume immediately — no restart needed.
403IP not whitelistedSource IP not in your allow-list.Add the egress IP of your server.
403Client account is suspendedAccount suspended.Contact your account manager.
403Account inactive. Please contact support.Account not activated.Contact support.
403Player blockedPlayer status is blocked.Unblock the player, or route them elsewhere.
404Game not foundUnknown game_uid, or not enabled for your currency.Re-read /api/game/list. Check currency support.
404Player not foundNo such player on your account.Check ext_player_id; launching creates it automatically.
500Launch failedUpstream provider rejected the session.Retry with backoff. Persisting? Send us the timestamp and game_uid.
500Failed to get game URLProvider unreachable or timed out.Retry with backoff.
500100HP launch failed100HP provider error.Retry with backoff.
500Internal errorUnexpected server fault.Retry. Persisting? Contact support with the timestamp.

Wallet codes (seamless responses)

Use these in the code field when your seamless endpoint returns "status": "error".

CodeMeaningWhen to return it
2001Insufficient balancePlayer cannot cover the debit. Do not use for a force: true rollback.
1006Player not foundext_player_id unknown on your side.
5001Seamless errorGeneric failure. Appixa fails the transaction.

Troubleshooting

Every launch returns 403 GGR balance low

Your prepaid balance has run out. Top it up; the next launch succeeds straight away. This gate applies only to /api/game/launch — /player/balance and /player/deposit keep working so you can always inspect and fund the account.

Seamless signature never matches

Almost always a raw-body problem. Sign timestamp + raw_body using the bytes exactly as received. If your framework parses JSON before you see it, mount a raw body parser on the wallet route.

Launch fails and the player id looks fine

Check the characters. The game provider accepts only lowercase letters and digits in the player id, up to 40 characters. An id like player_001 or Player001 is rejected. Map your internal ids to a compliant form once and store the mapping — the player id identifies the wallet, so it must stay stable.

A game is missing from the list

Either it is not enabled for your account, or it does not support your currency. The catalogue syncs daily, so newly released titles appear within 24 hours.

Duplicate debits after a timeout

Retries are expected. Appixa deduplicates on txn_id, and your seamless endpoint must do the same — store txn_id with a unique constraint and replay the stored result.

Balances drift from your own records

Reconcile by round_id, not by individual transaction. A round can contain a bet, several wins, and a refund or cancellation; only the full set nets out correctly.