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

# API Reference

> REST endpoints and the WebSocket protocol.

Base URL (local): `http://localhost:8000` · WebSocket: `ws://localhost:8000/ws`

## REST

### `GET /matches`

Returns matches, most-recent first.

**Query**: `limit` (1–100, default 50). **No `offset`/cursor**: the endpoint only ever returns the most-recent window (the frontend fetches `limit=100` and paginates client-side). Paging past the top 100 would require cursor pagination, see [Frontend](/architecture/frontend). Not built.

```json theme={null}
{ "data": [
  { "id": 1, "sport": "football", "homeTeam": "Arsenal", "awayTeam": "Chelsea",
    "status": "live", "startTime": "2026-06-16T15:00:00.000Z", "endTime": "2026-06-16T17:00:00.000Z",
    "homeScore": 2, "awayScore": 1, "createdAt": "2026-06-16T14:55:00.000Z" } 
] }
```

### `POST /matches`

Creates a match. `status` is **derived** from `startTime`/`endTime`, not supplied. Broadcasts `match_created` to all WS clients.

**Body**: `sport`, `homeTeam`, `awayTeam`, `startTime` (ISO), `endTime` (ISO, must be after `startTime`); optional `homeScore`, `awayScore` (default 0). → `201 { data }`, or `400` on invalid input.

### `PATCH /matches/:id/score`

Sets a match's live score and broadcasts `score_update` to **all** WS clients (scores show on every client's grid card, so it's global, not room-scoped).

**Body**: required `homeScore`, `awayScore` (non-negative ints). → `200 { data }` (the updated match), `400` on invalid id/body, `404` if the match doesn't exist.

### `GET /matches/:id/commentary`

Returns a match's commentary, most-recent first. **Query**: `limit` (1–100). → `200 { data }`, `400` on bad id/query.

### `POST /matches/:id/commentary`

Inserts a commentary event, broadcasts `commentary` to subscribers of that match.

**Body**: required `sequence` (positive int), `eventType`, `message`; optional `minute`, `period`, `actor`, `team`, `tags`, `metadata`. → `201 { data }`, `400` on invalid input.

<Warning>
  Posting to a **non-existent** match currently returns `500` (foreign-key violation), not `404`. Known gap, see [ISSUE-004](/issues).
</Warning>

## WebSocket protocol

Connect to `/ws`. Full walkthrough in [Real-Time Deep Dive](/architecture/realtime).

| Direction | Message                                                                   |
| --------- | ------------------------------------------------------------------------- |
| S→C       | `{ "type": "welcome" }`                                                   |
| C→S       | `{ "type": "subscribe", "matchId": 1 }`                                   |
| S→C       | `{ "type": "subscribed", "matchId": 1 }`                                  |
| C→S       | `{ "type": "unsubscribe", "matchId": 1 }`                                 |
| S→C       | `{ "type": "match_created", "data": { ...match } }` (all clients)         |
| S→C       | `{ "type": "commentary", "data": { ...event } }` (match subscribers only) |
| S→C       | `{ "type": "error", "message": "Invalid JSON" }`                          |

<Note>
  This is hand-written from the route code. A future improvement is generating an **OpenAPI spec** from the existing Zod schemas (`zod-to-openapi`), making the validation rules the single source of truth for these docs and enabling an interactive playground.
</Note>
