# Compare odds across bookmakers

Read one game's prices once and find the best price for each side.

## What it does

The program asks for one event's winner market from every bookmaker in your plan. Each outcome in the response lists every bookmaker's price, sorted best first, so the best price for each side is the first one. The program prints that price, the key of the bookmaker offering it and when we saw it.

One request does the work. The event odds route returns every bookmaker's quotes for the event, so there is no need to call once per bookmaker. `markets=winner` keeps the response to the market you compare, which keeps the [objects you use](https://sockodds.com/docs/best-practices/) low.

## The program

```javascript
const url = "https://api.sockodds.com/v1/events/ev_000ckk0nc1tg/odds?markets=winner";
const response = await fetch(url, {
  headers: { Authorization: `Bearer ${process.env.SOCKODDS_API_KEY}` },
});
const { data: event } = await response.json();

// The full match winner market, with each side's prices sorted best first.
const market = event.markets.find((m) => m.key === "winner" && m.period === "full");
const outcomes = new Map((market?.outcomes ?? []).map((outcome) => [outcome.side, outcome]));

console.log(`${event.name}, winner (full match)`);
for (const participant of event.participants) {
  const outcome = outcomes.get(participant.role);
  if (!outcome) {
    console.log(`${participant.identity.name}: no current price`);
    continue;
  }
  const best = outcome.prices[0];
  console.log(
    `${outcome.name}: best ${best.decimal_odds.toFixed(2)} at ${best.bookmaker}, ` +
      `seen ${best.observed_at}, ${outcome.prices.length} bookmakers quoting`,
  );
}
```

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

url = "https://api.sockodds.com/v1/events/ev_000ckk0nc1tg/odds?markets=winner"
request = urllib.request.Request(
    url, headers={"Authorization": "Bearer " + os.environ["SOCKODDS_API_KEY"]}
)
with urllib.request.urlopen(request) as response:
    event = json.load(response)["data"]

# The full match winner market, with each side's prices sorted best first.
market = next((m for m in event["markets"] if m["key"] == "winner" and m["period"] == "full"), None)
outcomes = {outcome["side"]: outcome for outcome in (market["outcomes"] if market else [])}

print(f"{event['name']}, winner (full match)")
for participant in event["participants"]:
    outcome = outcomes.get(participant["role"])
    if outcome is None:
        print(f"{participant['identity']['name']}: no current price")
        continue
    best = outcome["prices"][0]
    print(
        f"{outcome['name']}: best {best['decimal_odds']:.2f} at {best['bookmaker']}, "
        f"seen {best['observed_at']}, {len(outcome['prices'])} bookmakers quoting"
    )
```

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

```text
Australia v New Zealand, winner (full match)
New Zealand: best 2.23 at playup, seen 2026-10-15T07:58:00.000Z, 4 bookmakers quoting
Australia: best 1.81 at playup, seen 2026-10-15T07:58:00.000Z, 4 bookmakers quoting
```

## How it works

- **Sides, not names.** Each outcome carries a `side` key (`home` and `away` here; `competitor_1` and `competitor_2` in tennis and UFC). Every bookmaker's quote on a side sits in that one outcome's `prices`, even when bookmakers spell a team differently. The program prints sides in the order of the event's `participants`.
- **Best first.** `prices` is sorted by `decimal_odds`, highest first, so `prices[0]` is the best price and `prices.length` is how many bookmakers quote the side. A price names its bookmaker by key; [`/v1/bookmakers`](https://sockodds.com/docs/#listBookmakers) gives each key's display name.
- **One period.** `markets=winner` returns the winner market for every period a bookmaker quotes, such as halves or quarters. The program keeps `full` only, so a half price never competes with a full match price.
- **Current prices only.** A quote older than ten minutes is withheld from current responses, so every price the program sees was observed in the last ten minutes. It prints the price's `observed_at` so you can show your users when it was seen.
- **No price is not zero.** If no bookmaker quotes a side right now, the program says so instead of printing a number.

## Make it yours

- Compare a different market: change `markets=winner` to a key from [the market catalogue](https://sockodds.com/docs/markets/). Each handicap or total line is its own market, so read the best price per outcome the same way.
- Compare across a league: call [`/v1/odds`](https://sockodds.com/docs/#listOdds) with `league=` and loop over `data`, following `links.next` for more events.
- Limit the bookmakers: add `bookmakers=` with up to 20 keys from [the bookmaker list](https://sockodds.com/bookmakers/).

The prices above come from a local copy of the API with invented test prices. A best price is the best quote we saw, not an offer: bookmakers change prices often and may limit or refuse a bet.
