# Apogee API Reference

All endpoints accept and return `application/json`. All requests must be signed; see [INTEGRATION.md §3](./INTEGRATION.md#3-signing-requests).

**Base URLs:**
- Sandbox: `https://sandbox-api.apogeetech.net`
- Live:    `https://api.apogeetech.net`

**Live frontends:**
- Game: `https://apogeetech.net` (direct: `https://apogee-web-634190752875.europe-west1.run.app`)
- Admin: `https://admin.apogeetech.net` (staff only)

All monetary amounts are **integers in minor units** (cents, sats). All timestamps are ISO-8601 UTC.

---

## Platform endpoints (operator → Apogee)

### `POST /v1/sessions`
Create a game launch session.

**Request body:**
| Field        | Type    | Required | Description                                         |
|--------------|---------|----------|-----------------------------------------------------|
| `gameId`     | string  | yes      | Game ID from [GAMES.md](./GAMES.md).                |
| `playerId`   | string  | yes      | Opaque operator-side player ID. ≤ 64 chars.         |
| `playerName` | string  | no       | Display name for chat/leaderboards.                 |
| `currency`   | string  | yes      | ISO-4217.                                           |
| `balance`    | integer | yes      | Current balance in minor units.                     |
| `language`   | string  | no       | BCP-47. Default `en`.                               |
| `returnUrl`  | string  | no       | URL to send player to on exit.                      |
| `mode`       | enum    | no       | `real` · `demo` · `fun`. Default `real`.            |
| `country`    | string  | no       | ISO-3166 country code.                              |
| `ipAddress`  | string  | no       | Player IP, for fraud and geo.                       |

**Response 201:**
```json
{
  "sessionToken": "sess_01HXXXXXX",
  "launchUrl":    "https://apogeetech.net/play.html?token=sess_01HXXXXXX",
  "expiresAt":    "2026-04-11T12:30:00Z"
}
```

### `GET /v1/games`
List available games.

**Response 200:**
```json
{
  "games": [
    { "id": "skyward",    "name": "Skyward",       "type": "crash",   "status": "live", "rtp": 97 },
    { "id": "thermal",    "name": "Thermal",       "type": "crash",   "status": "beta", "rtp": 97 },
    { "id": "contrail",   "name": "Contrail",      "type": "crash",   "status": "live", "rtp": 99 },
    { "id": "apogeehot5", "name": "Apogee Hot 5",  "type": "slot",    "status": "live", "rtp": 96.5 },
    { "id": "hormuz",     "name": "Hormuz",         "type": "crash-step", "status": "live", "rtp": 97 },
    { "id": "apex",       "name": "Apex",           "type": "instant", "status": "live", "rtp": 97 }
  ]
}
```

### `GET /v1/games/{gameId}`
Full game descriptor including supported currencies, bet limits, RTP, and asset URLs.

### `GET /v1/rounds?gameId=&since=&limit=`
Historical round data. Requires `rounds.read` permission. Paginated via `cursor`.

### `POST /v1/sessions/{sessionToken}/close`
Force-end a session (e.g. operator logs the player out).

---

## Wallet endpoints (Apogee → operator)

You implement these at a base URL you configure in Admin Panel → Integration → **Wallet URL**. Apogee signs each request.

### `/wallet/balance`
**Request:**
```json
{
  "sessionToken": "sess_01HXXXXXX",
  "playerId":     "p_1234",
  "gameId":       "skyward",
  "currency":     "EUR",
  "requestId":    "req_01HYYY"
}
```
**Response 200:**
```json
{ "balance": 10000, "currency": "EUR" }
```

### `/wallet/debit`
Called when a player places a bet. Must be **idempotent on `txId`**.

**Request:**
```json
{
  "sessionToken": "sess_01HXXXXXX",
  "playerId":     "p_1234",
  "gameId":       "skyward",
  "currency":     "EUR",
  "roundId":      "rnd_8fxk9",
  "txId":         "tx_3bcd1",
  "requestId":    "req_01HYYY",
  "amount":       250
}
```
**Response 200:**
```json
{ "balance": 9750, "currency": "EUR", "txId": "tx_3bcd1" }
```
**Errors:**
| HTTP | code                 | when                                      |
|------|----------------------|-------------------------------------------|
| 401  | `invalid_signature`  | Signature mismatch or stale timestamp.    |
| 401  | `session_expired`    | Session token expired.                    |
| 402  | `insufficient_funds` | Player balance < amount.                  |
| 403  | `player_locked`      | Operator-side lock on the account.        |
| 409  | `currency_mismatch`  | Currency on the request ≠ session.        |
| 429  | `rate_limited`       | Too many requests. Apogee will backoff.   |

### `/wallet/credit`
Called on cashout / settlement. Idempotent on `txId`.

**Request:** same envelope as debit. The `txId` convention is `tx_<debit>.win`.

**Response 200:**
```json
{ "balance": 10750, "currency": "EUR", "txId": "tx_3bcd1.win" }
```

### `/wallet/rollback`
Cancels a previous debit or credit. Apogee invokes this on inconsistencies (rare) or on operator-initiated round void.

**Request:**
```json
{
  "sessionToken": "sess_01HXXXXXX",
  "playerId":     "p_1234",
  "roundId":      "rnd_8fxk9",
  "txId":         "tx_3bcd1",
  "requestId":    "req_01HZZZ"
}
```

**Response 200:**
```json
{ "balance": 10000, "currency": "EUR", "txId": "tx_3bcd1" }
```

If you've never seen the referenced `txId`, return **200 OK** with the current balance — treat the rollback as a no-op. Do not return 404.

---

## Error response shape

Errors share one envelope:

```json
{
  "code": "insufficient_funds",
  "message": "Player p_1234 balance 100 < requested 250.",
  "requestId": "req_01HYYY"
}
```

Apogee logs `requestId` on both sides so support can cross-reference.

---

## Rate limits

Test keys: **60 req/s** per endpoint.
Live keys: negotiated at onboarding; typically **500 req/s** sustained, **2,000 req/s** burst.

Apogee respects HTTP 429 with exponential backoff — if your wallet rate-limits, play slows for the affected player. Don't rate-limit `/wallet/rollback` ever.
