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

# Fetching Odds

> Fetch and compare betting odds from 365+ bookmakers. Covers single events, batch requests, filtering by sport/league, and real-time updates.

## Overview

The Odds-API.io provides several endpoints for fetching odds data from multiple bookmakers. This guide covers best practices and common patterns for working with odds data.

<Note>
  For an up-to-date list of all supported bookmakers, visit [odds-api.io/sportsbooks](https://odds-api.io/sportsbooks).
</Note>

## Which endpoint should I use?

| I need to...                              | Use                | Why                                                                                                                                        |
| ----------------------------------------- | ------------------ | ------------------------------------------------------------------------------------------------------------------------------------------ |
| Fetch one event on demand                 | `/v3/odds`         | Simplest call, one event at a time                                                                                                         |
| Fetch up to 10 events per call            | `/v3/odds/multi`   | Batches events into 1 request                                                                                                              |
| Keep odds fresh continuously              | **WebSocket**      | Real-time, compressed, filterable by `markets`, replays missed messages on reconnect, and does not count against your hourly request limit |
| Poll for changes without holding a socket | `/v3/odds/updated` | Fallback for callers that can't maintain a persistent connection                                                                           |

<Tip>
  If your app runs continuously, WebSocket is the right default. Reach for polling only when a persistent connection isn't an option.
</Tip>

## Basic Workflow

1. **Get available sports** - Fetch the list of supported sports
2. **Get leagues** - Retrieve leagues for your chosen sport
3. **Get events** - Find upcoming or live events
4. **Get odds** - Fetch odds from selected bookmakers
5. **Stay up to date** - Open the [WebSocket feed](/guides/websockets) to keep those odds current

## Recommended pattern: snapshot, then stream

Fetch your initial odds with `/v3/odds/multi`, then open a WebSocket connection to keep them current. Every `updated` message carries the full current market set for that event and bookmaker, so overwrite what you have stored. Never merge, or suspended markets will never disappear from your data.

<CodeGroup>
  ```javascript Node.js theme={null}
  import WebSocket from 'ws';

  const apiKey = process.env.ODDS_API_KEY;
  const eventIds = [123456, 123457, 123458];

  const snapshotRes = await fetch(
    `https://api.odds-api.io/v3/odds/multi?apiKey=${apiKey}&eventIds=${eventIds.join(',')}&bookmakers=Bet365,Unibet`
  );
  const snapshot = await snapshotRes.json();

  const store = new Map();
  for (const event of snapshot) {
    for (const [bookie, markets] of Object.entries(event.bookmakers)) {
      store.set(`${event.id}-${bookie}`, markets);
    }
  }
  console.log(`Loaded ${store.size} event/bookmaker pairs`);

  const params = new URLSearchParams({
    apiKey,
    markets: 'ML,Spread,Totals',
    sport: 'football'
  });
  const ws = new WebSocket(`wss://api.odds-api.io/v3/ws?${params}`);

  ws.on('message', (raw) => {
    const data = JSON.parse(raw);
    if (data.type === 'updated') {
      // Replace, never merge - this is the full current market set
      store.set(`${data.id}-${data.bookie}`, data.markets);
    }
  });
  ```

  ```python Python theme={null}
  import asyncio
  import json
  import os
  import requests
  import websockets

  api_key = os.environ['ODDS_API_KEY']
  event_ids = '123456,123457,123458'

  snapshot = requests.get(
      'https://api.odds-api.io/v3/odds/multi',
      params={'apiKey': api_key, 'eventIds': event_ids, 'bookmakers': 'Bet365,Unibet'}
  ).json()

  store = {}
  for event in snapshot:
      for bookie, markets in event['bookmakers'].items():
          store[f"{event['id']}-{bookie}"] = markets

  print(f"Loaded {len(store)} event/bookmaker pairs")

  params = {'apiKey': api_key, 'markets': 'ML,Spread,Totals', 'sport': 'football'}
  url = 'wss://api.odds-api.io/v3/ws?' + '&'.join(f'{k}={v}' for k, v in params.items())

  async def stream():
      async with websockets.connect(url, ping_interval=30) as ws:
          async for message in ws:
              data = json.loads(message)
              if data['type'] == 'updated':
                  # Replace, never merge - this is the full current market set
                  store[f"{data['id']}-{data['bookie']}"] = data['markets']

  asyncio.run(stream())
  ```
</CodeGroup>

<Note>
  Use Python's `websockets` library, not `websocket-client`. Only `websockets` negotiates the `permessage-deflate` compression the feed requires. See [Compression (Required)](/guides/websockets#compression-required).
</Note>

## Fetching Odds for a Single Event

Use the `/v3/odds` endpoint to get odds for a specific event. Add `markets` to keep only the market names you need; it's optional and defaults to every market:

<CodeGroup>
  ```javascript JavaScript theme={null}
  const apiKey = process.env.ODDS_API_KEY;
  const eventId = 123456;
  const bookmakers = ['Bet365', 'Unibet', 'SingBet'].join(',');

  const response = await fetch(
    `https://api.odds-api.io/v3/odds?apiKey=${apiKey}&eventId=${eventId}&bookmakers=${bookmakers}&markets=ML,Spread,Totals`
  );

  const data = await response.json();
  console.log(data);
  ```

  ```python Python theme={null}
  import requests
  import os

  api_key = os.environ['ODDS_API_KEY']
  event_id = 123456
  bookmakers = 'Bet365,Unibet,SingBet'

  response = requests.get(
      'https://api.odds-api.io/v3/odds',
      params={
          'apiKey': api_key,
          'eventId': event_id,
          'bookmakers': bookmakers,
          'markets': 'ML,Spread,Totals'
      }
  )

  data = response.json()
  print(data)
  ```

  ```php PHP theme={null}
  <?php
  $apiKey = getenv('ODDS_API_KEY');
  $eventId = 123456;
  $bookmakers = 'Bet365,Unibet,SingBet';

  $url = 'https://api.odds-api.io/v3/odds?' . http_build_query([
      'apiKey' => $apiKey,
      'eventId' => $eventId,
      'bookmakers' => $bookmakers,
      'markets' => 'ML,Spread,Totals'
  ]);

  $response = file_get_contents($url);
  $data = json_decode($response, true);
  print_r($data);
  ```
</CodeGroup>

## Fetching Odds for Multiple Events

For better efficiency, use the `/v3/odds/multi` endpoint to fetch odds for up to 10 events in a single request. Add `markets` to keep only the market names you need and cut payload size; it's optional and defaults to every market.

<CodeGroup>
  ```javascript JavaScript theme={null}
  const apiKey = process.env.ODDS_API_KEY;
  const eventIds = [123456, 123457, 123458].join(',');
  const bookmakers = ['Bet365', 'Unibet'].join(',');

  const response = await fetch(
    `https://api.odds-api.io/v3/odds/multi?apiKey=${apiKey}&eventIds=${eventIds}&bookmakers=${bookmakers}&markets=ML,Spread,Totals`
  );

  const data = await response.json();
  // Returns an array of event odds
  console.log(data);
  ```

  ```python Python theme={null}
  import requests
  import os

  api_key = os.environ['ODDS_API_KEY']
  event_ids = '123456,123457,123458'
  bookmakers = 'Bet365,Unibet'

  response = requests.get(
      'https://api.odds-api.io/v3/odds/multi',
      params={
          'apiKey': api_key,
          'eventIds': event_ids,
          'bookmakers': bookmakers,
          'markets': 'ML,Spread,Totals'
      }
  )

  data = response.json()
  # Returns an array of event odds
  print(data)
  ```
</CodeGroup>

<Tip>
  The multi-odds endpoint counts as only **1 API request** regardless of how many events you fetch (up to 10). Use it for your initial snapshot, then stream changes over WebSocket instead of calling it again on a timer.
</Tip>

## Understanding the Odds Response

The odds response includes multiple markets for each bookmaker:

```json theme={null}
{
  "id": 123456,
  "home": "Manchester United",
  "away": "Liverpool",
  "date": "2025-10-15T15:00:00Z",
  "status": "pending",
  "bookmakers": {
    "Bet365": [
      {
        "name": "ML",
        "odds": [
          {
            "home": "2.10",
            "draw": "3.40",
            "away": "3.20"
          }
        ],
        "updatedAt": "2025-10-04T10:30:00Z"
      },
      {
        "name": "Spread",
        "odds": [
          {
            "hdp": -0.5,
            "home": "1.95",
            "away": "1.85"
          }
        ]
      },
      {
        "name": "Totals",
        "odds": [
          {
            "max": 2.5,
            "over": "1.90",
            "under": "1.90"
          }
        ]
      }
    ]
  }
}
```

### Market Types

Pass these exact names to the `markets` parameter. Matching is case-insensitive.

* **ML** - Match result (Home, Draw, Away)
* **Spread** - Handicap betting with fractional lines, including Asian lines
* **Totals** - Total goals/points over or under a line
* **Both Teams To Score** - Yes/No markets
* **Correct Score** - Exact score predictions
* **Double Chance**, **Draw No Bet**, **European Handicap**, **Half Time / Full Time**
* **Team Total Home** / **Team Total Away** - Totals for one side only
* Half-time and period variants use a suffix: **ML HT**, **Totals HT**, **Spread HT**, **Totals 2H**
* Baseball's first five innings are named in full rather than with the HT suffix: **First 5 Innings ML**, **First 5 Innings Spread**, **First 5 Innings Totals**, **First 5 Innings Team Total Home** / **First 5 Innings Team Total Away**
* **And many more...**

Availability varies by sport. Tennis uses **Totals (Games)** and **Spread (Games)**, basketball adds **Player Props**. To see exactly which markets an event carries, request it without a `markets` filter.

<Note>
  Match the full market name. There is no market called "Match Winner" (use **ML**), and no bare "Over/Under" or "Asian Handicap", though longer names such as **Goals Over/Under** and **Alternative Corners** do exist. Call `GET /v3/markets?sport=<sport>` to see the exact names served for a sport.
</Note>

## Finding the Best Odds

Here's an example of comparing odds across bookmakers to find the best value:

```javascript theme={null}
function findBestOdds(oddsData) {
  const market = 'ML';
  const bestOdds = {
    home: { bookmaker: null, odds: 0, link: null },
    draw: { bookmaker: null, odds: 0, link: null },
    away: { bookmaker: null, odds: 0, link: null }
  };

  for (const [bookmaker, markets] of Object.entries(oddsData.bookmakers)) {
    const marketML = markets.find(m => m.name === market);
    if (!marketML?.odds[0]) continue;

    const odds = marketML.odds[0];

    if (parseFloat(odds.home) > bestOdds.home.odds) {
      bestOdds.home = {
        bookmaker,
        odds: parseFloat(odds.home)
      };
    }

    if (odds.draw && parseFloat(odds.draw) > bestOdds.draw.odds) {
      bestOdds.draw = {
        bookmaker,
        odds: parseFloat(odds.draw)
      };
    }

    if (parseFloat(odds.away) > bestOdds.away.odds) {
      bestOdds.away = {
        bookmaker,
        odds: parseFloat(odds.away)
      };
    }
  }

  return bestOdds;
}

