> ## Documentation Index
> Fetch the complete documentation index at: https://docs.akaramarkets.com/llms.txt
> Use this file to discover all available pages before exploring further.

# Sandbox

> Trigger a private mock session — a replayed real game — to test your integration end-to-end

The sandbox lets you drive the entire feed surface on demand, without waiting for a real live
game: one `POST` mints a **mock session** that replays a recorded real game through the production
pipeline. It is a real session in every observable way — it appears in your catalog, streams real
frames over the real WebSocket, ends with a real `session_closed`, and stays readable via
[`/history`](/api-reference/history) — so what you test is exactly what production sends. Wire it
into your CI: trigger, discover, subscribe, assert your state folds, observe the close.

<Note>
  Mock sessions are **private to your account** and **never appear in default catalog listings** —
  your production pipeline cannot confuse one for a real game (see
  [Telling mocks apart](#telling-mocks-apart)). Any authenticated account can trigger one; no league
  entitlement is required.
</Note>

## Trigger a mock session

```
POST /api/sandbox/sessions
```

```bash theme={null}
curl -X POST https://mm.akaramarkets.com/api/sandbox/sessions \
  -H "Authorization: Bearer ss_your_api_key" \
  -H "Content-Type: application/json" \
  -d '{"league": "mlb", "speed": 5}'
```

### Request body

| Field    | Type               | Description                                                                                                                                                                      |
| -------- | ------------------ | -------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- |
| `league` | string             | Which league's tape to replay (e.g. `mlb`, `nba`, `wc`). The mock session carries this league, so your league-filtering code runs realistically. Unknown league → `422`.         |
| `speed`  | integer (optional) | Playback multiplier, default `5`. A real game's event gaps are divided by this — at `5`, a \~2.5 h game replays in \~30 min. Must be ≥ 1; values above `60` are clamped to `60`. |

### Response

```json theme={null}
{
  "sessionId": "mlb-k3vX9q2LmPwd8",
  "league": "mlb",
  "speed": 5,
  "endsAt": "2026-07-13T15:12:44+00:00"
}
```

`endsAt` is the projected end of the replay. The session then ends exactly like a real game:
subscribers receive `{"type": "session_closed"}` and a close with code `4000`, and the session
moves to the catalog's `closed` slice.

### Semantics

* **One active mock per account.** Triggering while you already have one running **replaces** it:
  the old mock is force-ended (it moves to `status=closed`) and the response names the new one.
  Your CI can always trigger unconditionally — a leaked session from a crashed run can't wedge it.
* **The tape is a real recorded game** — one per sport, fixed — including scout corrections:
  expect `undo` events (with `retractsSeq`) and `score_set` corrections mid-stream. That is the
  point; those are the frames integrations get wrong.
* **Determinism:** every run of a league's tape replays the same events in the same order with the
  same state transitions. Only timestamps differ — they are stamped fresh at replay time, so your
  staleness/latency logic sees live-shaped data.
* **Game-clock values** inside event payloads (period, soccer minute) come from the recording and
  will not match wall clock at `speed` ≠ 1 — inherent to accelerated replay.
* A server restart mid-replay abandons the mock; it is force-ended shortly after (lazily). Just
  trigger again.

## The full test flow

1. `POST /api/sandbox/sessions` with your normal credential.
2. Poll `GET /api/sessions?status=open&sandbox=true` until the `sessionId` appears
   ([catalog](/api-reference/catalog) — note the `sandbox` parameter).
3. Connect `WS /api/subscribe/{sessionId}` — the [hello frame](/api-reference/websocket#connection-flow)
   carries `"sandbox": true`.
4. Fold frames through your reducer; exercise your gap detection against `outSeq`.
5. Observe `{"type": "session_closed"}` + close code `4000` when the tape ends.
6. `GET /api/history/{sessionId}` — the ended mock replays in full (also stamped `"sandbox": true`);
   assert your cold-load backfill reproduces the same terminal state.

## Telling mocks apart

Three guarantees keep a mock out of your production data path:

* **Default catalog listings exclude mocks.** `GET /api/sessions` never returns sandbox sessions
  unless you pass `?sandbox=true` — and that mode returns *only* your own mocks, no real games.
* **The hello frame and `/history` response carry `"sandbox": true`** on mock sessions. The key is
  **absent** on real games (never `false`), so existing parsers are unaffected. Check it wherever a
  session id arrives outside catalog context.
* **The matchup is unmistakable:** every mock plays `"Sandbox A"` vs `"Sandbox B"`.

## Access model

The trigger grants your account an entitlement on the minted session. No other consumer can see
or subscribe to it (their subscribe attempt is refused `4403`). Accounts with zero entitlements can
use the sandbox, which makes it a way to build your integration before going live.
