Reading prices

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

Two routes for current prices

RouteUse 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}/oddsOne event you already know. data is one event. This is also the only route that returns opening and closing snapshots.

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 the same body.

One event's prices

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

curl -sS "https://api.sockodds.com/v1/events/ev_000ckk0nc1tg/odds?markets=handicap&bookmakers=tab" \
  -H "Authorization: Bearer $SOCKODDS_API_KEY"
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));
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

{
  "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

FieldWhat it holds
idThe 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.
keyThe market type, such as winner, handicap or total_points. The market catalogue lists every key and the sides each offers.
nameThe display name of the market type.
subjectnull 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.
periodThe part of the game: full, half_1, quarter_2 and so on. Markets lists the periods, and Results explains how they settle.
alternatefalse for the main line, true for an alternate line. Alternates are left out unless you ask for them.
outcomesOne 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

FieldWhat it holds
idThe outcome's id, the same at every bookmaker. Results use the same id, so you can settle against it.
nameThe selection's display name: a team, a player, Over, Draw and so on.
sideThe side key: home, away, draw, over, under, yes, no, competitor_1, competitor_2, or a player id where each outcome is a player.
lineThe 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.
pricesOne 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

FieldWhat it holds
bookmakerThe bookmaker's key. The bookmakers page and /v1/bookmakers give each key's name.
decimal_oddsThe decimal price as the bookmaker showed it. We never convert, round or remove the margin.
implied_probability1 / 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.
availabletrue 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_atWhen 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.
  • 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:

curl -sS "https://api.sockodds.com/v1/events/ev_000ckk06psy5/odds?markets=winner" \
  -H "Authorization: Bearer $SOCKODDS_API_KEY"
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));
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

{
  "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.

Filters

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

ParameterEffect
marketsOnly these market keys, such as markets=winner,handicap.
bookmakersOnly these bookmakers, such as bookmakers=sportsbet,tab. The bookmakers page lists every key.
regionsOnly bookmakers that serve these regions, such as regions=au. Combine with bookmakers and a price must pass both.
alternatestrue adds alternate lines next to the main lines. Default false.

/v1/odds also takes:

ParameterEffect
leagueOne league key, such as epl or rlwc. See Leagues.
starts_after, starts_beforeA 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, cursorEvents per page and the next page. See 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:

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"
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));
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

{
  "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:

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}`);
    }
  }
}
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

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 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.
  • Event times and observed_at are always UTC. Convert to local time for display only.
Need help?FAQ · Email support

Search across the SockOdds documentation and catalogue.