> ## 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.

# Quickstart

> Backfill the tape, open the WebSocket, stay in sync

This walks through the full consumer path, using a soccer session with id `T1` as the example.

## 1. Backfill the tape

When you connect, the WebSocket sends the current state but no history. So on first load, fetch the full event log from `/history` to fill in what already happened, then add live frames on top.

```bash theme={null}
curl "https://mm.akaramarkets.com/api/history/T1?token=$CONSUMER_TOKEN"
```

```json theme={null}
{
  "contractVersion": "v3",
  "sport": "soccer",
  "league": "wc",
  "sessionId": "T1",
  "matchup": { "a": "Brazil", "b": "Argentina" },
  "events": [
    {
      "type": "goal",
      "payload": { "team": "A" },
      "seq": 12,
      "serverIngestTs": 1718630400.12
    }
  ]
}
```

`events` is the full log, oldest to newest, where each entry is the same `WireEvent` a live frame carries. The state is not returned here — and you don't compute it: the WebSocket you open next delivers the current [match snapshot](/sports/soccer#match-state) on connect and a fresh one on every frame. `/history` fills in the tape of what already happened. The team display names come from the top-level `matchup` `{a, b}`, so you can render the header straight from it. The schema identity is the pair (`sport`, `contractVersion`) — the version the log was recorded under counts per sport — so compare both to know how to interpret the events; older games stay readable even after the schema changes. See the [History backfill](/api-reference/history) for the full field reference.

## 2. Open the WebSocket

```javascript theme={null}
const ws = new WebSocket(
  `wss://mm.akaramarkets.com/api/subscribe/T1?token=${process.env.CONSUMER_TOKEN}`
);

ws.onmessage = (msg) => {
  const frame = JSON.parse(msg.data);

  // Control messages are not frames: the hello (schema identity — sport +
  // contract version) opens every subscription, an error message precedes
  // a deliberate close (4404/4403 — stop; 1011 — server-side outage, retry),
  // a ping arrives ~every 15s as a keepalive (ignore it), and session_closed
  // announces the normal end of the game (a 4000 close follows).
  if (frame.type === "hello" || frame.type === "error" || frame.type === "ping"
      || frame.type === "session_closed") return;

  // Replace your snapshot wholesale — every frame is authoritative.
  updateSnapshot(frame.state);

  // The connect frame has event === null. Every subsequent frame carries the triggering press.
  if (frame.event !== null) {
    appendToTape(frame.event);
  }
};

ws.onclose = (e) => {
  if (e.code === 4000) {
    // Normal end: the game is over (a {"type": "session_closed"} message arrived
    // just before this). Every session ends this way. Do NOT reconnect; the full
    // tape stays readable via GET /api/history/T1.
    return;
  }
  if (e.code === 4404 || e.code === 4403) {
    // Deliberate refusal — game over, never published, or not entitled. Do NOT
    // reconnect (the answer won't change); fetch GET /api/history/T1 for the tape.
    return;
  }
  // Anything else is transient (network drop, server restart): reconnect and
  // re-backfill from /history. An immediate failure with no frames at all usually
  // means the token was rejected (the handshake itself is refused with HTTP 403,
  // observed in a browser as a failed connection) — fix it, don't loop.
};
```

The first message is the hello (`{"type": "hello", "contractVersion": ..., "sport": ..., "league": ...}`); then comes the snapshot you get on connecting: `event` is `null` and `state` is the current match state. Every frame after that carries both the `event` that happened and the full `state` that resulted. See the [WebSocket feed](/api-reference/websocket) — including the [close codes](/api-reference/websocket#close-codes) that decide when reconnecting is correct.

## 3. Stay in sync

The pattern is always the same:

* **Replace** your snapshot with `frame.state` on every frame — never reconstruct state from events yourself.
* **Append** `frame.event` to your tape when it is non-null.
* **On reconnect**, re-backfill from `/history` (dedup by `seq` against your existing tape) then re-subscribe.

`frame.outSeq` always increases. A gap means you missed a frame, but there is nothing to resend — the next frame's `state` already brings you back in sync.

## 4. Reading the snapshot

Steps 1–3 are identical for every sport — only the snapshot fields differ. This example session is soccer, so the fields below are soccer's:

```javascript theme={null}
function updateSnapshot(state) {
  const score = `${state.score.a}–${state.score.b}`;
  const provisional = state.scoreUnderReview; // goal under VAR — treat score as provisional
  const zone = state.zone;                    // "a_half" | "b_half" | null
  const inBox = state.inBox;                  // true = ball is in the penalty area
  const possession = state.possession;        // "A" | "B" | null
  const varFreeze = state.openVar;            // non-null = VAR review open
  const setPiece = state.pendingSetPiece;     // non-null = set piece pending
  // ...
}
```

See [Match State](/sports/soccer#match-state) for every field and [Soccer Events](/sports/soccer#events) for the full event vocabulary. Consuming a different sport? The integration is the same — start from that sport's state instead: [Basketball](/sports/basketball#game-state) (`clock`, `period`, `openAttempt`) or [Baseball](/sports/baseball#game-state) (`inning`, `outs`, `bases`).
