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

# GET /matches — List Matches with Odds Available

> GET /v1/pinnacle/matches returns metadata for every game currently priced in one sport, with each game's in-play derivatives included. Prices are excluded — fetch them from /odds or the stream.

Returns the matches currently held in the book for one sport, as **metadata only**. This is the endpoint you use to discover match ids; prices come from [`GET /odds`](/passthrough/market-odds) or the [live stream](/passthrough/stream).

```text theme={null}
GET https://passthrough.sportrix.ai/v1/pinnacle/matches?sport=12&league=100
```

**Scope:** `odds` · **Sportsbook grant:** `pinnacle`

<Warning>
  Prices are deliberately **not** included here. `markets` is always `null` on this endpoint, no matter how many markets the match actually has. Listing the whole book with prices attached would be a large response that is stale the moment it is serialized.
</Warning>

## Query parameters

| Parameter | Type    | Required | Description                                                                          |
| --------- | ------- | -------- | ------------------------------------------------------------------------------------ |
| `sport`   | integer | **Yes**  | Sport id from [`GET /sports`](/passthrough/sports). Omitting it returns `400`        |
| `league`  | integer | No       | Restrict to one league id. Omit or pass `0` for all leagues in the sport             |
| `include` | string  | No       | Pass `specials` to return every derivative matchup too. Omit to get the default view |

<Note>
  `sport` is **required**. A match list is only useful per sport, and requiring it means one cached, gzipped response can be reused for every caller granted that sport. Call [`GET /sports`](/passthrough/sports) first for the ids you hold.
</Note>

<Note>
  Results are filtered by your key's [per-sport grants](/passthrough/authentication#per-sport-grants). With no `sport` filter — including `?league=<id>` on its own — matches in ungranted sports are silently absent. With `?sport=<id>` for a sport you do not hold, this endpoint returns `403 {"error": "API key not permitted for sport <id>"}`. Match ids you discover here are always safe to pass to [`GET /odds`](/passthrough/market-odds) and to [`WS /data`](/passthrough/stream) subscribes.
</Note>

## Request

```bash theme={null}
curl "https://passthrough.sportrix.ai/v1/pinnacle/matches?sport=12" \
  -H "X-API-Key: sk_your_api_key_here"
```

Fetch the whole book for a sport, including every derivative:

```bash theme={null}
curl "https://passthrough.sportrix.ai/v1/pinnacle/matches?sport=3&include=specials" \
  -H "X-API-Key: sk_your_api_key_here"
```

## What the response contains

By default the response is scoped to what you almost certainly want:

* **Games** — every top-level matchup for the sport.
* **Live derivative twins** — when a fixture goes in-play the sportsbook mints a *new* matchup id for the live book and parents it to the prematch one. Those in-play children are always listed, because a parent-only filter would hide the entire live book. Each carries `parent_id` back to its prematch entry.
* **Parentless specials** — a special with no parent (an outright, for example) is itself top-level and is always listed.

Every top-level entry carries `specials_count` and `live_specials_count`, so you can tell at a glance how many derivatives a game has and how many of them are currently in play.

Pass `?include=specials` to return the entire catalog — every derivative matchup, not just the in-play twins. On a busy card the difference is large (a live baseball sport goes from \~17 KiB by default to \~479 KiB with `include=specials`), so only opt in when you actually want to enumerate the whole book.

## Response

A bare JSON array, sorted ascending by `id`. No matches held returns `[]`.

```json theme={null}
[
  {
    "id": 1632570636,
    "sport_id": 12,
    "type": "matchup",
    "league_id": 100,
    "league_name": "CS2 - ESL Pro League",
    "home": "T1",
    "away": "FURIA",
    "participants": [
      { "id": 101, "name": "T1", "alignment": "home", "order": 0 },
      { "id": 102, "name": "FURIA", "alignment": "away", "order": 1 }
    ],
    "start_time": "2026-07-27T18:00:00Z",
    "status": "started",
    "is_live": true,
    "version": 8412,
    "specials_count": 51,
    "live_specials_count": 12,
    "markets": null,
    "updated_at": 1785110400123
  }
]
```

### Match object

