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

# Soccer

> Event vocabulary and match state for the soccer feed — World Cup group stage

The soccer feed covers live matches including the FIFA World Cup. 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>
  This API covers **group-stage matches only**. Extra time and penalty-shootout support is in development and will be documented when available.
</Note>

***

## Match state

Every WebSocket frame carries a full `MatchState` 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`, `match_end`, `undo`, `scout_status`) shared across every sport.

### Score

| Field              | Type                       | Description                                                                                                                          |
| ------------------ | -------------------------- | ------------------------------------------------------------------------------------------------------------------------------------ |
| `score`            | `{ a: number, b: number }` | Running score, derived from events. Increases on `goal` and `pen_scored`; decreases on `goal_disallowed`. Overwritten by `score_set` |
| `scoreUnderReview` | boolean                    | `true` when a VAR review with subject `goal` or `goal_line` is open. Treat the current score as provisional while this is `true`     |

### Phase and clock

Soccer runs on a continuous clock, so there is no separate live/dead clock state. `phase` is the broad match segment.

| Field   | Type    | Description                                                                                                                                                       |
| ------- | ------- | ----------------------------------------------------------------------------------------------------------------------------------------------------------------- |
| `phase` | string  | Current match segment. See [Phase values](#phase-values)                                                                                                          |
| `live`  | boolean | `true` while play is running. Updated on every restart — cleared by `dead`, `half_end`, and `match_end`; set by `kickoff`, `live`, `free_kick`, and `corner_kick` |

### Location

| Field        | Type                           | Description                                                                                                                                |
| ------------ | ------------------------------ | ------------------------------------------------------------------------------------------------------------------------------------------ |
| `zone`       | `"a_half" \| "b_half" \| null` | Which half the ball is in. `null` until the scout first reports location. See [Zone values](#zone-values)                                  |
| `inBox`      | boolean                        | Whether the ball is in the penalty area of the current `zone`. `false` until set by a `location` press                                     |
| `possession` | `"A" \| "B" \| null`           | Who has the ball. Set by every `location` press; cleared by `goal` and `kickoff`. `null` between phases or before the first location event |

### Pending slots

Both slots are independent and can be non-null at the same time (e.g. a penalty awarded while a VAR review is open).

| Field             | Type                      | Description                                                                                                                                                                                                                                                                                                                                              |
| ----------------- | ------------------------- | -------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- |
| `pendingSetPiece` | `PendingSetPiece \| null` | A dangerous restart is armed. Non-null from `free_kick_awarded` / `corner` / `penalty_awarded` until the delivery resolves it                                                                                                                                                                                                                            |
| `openVar`         | `OpenVar \| null`         | A VAR review is in progress. Non-null from `var_review` until `var_resolve` (the only event that closes it). Any corrections that arrive while it is open — `goal_disallowed`, `card_rescinded`, `foul_rescinded`, and fresh events — take effect immediately but leave the review open, so an overturn with several effects all lands within one review |

**`PendingSetPiece`**

| Field  | Type             | Description                                                                                            |
| ------ | ---------------- | ------------------------------------------------------------------------------------------------------ |
| `kind` | string           | `"free_kick"`, `"corner"`, or `"penalty"`                                                              |
| `team` | `"A" \| "B"`     | The side that will take the restart                                                                    |
| `zone` | `string \| null` | Zone where the free kick is taken from — only present for free kicks; `null` for corners and penalties |

**`OpenVar`**

| Field     | Type         | Description                                                                                                                                            |
| --------- | ------------ | ------------------------------------------------------------------------------------------------------------------------------------------------------ |
| `varId`   | string       | Unique id for this review — stable across every frame the review is open, so you can tell one review apart from a later one with the same subject/team |
| `subject` | string       | What is under review: `"goal"`, `"goal_line"`, `"penalty_shout"`, or `"red_card"`. The first two raise `scoreUnderReview`                              |
| `team`    | `"A" \| "B"` | The side the review concerns                                                                                                                           |

### Discipline

| Field            | Type                       | Description                                                                                                                                                                                         |
| ---------------- | -------------------------- | --------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- |
| `playersOnPitch` | `{ a: number, b: number }` | Players on the pitch per side. Starts at 11; decrements on `red_card`, restored (+1, capped at 11) by a `card_rescinded` red. A side reaching 6 cannot continue — the match is abandoned to `FINAL` |
| `yellowCards`    | `{ a: number, b: number }` | Cumulative yellow card count per side. Useful for second-yellow-risk tracking                                                                                                                       |
| `redCards`       | `{ a: number, b: number }` | Cumulative red card count per side (straight red or second yellow)                                                                                                                                  |

### Display metadata

The side display names are **not** on `MatchState`. 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 and `playersOnPitch` counts stay on the abstract `A`/`B` sides.

***

## Enum values

### Phase values

| Value     | Meaning                                               |
| --------- | ----------------------------------------------------- |
| `PREGAME` | Before kickoff                                        |
| `1H`      | First half in play                                    |
| `HT`      | Half-time break                                       |
| `2H`      | Second half in play                                   |
| `FT`      | Full-time (90 min) — terminal for group-stage matches |
| `FINAL`   | Match over — terminal state                           |

### Zone values

`zone` is just which half the ball is in. Combine it with `inBox` (whether it's in the penalty area) and `possession` (who has the ball) for the full picture:

| `zone`   | `inBox` | `possession` | Situation                       |
| -------- | ------- | ------------ | ------------------------------- |
| `a_half` | `true`  | `"B"`        | B in A's box — high danger      |
| `a_half` | `false` | `"B"`        | B pressing in A's half          |
| `a_half` | `false` | `"A"`        | A safe in own half — low danger |
| `b_half` | `false` | `"B"`        | B safe in own half              |
| `b_half` | `false` | `"A"`        | A pressing in B's half          |
| `b_half` | `true`  | `"A"`        | A in B's box — high danger      |

***

## Events

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

All payload field names are camelCase on the wire.

***

### Location

<AccordionGroup>
  <Accordion title="location — ball position update">
    Sets `state.zone`, `state.inBox`, and `state.possession` in a single press — three separate facts stated together. `zone` is which half the ball is in; `in_box` is whether it is in the penalty area; `team` is who has the ball. Pure context — it never changes the score, phase, or `live`.

    | Field    | Type         | Description                                          |
    | -------- | ------------ | ---------------------------------------------------- |
    | `zone`   | string       | Which half — `"a_half"` or `"b_half"`                |
    | `in_box` | boolean      | Whether the ball is in the penalty area of that half |
    | `team`   | `"A" \| "B"` | The side with possession                             |

    ```json theme={null}
    { "type": "location", "payload": { "zone": "a_half", "in_box": true, "team": "B" } }
    ```
  </Accordion>

  <Accordion title="panic — imminent goal signal">
    The scout's earliest signal that a goal is likely *now* — a striker through on goal, a goalmouth scramble. It fires *before* the shot, ahead of any factual event. The scout re-taps it to keep it active while the danger lasts, and it fades once they stop. It does not change any state.

    | Field  | Type         | Description              |
    | ------ | ------------ | ------------------------ |
    | `team` | `"A" \| "B"` | The side likely to score |

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

***

### Shots and goals

<AccordionGroup>
  <Accordion title="shot — goal attempt on its way">
    A goal attempt is in flight. The scout reports each attempt and follows it with either `missed_shot` (no goal) or `goal`. Multiple shots can chain in a single attack — each gets its own `shot` + resolution. A `goal` can also arrive without a preceding `shot` (tap-ins, deflections, scrambles).

    | Field  | Type         | Description        |
    | ------ | ------------ | ------------------ |
    | `team` | `"A" \| "B"` | The attacking side |

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

  <Accordion title="missed_shot — shot did not score">
    The shot produced no goal — saved, blocked, or off-target. The scout reports this after each `shot` that doesn't result in a `goal`. Saved vs. blocked vs. off-target is not distinguished.

    | Field  | Type         | Description        |
    | ------ | ------------ | ------------------ |
    | `team` | `"A" \| "B"` | The attacking side |

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

  <Accordion title="goal — ball in the net">
    The ball is in the net — increases `score[team]` right away. If a VAR review is open, treat the score as provisional.

    A `goal` is independent of `shot` — it never needs a preceding shot. `state.scoreUnderReview` tells you whether the score is provisional.

    | Field  | Type         | Description      |
    | ------ | ------------ | ---------------- |
    | `team` | `"A" \| "B"` | The scoring side |

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

  <Accordion title="goal_disallowed — VAR chalks off the goal">
    The goal under VAR review is chalked off — decreases `score[team]`. This is the normal way the score goes down. It does **not** close the review (it is a correction, not a resolution): only `var_resolve` does that.

    Different from `undo`: `goal_disallowed` records a real refereeing decision, while `undo` takes back a scout's mistaken press.

    | Field  | Type         | Description                        |
    | ------ | ------------ | ---------------------------------- |
    | `team` | `"A" \| "B"` | The side whose goal was disallowed |

    ```json theme={null}
    { "type": "goal_disallowed", "payload": { "team": "A" } }
    ```
  </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": 2, "b": 1 } }
    ```
  </Accordion>
