# Casino API — Client Integration Documentation

Base URL: `https://casinoapi.shop`
All endpoints live under `/api/public/v1/`.
Every response is JSON: `{ "code": 0, "msg": "Success", "data": { ... } }`
`code: 0` = success. Any other code = error, read `msg`.

---

## 1. Credentials

From **Client Panel → Settings** you get:

| Field | Meaning |
|---|---|
| `token` | Your API key. Send it with every request. |
| `secret` | Signs every callback with `X-Signature`. Keep it private. |
| `play currency` | The currency your players bet in. Fixed for your account. |
| `IP whitelist` | Your server's public IP(s). Required. |
| `callback URL` | Your endpoint that we POST every bet/win to. |

**Important:** all API calls must be made **from your server**, never from the
player's browser — otherwise the IP check fails and your token leaks.

---

## 2. IP whitelist

Add your server's public IP in **Client Panel → Settings → IP Whitelist**.
Single IPs (`203.0.113.10`) and CIDR ranges (`203.0.113.0/24`) are supported.
An empty list allows all IPs (not recommended for production).

Find your server IP:

```bash
curl https://api.ipify.org
```

---

## 3. Endpoints

### 3.1 Provider list

```
GET /api/public/v1/providers?token=YOUR_TOKEN
```

### 3.2 Game list

```
GET /api/public/v1/games?token=YOUR_TOKEN&limit=500&offset=0
```

Optional: `brand_id`, `q` (search), `currency_supported=1`.
Each game returns `external_uid` — this is the `game_uid` you use to launch.

### 3.3 GGR balance

```
GET /api/public/v1/ggr-balance?token=YOUR_TOKEN
```

Returns your remaining credit and your GGR rate. If it reaches 0, launches are
blocked until you top up USDT.

### 3.3b Player balance after the game (exit sync)

```
GET /api/public/v1/session?token=YOUR_TOKEN&session_id=SESSION_ID
GET /api/public/v1/session?token=YOUR_TOKEN&user_id=123
```

Call this on your return/exit page (the `return` URL you send at launch) to
VERIFY the player's balance:

```json
{
  "code": 0,
  "data": {
    "session_id": "...",
    "user_id": "123",
    "currency_code": "BDT",
    "start_balance": 1000,
    "balance": 1240.5,
    "total_bet": 300,
    "total_win": 540.5,
    "net": 240.5,
    "applied_net": 240.5,
    "pending_adjustment": 0,
    "pending_callbacks": 0,
    "fully_applied": true
  }
}
```

**Do not add money again on exit.** Every bet/win was already applied through
callbacks. `pending_adjustment` is always `0` — add nothing. `balance` here is
the **session** balance (money inside the game), not the player's wallet.

`launch` returns `session_id` — store it and pass it back here.

### 3.4 Demo (fun mode) launch

```
GET /api/public/v1/demo?token=YOUR_TOKEN&game_uid=GAME_UID
```

Returns a plain-text play URL. No player, no balance, no GGR charge.

### 3.4b Test mode (practice key)

Ask support for a **test API key** (or your account manager creates one for you).
A test key works with every endpoint above, but `launch` opens our built-in
practice game instead of a real-money game:

- No real money and no GGR charge — your credit is never touched.
- Every spin settles a round and POSTs the **same callback payload** as live
  play to your callback URL, so you can verify wallet sync end to end.
- `/api/public/v1/session` works for test sessions too.

When your integration passes, we switch you to a live key.

### 3.5 Real money launch

```
POST /api/public/v1/launch
Content-Type: application/json
```

```json
{
  "token": "YOUR_TOKEN",
  "user_id": "10001",
  "balance": 500.00,
  "game_uid": "GAME_UID",
  "currency_code": "USD",
  "language": "en",
  "return": "https://your-casino.com/lobby"
}
```

| Field | Required | Notes |
|---|---|---|
| `token` | yes | your API key |
| `user_id` | yes | digits only, unique per player |
| `balance` | yes | player's current balance in your wallet |
| `game_uid` | yes | from the game list |
| `currency_code` | no | must equal your play currency if sent |
| `language` | no | e.g. `en`, `bn`, `hi` |
| `return` | no | where the game's back button sends the player |

Response:

```json
{
  "code": 0,
  "msg": "Game launched successfully",
  "data": { "url": "https://...", "session_id": "uuid" }
}
```

Open `data.url` in an iframe or redirect the player to it.

---

## 4. Callback (seamless wallet)

Before launch we POST `verify_player`; then we POST every bet and win separately
to the **callback URL** saved in your panel.
This is how your wallet stays in sync.

Verify-player request and required response:

```json
{ "event": "verify_player", "member_account": "10001", "currency_code": "USD" }
{ "code": 0, "msg": "Success", "balance": 500.00 }
```

Request we send:

```json
{
  "event": "round",
  "callback_id": "stable-unique-event-id",
  "member_account": "10001",
  "game_uid": "GAME_UID",
  "game_name": "Sweet Bonanza",
  "game_round": "R-9912837",
  "serial_number": "unique-per-money-event",
  "bet_amount": 10.00,
  "win_amount": 25.00,
  "balance": 515.00,
  "currency_code": "USD",
  "timestamp": 1757500000
}
```

Rules for your endpoint:

