# Reading prices

Decimal prices, exactly as each bookmaker showed them, each with the time we saw it.

## Two routes for current prices

| Route | Use it for |
| --- | --- |
| `GET /v1/odds?league=...` | Many events at once: a league's upcoming games and their prices, a page at a time. `data` is a list of events. |
| `GET /v1/events/{event_id}/odds` | One event you already know. `data` is one event. This is also the only route that returns opening and closing [snapshots](https://sockodds.com/docs/history/). |

Both return events in the same shape: each event has its `markets`, each market its `outcomes`, and each outcome its `prices`, one per bookmaker, best first. Every market and outcome appears once, however many bookmakers price it. Both take the same filters, below. To be told about changes without asking, Pro plans can [stream](https://sockodds.com/docs/streaming/) the same body.

## One event's prices

Here is the handicap market for the Rugby League World Cup opener at one bookmaker:

```bash
curl -sS "https://api.sockodds.com/v1/events/ev_000ckk0nc1tg/odds?markets=handicap&bookmakers=tab" \
  -H "Authorization: Bearer $SOCKODDS_API_KEY"
```

```javascript
const response = await fetch("https://api.sockodds.com/v1/events/ev_000ckk0nc1tg/odds?markets=handicap&bookmakers=tab", {
  headers: { Authorization: `Bearer ${process.env.SOCKODDS_API_KEY}` },
});
console.log(JSON.stringify(await response.json(), null, 2));
```

```python
import json, os, urllib.request

request = urllib.request.Request(
    "https://api.sockodds.com/v1/events/ev_000ckk0nc1tg/odds?markets=handicap&bookmakers=tab",
    headers={"Authorization": "Bearer " + os.environ["SOCKODDS_API_KEY"]},
)
with urllib.request.urlopen(request) as response:
    print(json.dumps(json.load(response), indent=2))
```

Response (from a local copy of the API with invented test prices):

```json
{
  "data": {
    "id": "ev_000ckk0nc1tg",
    "name": "Australia v New Zealand",
    "league": {
      "key": "rlwc",
      "name": "Rugby League World Cup"
    },
    "scheduled_start": "2026-10-15T09:05:00.000Z",
    "status": "scheduled",
    "participants": [
      {
        "role": "away",
        "identity": {
          "key": "new_zealand_rugby_league",
          "name": "New Zealand"
        }
      },
      {
        "role": "home",
        "identity": {
          "key": "australia_rugby_league",
          "name": "Australia"
        }
      }
    ],
    "markets": [
      {
        "id": "mk_4n31ewa9j30vmrxd",
        "key": "handicap",
        "name": "Handicap",
        "period": "full",
        "subject": null,
        "alternate": false,
        "outcomes": [
          {
            "id": "oc_023zzy2y32rg8dbb",
            "name": "Australia",
            "side": "home",
            "line": -6.5,
            "prices": [
              {
                "bookmaker": "tab",
                "decimal_odds": 1.96,
                "implied_probability": 0.510204,
                "available": true,
                "observed_at": "2026-10-15T07:58:00.000Z"
              }
            ]
          },
          {
            "id": "oc_pacntj8smhyp1rjt",
            "name": "New Zealand",
            "side": "away",
            "line": 6.5,
            "prices": [
              {
                "bookmaker": "tab",
                "decimal_odds": 1.96,
                "implied_probability": 0.510204,
                "available": true,
                "observed_at": "2026-10-15T07:58:00.000Z"
              }
            ]
          }
        ]
      }
    ]
  },
  "meta": {
    "count": 1,
    "cursor": null
  },
  "links": {
    "next": null
  }
}
```

## Market fields

| Field | What it holds |
| --- | --- |
| `id` | The market's id. It is built from the event, the market type, its subject, its period and its line, so the same market has the same id at every bookmaker. Match markets across bookmakers by this id. |
| `key` | The market type, such as `winner`, `handicap` or `total_points`. The [market catalogue](https://sockodds.com/docs/markets/) lists every key and the sides each offers. |
| `name` | The display name of the market type. |
| `subject` | `null` for a market on the whole event. For a team market, the team's `key` and `name`. For a player market, the player's `id` and `name`. |
| `period` | The part of the game: `full`, `half_1`, `quarter_2` and so on. [Markets](https://sockodds.com/docs/markets/) lists the periods, and [Results](https://sockodds.com/docs/results/) explains how they settle. |
| `alternate` | `false` for the main line, `true` for an alternate line. Alternates are left out unless you ask for them. |
| `outcomes` | One entry per selection, each with every bookmaker's price. |

Markets come in catalogue order (winner, result, handicap, totals, then team and player markets), then by period, subject and line. Outcomes follow the market's sides: home, draw, away; over, under. Players and exact scores are listed by name.

## Outcome fields

| Field | What it holds |
| --- | --- |
| `id` | The outcome's id, the same at every bookmaker. Results use the same id, so you can settle against it. |
| `name` | The selection's display name: a team, a player, `Over`, `Draw` and so on. |
| `side` | The side key: `home`, `away`, `draw`, `over`, `under`, `yes`, `no`, `competitor_1`, `competitor_2`, or a player id where each outcome is a player. |
| `line` | The line for handicap and total markets, from this outcome's point of view, otherwise `null`. In the handicap above the home side is `-6.5` and the away side `6.5`. |
| `prices` | One price per bookmaker, best first: the highest decimal price, then bookmaker key. `prices[0]` is the best price among the bookmakers you asked for. |

## Price fields

| Field | What it holds |
| --- | --- |
| `bookmaker` | The bookmaker's key. The [bookmakers page](https://sockodds.com/bookmakers/) and `/v1/bookmakers` give each key's name. |
| `decimal_odds` | The decimal price as the bookmaker showed it. We never convert, round or remove the margin. |
| `implied_probability` | `1 / decimal_odds`, rounded to six places. A bookmaker's prices across one market's outcomes usually add up to more than 1: the excess is its margin. |
| `available` | `true` when the price is current. Current responses only include available prices; a stale price appears, as `false` with `null` prices and after the current ones, only when you ask for snapshots. |
| `observed_at` | When we saw this price at the bookmaker, in UTC. Use it to decide how fresh is fresh enough for you. |

## One block per bookmaker

Add `group_by=bookmaker` to either route (or the stream) to get the prices grouped the other way: each event has `bookmakers`, each bookmaker its own `markets`, and each outcome a single `decimal_odds` with its `bookmaker`, `alternate` and `observed_at`. The prices, filters and usage are the same; only the grouping changes. A pagination cursor works only with the grouping it came from.

## Why a price can be missing

A missing price means we have no current price to show, never that the price is zero or that the market is closed.

- **We have not seen it for ten minutes.** A price we have not seen again within ten minutes is withheld from current responses rather than shown as current. On a game that has started or finished, the markets list is often empty for this reason.
- **The bookmaker is not offering it.** Not every bookmaker prices every market, line or period.
- **The bookmaker withdrew it.** A suspended or withdrawn selection drops out until it is offered again.
- **Your plan leaves the bookmaker out.** Plans with a set list of bookmakers simply omit the others. See [Pricing](https://sockodds.com/pricing/).
- **A filter left it out**, including `alternates`, which is off by default.

Here is the NFL game that has already been played. Its prices were last seen hours ago, so none are current:

```bash
curl -sS "https://api.sockodds.com/v1/events/ev_000ckk06psy5/odds?markets=winner" \
  -H "Authorization: Bearer $SOCKODDS_API_KEY"
```

```javascript
const response = await fetch("https://api.sockodds.com/v1/events/ev_000ckk06psy5/odds?markets=winner", {
  headers: { Authorization: `Bearer ${process.env.SOCKODDS_API_KEY}` },
});
console.log(JSON.stringify(await response.json(), null, 2));
```

```python
import json, os, urllib.request

request = urllib.request.Request(
    "https://api.sockodds.com/v1/events/ev_000ckk06psy5/odds?markets=winner",
    headers={"Authorization": "Bearer " + os.environ["SOCKODDS_API_KEY"]},
)
with urllib.request.urlopen(request) as response:
    print(json.dumps(json.load(response), indent=2))
```

Response (from a local copy of the API with invented test prices):

```json
{
  "data": {
    "id": "ev_000ckk06psy5",
    "name": "Arizona Cardinals v Atlanta Falcons",
    "league": {
      "key": "nfl",
      "name": "NFL"
    },
    "scheduled_start": "2026-10-12T08:00:00.000Z",
    "status": "final",
    "participants": [
      {
        "role": "away",
        "identity": {
          "key": "atlanta_falcons",
          "name": "Atlanta Falcons"
        }
      },
      {
        "role": "home",
        "identity": {
          "key": "arizona_cardinals",
          "name": "Arizona Cardinals"
        }
      }
    ],
    "markets": []
  },
  "meta": {
    "count": 1,
    "cursor": null
  },
  "links": {
    "next": null
  }
}
```

On Pro, `snapshots=closing` on the same request shows the last price before the start. See [History and snapshots](https://sockodds.com/docs/history/).

## Filters

Both price routes take these filters. Each list filter is comma separated and takes at most 20 keys.

| Parameter | Effect |
| --- | --- |
| `markets` | Only these market keys, such as `markets=winner,handicap`. |
| `bookmakers` | Only these bookmakers, such as `bookmakers=sportsbet,tab`. The [bookmakers page](https://sockodds.com/bookmakers/) lists every key. |
| `regions` | Only bookmakers that serve these regions, such as `regions=au`. Combine with `bookmakers` and a price must pass both. |
| `alternates` | `true` adds alternate lines next to the main lines. Default `false`. |

`/v1/odds` also takes:

| Parameter | Effect |
| --- | --- |
| `league` | One league key, such as `epl` or `rlwc`. See [Leagues](https://sockodds.com/leagues/). |
| `starts_after`, `starts_before` | A window on the scheduled start, in UTC (`2026-10-16T00:00:00Z`). `starts_after` includes that moment and `starts_before` excludes it. With neither, the list starts 48 hours ago: live and upcoming games plus the last two days. |
| `limit`, `cursor` | Events per page and the next page. See [Pagination](https://sockodds.com/docs/pagination/). |

Filters narrow the answer and never cause an error by matching nothing: a market key no bookmaker quotes returns events with an empty `markets` list. A key that is not a valid key at all (capitals, spaces) is refused with 400.

Here are the NFL games starting on 16 October 2026 (UTC), with one bookmaker's winner prices:

```bash
curl -sS "https://api.sockodds.com/v1/odds?league=nfl&starts_after=2026-10-16T00:00:00Z&starts_before=2026-10-17T00:00:00Z&markets=winner&bookmakers=sportsbet" \
  -H "Authorization: Bearer $SOCKODDS_API_KEY"
```

```javascript
const response = await fetch("https://api.sockodds.com/v1/odds?league=nfl&starts_after=2026-10-16T00:00:00Z&starts_before=2026-10-17T00:00:00Z&markets=winner&bookmakers=sportsbet", {
  headers: { Authorization: `Bearer ${process.env.SOCKODDS_API_KEY}` },
});
console.log(JSON.stringify(await response.json(), null, 2));
```

```python
import json, os, urllib.request

request = urllib.request.Request(
    "https://api.sockodds.com/v1/odds?league=nfl&starts_after=2026-10-16T00:00:00Z&starts_before=2026-10-17T00:00:00Z&markets=winner&bookmakers=sportsbet",
    headers={"Authorization": "Bearer " + os.environ["SOCKODDS_API_KEY"]},
)
with urllib.request.urlopen(request) as response:
    print(json.dumps(json.load(response), indent=2))
```

Response (from a local copy of the API with invented test prices):

```json
{
  "data": [
    {
      "id": "ev_000ckk0ee4ze",
      "name": "Arizona Cardinals v Atlanta Falcons",
      "league": {
        "key": "nfl",
        "name": "NFL"
      },
      "scheduled_start": "2026-10-16T19:00:00.000Z",
      "status": "scheduled",
      "participants": [
        {
          "role": "away",
          "identity": {
            "key": "atlanta_falcons",
            "name": "Atlanta Falcons"
          }
        },
        {
          "role": "home",
          "identity": {
            "key": "arizona_cardinals",
            "name": "Arizona Cardinals"
          }
        }
      ],
      "markets": [
        {
          "id": "mk_1e3spch3ksp3ptf7",
          "key": "winner",
          "name": "Winner",
          "period": "full",
          "subject": null,
          "alternate": false,
          "outcomes": [
            {
              "id": "oc_2b675wjrrdr96ngx",
              "name": "Arizona Cardinals",
              "side": "home",
              "line": null,
              "prices": [
                {
                  "bookmaker": "sportsbet",
                  "decimal_odds": 1.72,
                  "implied_probability": 0.581395,
                  "available": true,
                  "observed_at": "2026-10-15T07:58:00.000Z"
                }
              ]
            },
            {
              "id": "oc_hsr6kxp5rq99zsfx",
              "name": "Atlanta Falcons",
              "side": "away",
              "line": null,
              "prices": [
                {
                  "bookmaker": "sportsbet",
                  "decimal_odds": 2.12,
                  "implied_probability": 0.471698,
                  "available": true,
                  "observed_at": "2026-10-15T07:58:00.000Z"
                }
              ]
            }
          ]
        }
      ]
    }
  ],
  "meta": {
    "count": 1,
    "cursor": null
  },
  "links": {
    "next": null
  }
}
```

## Compare bookmakers

Each outcome already lists every bookmaker's price, best first, so the best price is `prices[0]`. This program reads the next English Premier League game's result market from every Australian bookmaker quoting it and prints the best price for each outcome:

```javascript
const response = await fetch(`https://api.sockodds.com/v1/odds?league=epl&markets=result&regions=au&limit=1&starts_after=${new Date().toISOString().slice(0, 19)}Z`, {
  headers: { Authorization: `Bearer ${process.env.SOCKODDS_API_KEY}` },
});
const body = await response.json();
for (const event of body.data) {
  for (const market of event.markets) {
    const bookmakers = new Set(market.outcomes.flatMap((outcome) => outcome.prices.map((price) => price.bookmaker)));
    console.log(`${event.name}, ${bookmakers.size} bookmakers`);
    for (const outcome of market.outcomes) {
      const best = outcome.prices[0];
      console.log(`  ${outcome.name.padEnd(18)} ${best.decimal_odds.toFixed(2)}  ${best.bookmaker}`);
    }
  }
}
```

```python
import json, os, urllib.request
from datetime import datetime, timezone

request = urllib.request.Request(
    f"https://api.sockodds.com/v1/odds?league=epl&markets=result&regions=au&limit=1&starts_after={datetime.now(timezone.utc):%Y-%m-%dT%H:%M:%SZ}",
    headers={"Authorization": "Bearer " + os.environ["SOCKODDS_API_KEY"]},
)
with urllib.request.urlopen(request) as response:
    body = json.load(response)
for event in body["data"]:
    for market in event["markets"]:
        bookmakers = {price["bookmaker"] for outcome in market["outcomes"] for price in outcome["prices"]}
        print(f"{event['name']}, {len(bookmakers)} bookmakers")
        for outcome in market["outcomes"]:
            best = outcome["prices"][0]
            print(f"  {outcome['name'].ljust(18)} {best['decimal_odds']:.2f}  {best['bookmaker']}")
```

Output (from a local copy of the API with invented test prices):

```text
AFC Bournemouth v Arsenal, 4 bookmakers
  AFC Bournemouth    2.15  swiftbet
  Draw               3.57  swiftbet
  Arsenal            3.78  swiftbet
```

Prices move, and a price you saw may be gone by the time you act on it. Check `observed_at`, and remember that bookmakers can limit or close accounts. [Odds comparison](https://sockodds.com/docs/examples/odds-comparison/) builds this out into a full example.

## Good to know

- Prices are pre-match first. This is not a low-latency in-play feed.
- Every event and every price in a response counts towards your monthly objects, so filter to the markets and bookmakers you need. See [Limits](https://sockodds.com/docs/limits/).
- Event times and `observed_at` are always UTC. Convert to local time for display only.
