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

# Basketball

> Event vocabulary and game state for the basketball feed — NBA and WNBA

The basketball feed covers live NBA and WNBA games — both leagues speak the same event vocabulary and state shape; the catalog row's `league` slug (`nba` / `wnba`) tells them apart. Feed reads carry no sport in the URL — the [catalog](/api-reference/catalog), [WebSocket](/api-reference/websocket), [history backfill](/api-reference/history), and [session health](/api-reference/health) are all top-level endpoints addressed by session id.

<Note>
  Basketball state is split into **independent parts** — possession and the clock move separately. During free throws, for example, the ball is dead but the shooting team still has possession, so one combined status field couldn't capture both. Each part has its own event.
</Note>

***

## Game state

Every WebSocket frame carries a full `GameState` snapshot. The history backfill returns events only — to rebuild this state from the log, replay the events; each event's entry below specifies its exact effect on state. Fields are camelCase on the wire. See the [Sports overview](/sports/overview) for the conventions and cross-sport events (`score_set`, `game_end`, `undo`, `scout_status`) shared across every sport.

### Score

| Field   | Type                       | Description                                                                                                                                    |
| ------- | -------------------------- | ---------------------------------------------------------------------------------------------------------------------------------------------- |
| `score` | `{ a: number, b: number }` | Running score, derived from events. Increases on `made` by the shot's point value (`2pt` → 2, `3pt` → 3, `ft` → 1); overwritten by `score_set` |

### Clock and period

The clock tracks whether play is live or stopped. `LIVE` is set only by a `live` press; the stopped states (`DEAD`, `BREAK`, `TIMEOUT`) are each pressed directly.

