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

# Games

> UUID-based per-game endpoints returning all markets × all bookmakers in a single matrix view.

## Overview

The Games endpoints give you a **single call** for everything happening in one match: every market type (moneyline, spread, totals, halftime, props) × every bookmaker, laid out in a matrix with Pinnacle as the reference column.

This is the fastest way to build a comparison view — no joins, no multiple calls.

### How game IDs work

Every game has a stable `game_id` UUID derived from `(competition, match_name)`:

```
game_id = md5(competition + '::' + lower(trim(match_name)))::uuid
```

The same game always gets the same UUID — store it in your DB, use it in WebSocket filters, or share it in URLs. It never changes while the game is live.

***

## GET /api/games

List all games currently in the odds database.

### Query Parameters

| Parameter     | Type    | Default | Description                                          |
| ------------- | ------- | ------- | ---------------------------------------------------- |
| `sport`       | string  | —       | Filter by sport name (e.g. `Football`, `Basketball`) |
| `competition` | string  | —       | Filter by competition code (e.g. `EPL`, `NBA`)       |
| `bookmaker`   | string  | —       | Only games where this bookmaker has odds             |
| `is_live`     | boolean | —       | `true` = in-play only, `false` = pre-match only      |
| `limit`       | integer | `200`   | Max games to return, up to 500                       |

### Response

```json theme={null}
{
  "data": [
    {
      "game_id": "a3f1d2e4-b5c6-7890-abcd-ef1234567890",
      "match_name": "PSG - Lyon",
      "competition": "LIG1",
      "sport": "Football",
      "match_date": "2026-04-20T20:00:00Z",
      "is_live": false,
      "bookmaker_count": 5,
      "last_scraped_at": "2026-04-20T14:30:00Z"
    }
  ],
  "meta": {
    "count": 47,
    "rate_limit_remaining": 199
  }
}
```

### Response Fields

| Field             | Type             | Description                                      |
| ----------------- | ---------------- | ------------------------------------------------ |
| `game_id`         | UUID string      | Stable deterministic ID for this game            |
| `match_name`      | string           | Match name as stored in odds database            |
| `competition`     | string           | Competition code (e.g. `EPL`, `NBA`, `LIG1`)     |
| `sport`           | string           | Sport name derived from competition code         |
| `match_date`      | ISO-8601 \| null | Scheduled kick-off time (UTC)                    |
| `is_live`         | boolean          | Whether any bookmaker has this as live right now |
| `bookmaker_count` | integer          | Number of bookmakers with active odds            |
| `last_scraped_at` | ISO-8601 \| null | Most recent odds update for this game            |

### Code Example

```bash theme={null}
# All live Football games
curl "https://api.oddsstream.io/api/games?sport=Football&is_live=true" \
  -H "X-Api-Key: os_live_YOUR_KEY"
```

```python theme={null}
import requests

games = requests.get(
    "https://api.oddsstream.io/api/games",
    params={"sport": "Football", "competition": "EPL"},
    headers={"X-Api-Key": "os_live_YOUR_KEY"},
).json()["data"]

for g in games:
    print(g["game_id"], g["match_name"], g["bookmaker_count"], "books")
```

***

## GET /api/games/\{game\_id}

All odds for one game — every market type × every bookmaker in a single **matrix response**.

Markets are grouped by `(market_type, period)`. Within each market, selections are rows and bookmakers are columns, with Pinnacle always shown separately.

### Path Parameter

| Parameter | Description                |
| --------- | -------------------------- |
| `game_id` | UUID from `GET /api/games` |

### Query Parameters

| Parameter     | Type    | Default | Description                                           |
| ------------- | ------- | ------- | ----------------------------------------------------- |
| `market_type` | string  | —       | Filter to one market type (e.g. `moneyline`, `total`) |
| `period`      | integer | —       | Filter to one period (0=full-time, 1=first-half)      |
| `bookmaker`   | string  | —       | Include only one bookmaker's prices                   |

### Response