// Usage
const bestOdds = findBestOdds(oddsData);
console.log('Best home odds:', bestOdds.home.odds, 'at', bestOdds.home.bookmaker);
```

## Keeping Odds Fresh

For real-time applications, open a WebSocket connection instead of polling. It pushes odds changes the moment they happen, negotiates compression, and replays anything you missed on reconnect.

<Card title="WebSocket Odds API" icon="bolt" href="/guides/websockets">
  Full connection details, filters, message shapes, and reconnection with replay.
</Card>

### Polling with `/v3/odds/updated`

If you can't hold a persistent connection, `/v3/odds/updated` is the fallback. It returns odds that changed since a given timestamp.

| Parameter   | Required | Description                                                           |
| ----------- | -------- | --------------------------------------------------------------------- |
| `since`     | required | Unix timestamp in seconds, no more than 90 seconds old                |
| `bookmaker` | required | Bookmaker name from `/v3/bookmakers`; the response uses the same name |
| `sport`     | required | Sport name                                                            |
| `markets`   | optional | Comma-separated market names, case-insensitive, max 20                |
| `limit`     | optional | Max results per response, 1-10000                                     |

Each response includes two headers:

| Header          | Description                                                                   |
| --------------- | ----------------------------------------------------------------------------- |
| `X-Server-Time` | The server's current unix time                                                |
| `X-Next-Since`  | Pass this as `since` on your next call to continue exactly where you left off |

Make your first call with `since` set to 20 seconds ago, then use `X-Next-Since` for every call after that:

<CodeGroup>
  ```javascript JavaScript theme={null}
  const apiKey = process.env.ODDS_API_KEY;
  let since = Math.floor(Date.now() / 1000) - 20;

  async function poll() {
    const response = await fetch(
      `https://api.odds-api.io/v3/odds/updated?apiKey=${apiKey}&since=${since}&bookmaker=Bet365&sport=Football&markets=ML,Spread,Totals`
    );

    const updated = await response.json();
    since = response.headers.get('X-Next-Since');
    return updated;
  }
  ```

  ```python Python theme={null}
  import os
  import time
  import requests

  api_key = os.environ['ODDS_API_KEY']
  since = int(time.time()) - 20

  def poll():
      global since
      response = requests.get(
          'https://api.odds-api.io/v3/odds/updated',
          params={
              'apiKey': api_key,
              'since': since,
              'bookmaker': 'Bet365',
              'sport': 'Football',
              'markets': 'ML,Spread,Totals'
          }
      )
      since = response.headers['X-Next-Since']
      return response.json()
  ```
</CodeGroup>

<Warning>
  Passing `now - 90` on every poll re-downloads the same 90 seconds of odds each time. Always carry forward `X-Next-Since` instead. Each call also returns the full market set for every changed event, so add `markets` to keep responses small.
</Warning>

## Caching Strategies

To optimize performance and reduce API calls:

1. **Cache event lists** for 5-10 minutes
2. **Cache pre-match odds** for 30-60 seconds
3. **For live odds, stream instead of caching** - a WebSocket connection is cheaper than any poll interval

```javascript theme={null}
// Example caching with Node.js
const cache = new Map();
const CACHE_TTL = 60000; // 60 seconds