| Field          | Type    | Description                                                                                                                                                    |
| -------------- | ------- | -------------------------------------------------------------------------------------------------------------------------------------------------------------- |
| `clock`        | string  | Live/dead state. See [Clock values](#clock-values)                                                                                                             |
| `period`       | integer | Current period (1-indexed). Overtime is just `period > 4`. Advances automatically on the `period_end` → `live` quarter tip; set directly by the `period` event |
| `ftsRemaining` | integer | Free throws still to be shot this trip. `0` = not in free throws; `1`/`2`/`3` = that many pending. "In free throws" is exactly `ftsRemaining > 0`              |

### Possession

| Field        | Type                 | Description                                                                                                                                                                                      |
| ------------ | -------------------- | ------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------ |
| `possession` | `"A" \| "B" \| null` | Who has the ball. Set only by `possession`. `null` before the opening tip                                                                                                                        |
| `tipoffTeam` | `"A" \| "B" \| null` | The side given the ball on the first possession of the game — set once and never changed. Carries the NBA possession-arrow rule so the quarter tip can auto-assign. `null` until the opening tip |

### Shot attempt

At most one attempt is open at a time. A `made` or `miss` resolves it, and self-describes the shot it resolved on its own event payload — see [`made`](#shots-and-scoring) below. The state snapshot itself only ever carries the *open* attempt, not the most recent resolution.

| Field         | Type                  | Description                                                                                                                                                                                                         |
| ------------- | --------------------- | ------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- |
| `openAttempt` | `OpenAttempt \| null` | The ball in the air. Non-null from a shot attempt until `made` or `miss` resolves it (or an out-of-play press or possession change clears it). `null` when no shot is in the air. See [`OpenAttempt`](#openattempt) |

**`OpenAttempt`**

| Field      | Type         | Description                                                                |
| ---------- | ------------ | -------------------------------------------------------------------------- |
| `team`     | `"A" \| "B"` | The shooter's side, as sent on the opening `*_attempt` event's own payload |
| `shotType` | string       | `"2pt"`, `"3pt"`, or `"ft"`. See [Shot-type values](#shot-type-values)     |

### Display metadata

The side display names are **not** on `GameState`. They are fixed at session creation and ride the top-level `matchup` `{a, b}` on every WS frame and on the `/api/history` / `/api/health` responses (from the session manifest). Scoring stays on the abstract `A`/`B` sides.

***

## Enum values

### Clock values

| Value     | Meaning                                                        |
| --------- | -------------------------------------------------------------- |
| `PREGAME` | Before the opening tip — boot state                            |
| `LIVE`    | Play is running                                                |
| `DEAD`    | A same-period pause (foul, out-of-bounds, held ball)           |
| `BREAK`   | A between-period intermission (end of quarter/half/regulation) |
| `TIMEOUT` | A timeout-length pause                                         |
| `FINAL`   | Game over — terminal state                                     |

### Shot-type values

| Value | Points | Meaning                |
| ----- | ------ | ---------------------- |
| `2pt` | 2      | Two-point field goal   |
| `3pt` | 3      | Three-point field goal |
| `ft`  | 1      | Free throw             |

***

## Events

Events arrive on the WebSocket as `frame.event`. The `type` field is the discriminator; `payload` carries the event's fields minus `type`.

State snapshot field names are camelCase; event payload field names are snake\_case (`shot_type`, not `shotType`).

***

### Possession and clock

<AccordionGroup>
  <Accordion title="possession — who has the ball">
    Sets `state.possession` — the only event that does. It can be set while the ball is dead (an inbound during a timeout) and never moves the clock. The first `possession` of the game also fixes `tipoffTeam` (the possession arrow).

    | Field  | Type         | Description            |
    | ------ | ------------ | ---------------------- |
    | `team` | `"A" \| "B"` | The side with the ball |

    ```json theme={null}
    { "type": "possession", "payload": { "team": "A" } }
    ```
  </Accordion>

  <Accordion title="live — ball in play">
    The only event that starts the clock. Resumes to `LIVE` from `PREGAME` (tip-off) or any stopped state (`DEAD`/`BREAK`/`TIMEOUT`), and clears the free-throw countdown (play resuming means the trip is over). Resuming from a `BREAK` is the quarter tip: it applies the period advance the `period_end` press set up, so `period` increases here. Nothing else starts the clock — possession and shot attempts never do.

    No payload fields.

    ```json theme={null}
    { "type": "live", "payload": {} }
    ```
  </Accordion>

  <Accordion title="dead — same-period pause">
    Ball out of play — a brief pause (foul, out-of-bounds, held ball). Pressing it stops play directly. It's the general-purpose stop; the scout records the cause afterward while the ball is dead (`to_the_line` → `award_free_throws` for a foul). Leaves the period unchanged; play resumes on the next `live`.

    No payload fields.

    ```json theme={null}
    { "type": "dead", "payload": {} }
    ```
  </Accordion>

  <Accordion title="period_end — the current period ended">
    Ball out of play — end of a quarter, half, or regulation. When it fires, `period` still shows the period that just ended, so the display can read "End of Q1". It sets up the next advance, so the next `live` press — the quarter tip — increases `period`. The scout never types the new number for a normal rollover.

    No payload fields.

    ```json theme={null}
    { "type": "period_end", "payload": {} }
    ```
  </Accordion>

  <Accordion title="timeout — timeout pause">
    Ball out of play — a timeout, called out as its own event so the tape reads it as a timeout rather than a generic dead ball. Self-contained: pressing it stops play directly. Carries no team or count — only that play paused is recorded. A same-period pause (like `dead`); play resumes on the next `live`.

    No payload fields.

    ```json theme={null}
    { "type": "timeout", "payload": {} }
    ```
  </Accordion>
</AccordionGroup>

***

### Shots and scoring

<AccordionGroup>
  <Accordion title="2pt_attempt — two-point field goal up">
    A live two-point field goal is in the air — opens an attempt. The shot type is the event; `team` is the shooter's side. If the clock is not `LIVE`, this is flagged as an anomaly — the scout likely missed the `live` press.

    | Field  | Type         | Description        |
    | ------ | ------------ | ------------------ |
    | `team` | `"A" \| "B"` | The shooter's side |

    ```json theme={null}
    { "type": "2pt_attempt", "payload": { "team": "A" } }
    ```
  </Accordion>

  <Accordion title="3pt_attempt — three-point field goal up">
    A live three-point field goal is in the air — opens an attempt. Behaves exactly like `2pt_attempt`; only the point value differs.

    | Field  | Type         | Description        |
    | ------ | ------------ | ------------------ |
    | `team` | `"A" \| "B"` | The shooter's side |

    ```json theme={null}
    { "type": "3pt_attempt", "payload": { "team": "A" } }
    ```
  </Accordion>

  <Accordion title="ft_attempt — free-throw attempt">
    A free-throw attempt — opens an attempt. A one-point shot taken while the ball is dead. Unlike a field goal, it does not resume `LIVE`. A free throw while the clock is `LIVE`, or with no count awarded, is flagged as an anomaly.

    | Field  | Type         | Description        |
    | ------ | ------------ | ------------------ |
    | `team` | `"A" \| "B"` | The shooter's side |

    ```json theme={null}
    { "type": "ft_attempt", "payload": { "team": "A" } }
    ```
  </Accordion>

  <Accordion title="made — shot made">
    Resolves the open attempt and increases the score by the shot's point value. A resolved free throw also lowers `ftsRemaining` by one. Self-describes the shot it resolved — `team` and `shot_type` ride the event's own payload (matching the attempt that opened it), so you don't need the state snapshot to attribute the row.

    | Field       | Type         | Description                                                            |
    | ----------- | ------------ | ---------------------------------------------------------------------- |
    | `team`      | `"A" \| "B"` | The shooter's side                                                     |
    | `shot_type` | string       | `"2pt"`, `"3pt"`, or `"ft"`. See [Shot-type values](#shot-type-values) |

    ```json theme={null}
    { "type": "made", "payload": { "team": "A", "shot_type": "3pt" } }
    ```
  </Accordion>

  <Accordion title="miss — shot missed">
    Resolves the open attempt with no score change. A resolved free throw still lowers `ftsRemaining` by one. It deliberately leaves the clock and possession unchanged — any change of possession comes as a separate `possession` press. Self-describes the shot it resolved, same as `made`.

    | Field       | Type         | Description                 |
    | ----------- | ------------ | --------------------------- |
    | `team`      | `"A" \| "B"` | The shooter's side          |
    | `shot_type` | string       | `"2pt"`, `"3pt"`, or `"ft"` |

    ```json theme={null}
    { "type": "miss", "payload": { "team": "B", "shot_type": "2pt" } }
    ```
  </Accordion>

  <Accordion title="score_set — manual score correction">
    Overwrites the running score. Scoring continues from the new values. Used to correct a drifted score; rare during normal play.

    | Field | Type    | Description          |
    | ----- | ------- | -------------------- |
    | `a`   | integer | New score for side A |
    | `b`   | integer | New score for side B |

    ```json theme={null}
    { "type": "score_set", "payload": { "a": 88, "b": 90 } }
    ```
  </Accordion>
</AccordionGroup>

***

### Free throws

<AccordionGroup>
  <Accordion title="to_the_line — foul awards free throws">
    A foul that awards free throws has been called — a player is heading to the line. Stops the clock (a foul makes the ball dead) and signals that free throws are coming. The count follows in a separate `award_free_throws` once it's known; this event carries no count of its own.

    No payload fields.

    ```json theme={null}
    { "type": "to_the_line", "payload": {} }
    ```
  </Accordion>

  <Accordion title="award_free_throws — set the free-throw count">
    The number of free throws, sent once it's known. `count` is the number of shots (1, 2, or 3 in the NBA). For a shooting foul, your client can derive it from the field goal just taken (made → 1, the and-one; missed 2pt → 2; missed 3pt → 3); for a technical or team foul, the scout states it. Sets `ftsRemaining`.

    `count: 0` is the **cancel** — it sets `ftsRemaining` back to 0, calling off free throws that were signalled but never happened. It only resets the countdown; it does not touch the clock.

    | Field   | Type    | Description                                                                  |
    | ------- | ------- | ---------------------------------------------------------------------------- |
    | `count` | integer | Number of free throws awarded — `0`, `1`, `2`, or `3` (`0` cancels the trip) |

    ```json theme={null}
    { "type": "award_free_throws", "payload": { "count": 2 } }
    ```
  </Accordion>
</AccordionGroup>

***

### Period and corrections

<AccordionGroup>
  <Accordion title="period — period correction">
    Sets the period directly to the stated value (overtime is just `period > 4`). Use it to fix a period that has drifted, or to label start-of-quarter free throws before they're shot. Normal quarter changes happen on the `period_end` → `live` tip. This clears any pending advance, so the next `live` does not also increase the period.

    | Field    | Type    | Description               |
    | -------- | ------- | ------------------------- |
    | `period` | integer | The correct period number |

    ```json theme={null}
    { "type": "period", "payload": { "period": 4 } }
    ```
  </Accordion>

  <Accordion title="game_end — game is final">
    Terminal: `clock → FINAL`. Consumers should close the WebSocket after receiving this — no further frames will arrive.

    No payload fields.

    ```json theme={null}
    { "type": "game_end", "payload": {} }
    ```
  </Accordion>
</AccordionGroup>

***

### Server advisory

<AccordionGroup>
  <Accordion title="scout_status — liveness signal">
    Comes from the server, not from a scout. The liveness monitor sends it when the scout's status changes. `seq` is `null` on these frames.

    | Field           | Type             | Description                                                                                                      |
    | --------------- | ---------------- | ---------------------------------------------------------------------------------------------------------------- |
    | `status`        | string           | `"dark"` — scout not seen recently, no new events expected. `"live"` — scout is active again                     |
    | `lastHeartbeat` | `number \| null` | Unix timestamp (seconds) of the last heartbeat received from the scout. `null` if none seen yet for this session |

    ```json theme={null}
    { "type": "scout_status", "payload": { "status": "dark", "lastHeartbeat": 1718632800.0 } }
    ```

    See [Core Concepts — Advisories](/concepts#advisories).
  </Accordion>
</AccordionGroup>

***

### Meta

<AccordionGroup>
  <Accordion title="undo — retract last press">
    Retracts the most recent scout press. Both the original press and the `undo` appear in `/history` as raw events — a client replaying the log resolves it there: each `undo` cancels the most recent non-`undo` event. An undo can lower the score or change possession back — the one case where a scout press reverses earlier state.

    The scout sends no payload fields; the served payload carries the server-stamped `retractsSeq` — the `seq` of the press the undo cancelled (`null` when nothing remained) — on the live frame and in `/history` alike, so a display can strike the retracted row without replaying.

    ```json theme={null}
    { "type": "undo", "payload": { "retractsSeq": 41 } }
    ```
  </Accordion>
</AccordionGroup>