```json theme={null}
{
  "data": {
    "game_id": "a3f1d2e4-b5c6-7890-abcd-ef1234567890",
    "match_name": "PSG - Lyon",
    "competition": "LIG1",
    "sport": "Football",
    "match_date": "2026-04-20T20:00:00Z",
    "is_live": false,
    "all_bookmakers": ["Betsson", "Unibet.fr", "Winamax"],
    "markets": [
      {
        "market_type": "moneyline",
        "period": 0,
        "period_label": "Full Time",
        "bookmakers": ["Betsson", "Unibet.fr", "Winamax"],
        "pinnacle_scraped_at": "2026-04-20T14:29:45Z",
        "selections": [
          {
            "selection": "PSG",
            "pinnacle": { "odds": 1.72, "liquidity": 4200, "scraped_at": "2026-04-20T14:29:45Z" },
            "books": {
              "Betsson":   { "odds": 1.78, "scraped_at": "2026-04-20T14:28:00Z", "url": null },
              "Unibet.fr": { "odds": 1.75, "scraped_at": "2026-04-20T14:27:30Z", "url": null },
              "Winamax":   { "odds": 1.80, "scraped_at": "2026-04-20T14:29:10Z", "url": null }
            }
          },
          {
            "selection": "Draw",
            "pinnacle": { "odds": 3.85, "liquidity": 1800, "scraped_at": "2026-04-20T14:29:45Z" },
            "books": {
              "Betsson":   { "odds": 3.90, "scraped_at": "2026-04-20T14:28:00Z", "url": null },
              "Unibet.fr": { "odds": 3.80, "scraped_at": "2026-04-20T14:27:30Z", "url": null },
              "Winamax":   { "odds": 3.95, "scraped_at": "2026-04-20T14:29:10Z", "url": null }
            }
          },
          {
            "selection": "Lyon",
            "pinnacle": { "odds": 4.50, "liquidity": 1200, "scraped_at": "2026-04-20T14:29:45Z" },
            "books": {
              "Betsson":   { "odds": 4.60, "scraped_at": "2026-04-20T14:28:00Z", "url": null },
              "Unibet.fr": null,
              "Winamax":   { "odds": 4.55, "scraped_at": "2026-04-20T14:29:10Z", "url": null }
            }
          }
        ]
      },
      {
        "market_type": "total",
        "period": 0,
        "period_label": "Full Time",
        "bookmakers": ["Betsson", "Winamax"],
        "pinnacle_scraped_at": "2026-04-20T14:29:45Z",
        "selections": [
          {
            "selection": "Over 2.5",
            "pinnacle": { "odds": 2.05, "liquidity": 3500, "scraped_at": "2026-04-20T14:29:45Z" },
            "books": {
              "Betsson": { "odds": 2.10, "scraped_at": "2026-04-20T14:28:00Z", "url": null },
              "Winamax": { "odds": 2.08, "scraped_at": "2026-04-20T14:29:10Z", "url": null }
            }
          }
        ]
      }
    ]
  },
  "meta": {
    "rate_limit_remaining": 198
  }
}
```

### Response Fields

**Top-level `data`:**

| Field            | Type             | Description                            |
| ---------------- | ---------------- | -------------------------------------- |
| `game_id`        | UUID string      | Stable game identifier                 |
| `match_name`     | string           | Match name                             |
| `competition`    | string           | Competition code                       |
| `sport`          | string           | Sport name                             |
| `match_date`     | ISO-8601 \| null | Kick-off time (UTC)                    |
| `is_live`        | boolean          | Whether the game is currently live     |
| `all_bookmakers` | string\[]        | All bookmakers with odds for this game |
| `markets`        | MarketMatrix\[]  | All markets in matrix format           |

**`MarketMatrix` object:**

| Field                 | Type             | Description                                                       |
| --------------------- | ---------------- | ----------------------------------------------------------------- |
| `market_type`         | string           | Market type (`moneyline`, `spread`, `total`, `player_prop`, etc.) |
| `period`              | integer          | Period (0=full-time, 1=first-half, 2=second-half)                 |
| `period_label`        | string           | Human-readable period (e.g. `"Full Time"`, `"1st Half"`)          |
| `bookmakers`          | string\[]        | Bookmakers with odds for this specific market                     |
| `pinnacle_scraped_at` | ISO-8601 \| null | When Pinnacle's prices were last updated for this market          |
| `selections`          | MatrixRow\[]     | One row per outcome                                               |

**`MatrixRow` object:**

| Field       | Type                                      | Description                                         |
| ----------- | ----------------------------------------- | --------------------------------------------------- |
| `selection` | string                                    | Outcome name (e.g. `"PSG"`, `"Over 2.5"`, `"Draw"`) |
| `pinnacle`  | PinnacleCell \| null                      | Pinnacle's reference price                          |
| `books`     | `Record<bookmaker, BookOddsCell \| null>` | Each bookmaker's price (null = no odds available)   |

