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 low.
The program
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`,
);
}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
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 quotingHow it works
- Sides, not names. Each outcome carries a
sidekey (homeandawayhere;competitor_1andcompetitor_2in tennis and UFC). Every bookmaker's quote on a side sits in that one outcome'sprices, even when bookmakers spell a team differently. The program prints sides in the order of the event'sparticipants. - Best first.
pricesis sorted bydecimal_odds, highest first, soprices[0]is the best price andprices.lengthis how many bookmakers quote the side. A price names its bookmaker by key;/v1/bookmakersgives each key's display name. - One period.
markets=winnerreturns the winner market for every period a bookmaker quotes, such as halves or quarters. The program keepsfullonly, 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_atso 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=winnerto a key from the market catalogue. 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/oddswithleague=and loop overdata, followinglinks.nextfor more events. - Limit the bookmakers: add
bookmakers=with up to 20 keys from the bookmaker list.
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.