</AccordionGroup>

***

### Fouls and set pieces

<AccordionGroup>
  <Accordion title="foul — foul committed">
    A foul was committed. It only records the foul and sets up nothing on its own. The scout reports the resulting restart separately: `free_kick_awarded` outside the box, `penalty_awarded` inside it.

    | Field  | Type         | Description                          |
    | ------ | ------------ | ------------------------------------ |
    | `team` | `"A" \| "B"` | The side that **committed** the foul |
    | `zone` | string       | Which half the foul occurred in      |

    ```json theme={null}
    { "type": "foul", "payload": { "team": "B", "zone": "b_half" } }
    ```
  </Accordion>

  <Accordion title="free_kick_awarded — free kick given">
    A free kick is awarded to the fouled side. Arms `pendingSetPiece` as a free kick. The scout asserts this explicitly — it is never derived from a `foul`. Stage one of a two-stage sequence: `free_kick_awarded` → `free_kick`.

    | Field  | Type         | Description                                             |
    | ------ | ------------ | ------------------------------------------------------- |
    | `team` | `"A" \| "B"` | The side awarded the free kick                          |
    | `zone` | string       | Where the kick is taken from — `"a_half"` or `"b_half"` |

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

  <Accordion title="free_kick — free kick taken">
    The free kick is being taken — ball is live. Sets `live` and clears `pendingSetPiece`. The delivery of the awarded free kick.

    No payload fields.

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

  <Accordion title="foul_rescinded — a pending set piece is taken back">
    A VAR overturn clears the pending set piece, whatever it holds (free kick, corner, or penalty) — the set-piece equivalent of `goal_disallowed`. It takes no payload, since there is only ever one pending set piece to clear. It is a correction, not a resolution, so it does not close the review.

    No payload fields.

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

  <Accordion title="corner — corner kick awarded">
    A corner kick is awarded. Arms `pendingSetPiece` as a corner.

    | Field  | Type         | Description                 |
    | ------ | ------------ | --------------------------- |
    | `team` | `"A" \| "B"` | The side awarded the corner |

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

  <Accordion title="corner_kick — corner kick taken">
    The corner kick is being taken — ball is live. Sets `live` and clears `pendingSetPiece`. Distinct from `corner` (the award): the award is the dead-ball lull before the restart; the kick is the live-ball release that ends it.

    | Field  | Type         | Description              |
    | ------ | ------------ | ------------------------ |
    | `team` | `"A" \| "B"` | The side taking the kick |

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

  <Accordion title="penalty_awarded — penalty given">
    A penalty is given — the most dangerous restart short of a goal. Arms `pendingSetPiece` as a penalty. Stage one of a three-stage sequence: `penalty_awarded` → `pen_kick` → `pen_scored` | `pen_missed`.

    | Field  | Type         | Description                  |
    | ------ | ------------ | ---------------------------- |
    | `team` | `"A" \| "B"` | The side awarded the penalty |

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

  <Accordion title="pen_kick — taker stepping up">
    The penalty taker is stepping up — reported before the outcome, where slower feeds report only the result.

    | Field    | Type             | Description                                          |
    | -------- | ---------------- | ---------------------------------------------------- |
    | `team`   | `"A" \| "B"`     | The taking side                                      |
    | `kicker` | `number \| null` | Jersey number of the taker. `null` if not identified |

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

  <Accordion title="pen_scored — penalty converted">
    Resolves the penalty: it is converted. Increases `score`. No `team` field — it inherits from the open penalty.

    No payload fields.

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

  <Accordion title="pen_missed — penalty not scored">
    Resolves the penalty: it was saved or off-target. Clears `pendingSetPiece`. No `team` field — it inherits from the open penalty. Whether it was saved or off-target is left to the slower official feed.

    No payload fields.

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