1. Respond **HTTP 200** quickly with `{ "code": 0, "balance": NEW_BALANCE }`.
2. `callback_id` and `serial_number` are unique per real money event. A retry keeps both values unchanged: acknowledge it as success with the live balance, but do not move money twice.
3. Apply the **delta only**: `wallet = wallet - bet_amount + win_amount`, atomically. Never SET the wallet from a payload field. `session_balance` is informational (what the game shows for this session) — the wallet is the player's whole balance and is a different number.
4. Never trust `member_account` blindly — map it to your own player record.
5. Use HTTPS. Your endpoint must be publicly reachable.
6. **Verify the signature.** Every callback carries an `X-Signature` header:
   `HMAC-SHA256(raw request body, your API key secret)` as hex. Reject any
   request whose signature does not match.

### Ready-made PHP handler (download)

A complete, production-ready PHP callback handler (signature verification,
idempotency, balance update, bet logging) is included in this package:

- **[Callback.php](./Callback.php)** — drop it on your server, set
  `API_SECRET` to your **API key secret** (shown in your panel next to the
  key), point the DB config at your database, and set that file's public URL
  as your callback URL. Adjust the table/column names to your schema.

### PHP callback example

```php
<?php
// callback.php
$raw  = file_get_contents('php://input');
$data = json_decode($raw, true);

$serial = $data['callback_id'] ?? null;
$player = $data['member_account'] ?? null;
if (!$serial || !$player) { http_response_code(400); exit; }

$pdo = new PDO('mysql:host=localhost;dbname=casino', 'user', 'pass');

// idempotency
$stmt = $pdo->prepare('SELECT 1 FROM api_rounds WHERE serial = ?');
$stmt->execute([$serial]);
if ($stmt->fetch()) { echo 'OK'; exit; }

$bet = (float)($data['bet_amount'] ?? 0);
$win = (float)($data['win_amount'] ?? 0);

$pdo->beginTransaction();
$pdo->prepare('INSERT INTO api_rounds (serial, player, bet, win) VALUES (?,?,?,?)')
    ->execute([$serial, $player, $bet, $win]);
$pdo->prepare('UPDATE players SET balance = balance - ? + ? WHERE id = ?')
    ->execute([$bet, $win, $player]);
$pdo->commit();

http_response_code(200);
echo 'OK';
```

### Node.js callback example

```js
app.post('/casino/callback', express.json(), async (req, res) => {
  const { callback_id, member_account, bet_amount = 0, win_amount = 0 } = req.body;
  if (!callback_id || !member_account) return res.sendStatus(400);
  if (await rounds.exists(callback_id)) return res.send('OK'); // duplicate
  await rounds.insert({ callback_id, member_account });
  await players.applyDelta(member_account, win_amount - bet_amount);
  res.send('OK');
});
```

---

## 5. Full launch flow (PHP)

```php
<?php
$token = 'YOUR_TOKEN';

function api_post($path, $body) {
  $ch = curl_init('https://casinoapi.shop' . $path);
  curl_setopt_array($ch, [
    CURLOPT_POST => true,
    CURLOPT_RETURNTRANSFER => true,
    CURLOPT_HTTPHEADER => ['Content-Type: application/json'],
    CURLOPT_POSTFIELDS => json_encode($body),
    CURLOPT_TIMEOUT => 20,
  ]);
  $res = curl_exec($ch); curl_close($ch);
  return json_decode($res, true);
}

$player = $_SESSION['player']; // your logged-in player

$r = api_post('/api/public/v1/launch', [
  'token'     => $token,
  'user_id'   => (string)$player['id'],
  'balance'   => (float)$player['balance'],
  'game_uid'  => $_GET['game_uid'],
  'language'  => 'en',
  'return'    => 'https://your-casino.com/lobby',
]);

if (($r['code'] ?? 1) !== 0) { die('Launch failed: ' . ($r['msg'] ?? 'unknown')); }
header('Location: ' . $r['data']['url']);
```

Embed instead of redirect:

```html
<iframe src="<?= htmlspecialchars($r['data']['url']) ?>"
        allowfullscreen style="width:100%;height:100vh;border:0"></iframe>
```

---

## 6. Billing (GGR)

- We charge only on **positive GGR**: `max(0, bet - win) × your GGR rate`.
- Charges are deducted from your USDT credit automatically per round.
- Top up in **Client Panel → Deposit USDT**.
- Check remaining credit any time with `/api/public/v1/ggr-balance`.

---

## 7. Error codes

| code | Meaning | Fix |
|---|---|---|
| 401 | Missing / invalid token, or account inactive | check token in panel |
| 403 | IP not allowed | whitelist your server IP |
| 404 | Game not allowed / not found | use `game_uid` from the game list |
| 400 | Invalid JSON, decrypt failed, timestamp expired | check request body |
| 1 | Business error (currency mismatch, low GGR, provider not allowed) | read `msg` |
| 1008 | Game has no demo mode | use real money launch |
| 500 | Session could not be created | retry, then contact support |

---

## 8. Go-live checklist

- [ ] Server IP whitelisted
- [ ] Callback URL saved and returning HTTP 200
- [ ] Settings → Test connection passes
- [ ] `verify_player` returns the player's real current balance
- [ ] Duplicate `serial_number` handling implemented
- [ ] Game list cached on your side (refresh daily)
- [ ] Balance sent on launch matches your wallet
- [ ] USDT credit topped up
- [ ] Tested with a real player account in a real game

Support: **Client Panel → Support**.