async function getCachedOdds(eventId) {
  const cacheKey = `odds:${eventId}`;
  const cached = cache.get(cacheKey);

  if (cached && Date.now() - cached.timestamp < CACHE_TTL) {
    return cached.data;
  }

  const data = await fetchOdds(eventId);
  cache.set(cacheKey, { data, timestamp: Date.now() });

  return data;
}
```

## Best Practices

<AccordionGroup>
  <Accordion title="Stream, Don't Poll">
    For anything live, open a [WebSocket connection](/guides/websockets) instead of polling. It's real-time, compressed, replays missed messages on reconnect, and doesn't count against your hourly request limit.
  </Accordion>

  <Accordion title="Select Only Needed Bookmakers">
    Select only the most relevant bookmakers for your users. See the [full list of supported bookmakers](https://odds-api.io/sportsbooks).
  </Accordion>

  <Accordion title="Use Multi-Odds for the Initial Snapshot">
    Batch requests using `/v3/odds/multi` to load your initial snapshot (up to 10 events, counts as 1 request), then stream changes over WebSocket instead of polling.
  </Accordion>

  <Accordion title="Implement Proper Caching">
    Cache odds data appropriately based on match status (pre-match vs live).
  </Accordion>

  <Accordion title="Handle Missing Data Gracefully">
    Not all bookmakers offer all markets. Always check if data exists before accessing it.
  </Accordion>
</AccordionGroup>

## Next Steps

<CardGroup cols={2}>
  <Card title="WebSockets" icon="bolt" href="/guides/websockets">
    Get real-time odds updates via WebSocket connections
  </Card>

  <Card title="Value Bets" icon="magnifying-glass-dollar" href="/guides/value-bets">
    Learn how to identify profitable betting opportunities
  </Card>
</CardGroup>