***

### VAR

<AccordionGroup>
  <Accordion title="var_review — VAR freeze">
    The referee is reviewing. Opens `openVar`; if `subject` is `"goal"` or `"goal_line"`, it also sets `scoreUnderReview`. The review opening is itself the signal, before any outcome.

    Only `var_resolve` closes it. Corrections that arrive while the review is open (`goal_disallowed`, `card_rescinded`, `foul_rescinded`, and fresh `goal` / `penalty_awarded` / `red_card`) take effect immediately but leave the review open, so an overturn with several effects all stays within one review until it resolves.

    Every frame from the review's opening through its resolution carries the same `state.openVar.varId`, so you can tell which review is in progress without tracking anything client-side.

    | Field     | Type         | Description                                                                       |
    | --------- | ------------ | --------------------------------------------------------------------------------- |
    | `subject` | string       | What is under review: `"goal"`, `"goal_line"`, `"penalty_shout"`, or `"red_card"` |
    | `team`    | `"A" \| "B"` | The side the review concerns                                                      |

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

  <Accordion title="var_resolve — the review closes">
    Closes the review. Clears `openVar` and `scoreUnderReview`, whatever the outcome. A review that changed nothing is just `var_review → var_resolve` with nothing in between; an overturn is the corrections (`goal_disallowed`, `card_rescinded`, `foul_rescinded`, or fresh events) followed by this resolve.

    No payload fields.

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