| Field                 | Type    | Description                                                                                                                               |
| --------------------- | ------- | ----------------------------------------------------------------------------------------------------------------------------------------- |
| `id`                  | integer | The sportsbook's match id. Pass it as `match` to [`GET /odds`](/passthrough/market-odds), or as `match_id` when subscribing on the stream |
| `sport_id`            | integer | Sport this match belongs to                                                                                                               |
| `type`                | string  | `matchup` for a normal head-to-head, `special` for a derivative market. Note that some derivatives are also `matchup` — see below         |
| `league_id`           | integer | League id                                                                                                                                 |
| `league_name`         | string  | League name                                                                                                                               |
| `home`                | string  | Home side name. Empty on specials                                                                                                         |
| `away`                | string  | Away side name. Empty on specials                                                                                                         |
| `parent_id`           | integer | For a derivative or in-play twin, the id of the matchup it derives from. Omitted otherwise                                                |
| `special`             | string  | For a `type:"special"` derivative, its description — for example `Correct Score`. Omitted otherwise                                       |
| `units`               | string  | Unit the derivative is expressed in, when the sportsbook supplies one — for example `Games` or `Sets` on tennis matchup derivatives       |
| `participants`        | array   | Competitors or outcomes. See below                                                                                                        |
| `start_time`          | string  | Scheduled start, ISO 8601 UTC                                                                                                             |
| `status`              | string  | The sportsbook's status string for the match                                                                                              |
| `is_live`             | boolean | Whether the match is in play                                                                                                              |
| `version`             | integer | The sportsbook's match-level version. Distinct from the per-market `version` on selections                                                |
| `specials_count`      | integer | On a top-level entry, the number of derivative matchups parented to it — of any `type`, including in-play twins                           |
| `live_specials_count` | integer | On a top-level entry, how many of those derivatives are currently in play. Use this to find the in-play twin without a full scan          |
| `markets`             | null    | Always `null` here — see the warning above                                                                                                |
| `updated_at`          | integer | Epoch milliseconds when SportriX last applied a change to this match                                                                      |

### Participant object

| Field       | Type    | Description                                                                                  |
| ----------- | ------- | -------------------------------------------------------------------------------------------- |
| `id`        | integer | Participant id. Markets reference outcomes by this id                                        |
| `name`      | string  | Team name on a matchup, or the outcome text on a special — for example `Leviatan 0, FURIA 2` |
| `alignment` | string  | `home` or `away`. Absent on specials, whose outcomes are neutral                             |
| `order`     | integer | The sportsbook's display order                                                               |

## Specials

A **special** is a derivative market that the sportsbook models as its own matchup — player props, correct score, winning margin, odd/even, and similar. A parent game's own book does not contain them; each derivative has its own id, its own participants, and its own markets.

The split between a game and its derivatives is on `parent_id`, not on `type`. A child's `type` varies by sport:

* On baseball and most sports, derivatives are `type:"special"` with a `special` description.
* On tennis, many derivatives are `type:"matchup"` carrying `units:"Games"` or `units:"Sets"` — around half of the tennis catalog in practice.

Both shapes are counted in `specials_count`, and both are included when you pass `?include=specials`. To tell whether an entry is a derivative, check `parent_id` rather than `type`.

### Reading a game and its derivatives

There are three ways to pull the derivatives for one game:

1. Read `specials_count` and `live_specials_count` on the game in the default list, then follow up with [`GET /odds?match=<game>&include=specials`](/passthrough/market-odds) to fetch the game's own selections and every derivative's selections in one call.
2. Ask for the whole catalog with `?include=specials` here, then filter by `parent_id`.
3. Subscribe to the game on the [live stream](/passthrough/stream) — subscribing to a game covers its derivatives too.

## Caching, gzip, and ETag

Because this response is metadata rather than prices, it is safe to serve from a short-lived cache — and it is the only Passthrough endpoint that is cached.

* **Cache TTL.** Responses are reused for up to `PINNODDS_REST_CACHE_TTL` (default **1 second**) across every client granted that sport. Worst-case staleness is therefore one TTL, and only for fields like `is_live`, `specials_count`, and the match list itself. Prices never appear here, so the trade-off is bounded and safe.
* **Gzip.** Responses above `PINNODDS_REST_GZIP_MIN_BYTES` (default 1 KiB) are compressed if you send `Accept-Encoding: gzip`. On a live baseball card this is a roughly 17× reduction — 212 KB raw to about 13 KB on the wire.
* **ETag.** Every response carries an `ETag` header. Send it back on the next request as `If-None-Match` and the server answers `304 Not Modified` with an empty body when the response would be identical.

```bash theme={null}
# First request: cache and store the ETag.
curl -sD - "https://passthrough.sportrix.ai/v1/pinnacle/matches?sport=3" \
  -H "X-API-Key: sk_your_api_key_here" \
  -H "Accept-Encoding: gzip" \
  --compressed | grep -i etag

# Follow-up: send the ETag and expect 304 while nothing has changed.
curl -sI "https://passthrough.sportrix.ai/v1/pinnacle/matches?sport=3" \
  -H "X-API-Key: sk_your_api_key_here" \
  -H 'If-None-Match: "abc123"'
```

Nothing that carries a price is cached: [`GET /odds`](/passthrough/market-odds) and the [WebSocket](/passthrough/stream) are always live.

## Errors

| Status | Body                                            | Cause                                                                                     |
| ------ | ----------------------------------------------- | ----------------------------------------------------------------------------------------- |
| `400`  | `{"error": "sport query param is required, …"}` | `sport` missing or zero. The error text points you to `/sports`                           |
| `401`  | `{"error": "invalid or inactive API key"}`      | Missing, unknown, or inactive API key                                                     |
| `403`  | `{"error": "…"}`                                | Key lacks the `odds` scope or the `pinnacle` grant, or is not granted the requested sport |
| `503`  | `{"error": "auth backend unavailable"}`         | Authentication backend unavailable                                                        |