**`PinnacleCell`:**

| Field        | Type             | Description                                  |
| ------------ | ---------------- | -------------------------------------------- |
| `odds`       | float \| null    | Decimal odds                                 |
| `liquidity`  | float \| null    | Pinnacle max bet — proxy for line confidence |
| `scraped_at` | ISO-8601 \| null | When this price was scraped                  |

**`BookOddsCell`:**

| Field        | Type             | Description                              |
| ------------ | ---------------- | ---------------------------------------- |
| `odds`       | float \| null    | Decimal odds                             |
| `scraped_at` | ISO-8601 \| null | When this price was scraped              |
| `url`        | string \| null   | Direct link to the market (if available) |

### Code Examples

```bash theme={null}
# All markets for a game
curl "https://api.oddsstream.io/api/games/a3f1d2e4-b5c6-7890-abcd-ef1234567890" \
  -H "X-Api-Key: os_live_YOUR_KEY"

# Only moneyline full-time
curl "https://api.oddsstream.io/api/games/a3f1d2e4-b5c6-7890-abcd-ef1234567890?market_type=moneyline&period=0" \
  -H "X-Api-Key: os_live_YOUR_KEY"
```

```python theme={null}
import requests

def get_game(game_id: str, api_key: str) -> dict:
    return requests.get(
        f"https://api.oddsstream.io/api/games/{game_id}",
        headers={"X-Api-Key": api_key},
    ).json()["data"]

game = get_game("a3f1d2e4-b5c6-7890-abcd-ef1234567890", "os_live_YOUR_KEY")

for market in game["markets"]:
    print(f"\n{market['market_type']} — {market['period_label']}")
    for row in market["selections"]:
        pin = row["pinnacle"]
        pin_str = f"Pinnacle: {pin['odds']}" if pin else "Pinnacle: —"
        book_strs = ", ".join(
            f"{bk}: {cell['odds']}" if cell else f"{bk}: —"
            for bk, cell in row["books"].items()
        )
        print(f"  {row['selection']:20s}  {pin_str}  |  {book_strs}")
```

```javascript theme={null}
const game = await fetch(
  `https://api.oddsstream.io/api/games/${gameId}`,
  { headers: { "X-Api-Key": "os_live_YOUR_KEY" } }
).then(r => r.json()).then(r => r.data);

for (const market of game.markets) {
  console.log(`\n${market.market_type} — ${market.period_label}`);
  for (const row of market.selections) {
    const pin = row.pinnacle?.odds ?? "—";
    const books = Object.entries(row.books)
      .map(([bk, cell]) => `${bk}: ${cell?.odds ?? "—"}`)
      .join(" | ");
    console.log(`  ${row.selection.padEnd(20)} Pinnacle: ${pin} | ${books}`);
  }
}
```

***

## Using game\_id with WebSocket

Subscribe to live updates for a single game by passing `game_id` as a query parameter when connecting:

```javascript theme={null}
// Step 1: get a short-lived token (5-min TTL)
const { token } = await fetch("https://oddsstream.io/api/stream/token", {
  method: "POST",
  headers: { "X-Api-Key": "os_live_YOUR_KEY" },
}).then(r => r.json());

// Step 2: connect with token + game_id filter
const ws = new WebSocket(
  `wss://api.oddsstream.io/api/stream?token=${token}&game_id=a3f1d2e4-b5c6-7890-abcd-ef1234567890`
);

ws.onmessage = (event) => {
  const msg = JSON.parse(event.data);
  // msg.bookmaker is "Pinnacle" for Pinnacle changes (includes msg.liquidity)
  // msg.bookmaker is the bookmaker name for comparison book changes
  console.log(`${msg.bookmaker} changed ${msg.market_type} ${msg.selection} → ${msg.odds}`);
};
```

<Tip>
  Combine `GET /api/games/{id}` on page load (full snapshot) with a `game_id`-filtered WebSocket connection for real-time updates — you get a complete live odds comparison view with a single REST call and one WebSocket.
</Tip>

<CardGroup cols={2}>
  <Card title="WebSocket Guide" icon="plug" href="/guides/websocket-setup">
    Full tutorial for building real-time odds feeds.
  </Card>

  <Card title="Value Bets" icon="bolt" href="/api-reference/value-bets">
    Pre-calculated +EV opportunities against Pinnacle.
  </Card>
</CardGroup>