***

### Discipline

<AccordionGroup>
  <Accordion title="red_card — player sent off">
    A player is sent off (straight red or second yellow — the scout presses this either way). Decrements `playersOnPitch[team]` permanently and increments `redCards[team]`. A sending-off during a VAR review is a correction, not a resolution — it does **not** close the review; the freeze lifts only on `var_resolve`.

    A side reduced to **six** players cannot continue (Law 3): the match is abandoned, with `phase → FINAL` and `live` cleared. Six is the floor — a further red against a six-player side does nothing.

    | Field  | Type         | Description      |
    | ------ | ------------ | ---------------- |
    | `team` | `"A" \| "B"` | The side reduced |

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

  <Accordion title="yellow_card — caution">
    A caution. Increments `yellowCards[team]`. Use for second-yellow-risk tracking.

    | Field  | Type         | Description        |
    | ------ | ------------ | ------------------ |
    | `team` | `"A" \| "B"` | The cautioned side |

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

  <Accordion title="card_rescinded — a card is taken back">
    A VAR overturn reverses a card (the disciplinary equivalent of `goal_disallowed`). A `red` restores a player to `playersOnPitch[team]` (+1, capped at 11); a `yellow` removes one from `yellowCards[team]`. A red downgraded to a yellow is `card_rescinded` (`red`) followed by `yellow_card`. It is a correction, not a resolution, so it does not close the review.

    | Field  | Type                | Description                      |
    | ------ | ------------------- | -------------------------------- |
    | `team` | `"A" \| "B"`        | The side whose card is rescinded |
    | `card` | `"red" \| "yellow"` | Which card is taken back         |

    ```json theme={null}
    { "type": "card_rescinded", "payload": { "team": "A", "card": "red" } }
    ```
  </Accordion>
</AccordionGroup>

***

### Phase and clock

<AccordionGroup>
  <Accordion title="kickoff — half begins">
    Starts a half. Moves out of a break phase along a fixed path:

    `PREGAME → 1H`, `HT → 2H`

    Sets `live`. Carries no payload — it states one thing: the half is underway.

    No payload fields.

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

  <Accordion title="half_end — half whistle">
    The whistle that ends a half. Moves into the break phase along a fixed path:

    `1H → HT`, `2H → FT`

    Clears `live`. The next `kickoff` moves out of the break.

    No payload fields.

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

  <Accordion title="dead — ball out of play">
    The ball is out of play. Clears `live`. Pressed for any stoppage — throw-ins, goal-kicks, and longer breaks (serious injury, long VAR delay, cooling break) alike. Play resumes on the next `live`.

    No payload fields.

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

  <Accordion title="live — play resumes mid-half">
    Play has resumed after a `dead` stoppage mid-half. Sets `live`. For between-phase resumes use `kickoff` (it also advances the phase). For set-piece resumes use `free_kick` or `corner_kick` (they also clear `pendingSetPiece`).

    No payload fields.

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

  <Accordion title="match_end — match over">
    Terminal: `phase → FINAL`. Disarms any pending phase advance. Consumers should close the WebSocket after receiving this — no further frames will arrive.

    No payload fields.

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

  <Accordion title="phase — phase correction">
    Sets the phase directly — use it to fix a phase that has drifted. Normal phase changes happen through `half_end` and `kickoff`; this event is for corrections only and should be rare.

    | Field   | Type   | Description                                          |
    | ------- | ------ | ---------------------------------------------------- |
    | `phase` | string | The correct phase — any [Phase value](#phase-values) |

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

***

### Server advisory

<AccordionGroup>
  <Accordion title="scout_status — liveness signal">
    Server-originated — not a scout press. Emitted by the liveness monitor when the scout's status transitions. `WireEvent.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.

    Different from `goal_disallowed`: `undo` corrects a scout's mistake, while `goal_disallowed` records a real refereeing event.

    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>
