API reference
Get odds
Prices for one event: per market, per selection, per book. A finished event returns its closing card.
GET
/odds/v1/odds- Cost
- 1 token (tier=main), 5 tokens (tier=extended); the same for a finished event
- Authentication
- X-API-Key, demo key works
Parameters
event_idstringrequiredAn event_id from /events, sent back verbatim. Live or finished.tierstringoptionalmain: the headline markets for the sport. extended: also alternate lines, player props and period markets.Default:mainmarketscomma listoptionalMarket keys, e.g. head_to_head,total_goals.Default:all in the tierbookscomma listoptionalBook keys from /books, e.g. betfair,sportsbet.Default:allformatstringoptionaldecimal, american or fractional. See Odds formats.Default:decimal
Request
# Take the next EPL match, then ask for its head-to-head prices
EVENT_ID=$(curl -s "https://api.b337.ai/odds/v1/events?league=epl&limit=1" \
-H "X-API-Key: demo" | jq -r '.data[0].event_id')
curl -sG "https://api.b337.ai/odds/v1/odds" \
--data-urlencode "event_id=$EVENT_ID" \
--data-urlencode "markets=head_to_head" \
--data-urlencode "books=sportsbet,tab" \
-H "X-API-Key: demo" --compressedimport requests
BASE = "https://api.b337.ai/odds/v1"
HEADERS = {"X-API-Key": "demo"}
events = requests.get(f"{BASE}/events", params={"league": "epl", "limit": 1},
headers=HEADERS, timeout=10).json()
event_id = events["data"][0]["event_id"]
odds = requests.get(f"{BASE}/odds", headers=HEADERS, timeout=10, params={
"event_id": event_id,
"markets": "head_to_head",
"books": "sportsbet,tab",
}).json()
for market in odds["markets"]:
for sel in market["selections"]:
best = max(sel["prices"].items(), key=lambda kv: kv[1]["odds"])
print(sel["label"], "best:", best[0], best[1]["odds"])// Node 18+, saved as an .mjs file
const BASE = "https://api.b337.ai/odds/v1";
const headers = { "X-API-Key": "demo" };
const events = await (await fetch(`${BASE}/events?league=epl&limit=1`, { headers })).json();
const eventId = events.data[0].event_id;
const url = new URL(`${BASE}/odds`);
url.searchParams.set("event_id", eventId);
url.searchParams.set("markets", "head_to_head");
url.searchParams.set("books", "sportsbet,tab");
const res = await fetch(url, { headers });
console.log(res.headers.get("X-Tokens-Remaining"));
const { event, markets } = await res.json();
console.log(event.name, markets[0].selections.map((s) => s.label));Response
200 OK
{
"schema_version": 1,
"event": {"event_id": "soccer/gb/epl/arsenal_fc_v_leeds_united_fc/2026-10-10",
"sport": "soccer", "league": "epl", "country": "gb",
"name": "Arsenal FC v Leeds United FC", "home": "Arsenal FC",
"away": "Leeds United FC", "participants": ["Arsenal FC", "Leeds United FC"],
"competitors": [
{"id": "soccer:arsenal_fc", "name": "Arsenal FC", "side": "home",
"logo_url": "https://api.b337.ai/odds/v1/media/logo/2f6693605fb6dcfa.png"},
{"id": "soccer:leeds_united_fc", "name": "Leeds United FC", "side": "away",
"logo_url": "https://api.b337.ai/odds/v1/media/logo/c6d81a00ff2a0516.png"}],
"is_outright": false, "start_time": "2026-10-10T11:30:00Z",
"status": "prematch", "books": ["betfair", "sportsbet", "tab"]},
"tier": "main",
"odds_format": "decimal",
"books": {
"betfair": {"updated_at": "2026-10-05T03:06:23Z", "age_seconds": 19.6, "stale": false,
"url": "https://www.betfair.com.au/exchange/plus/market/1.262353261"},
"sportsbet": {"updated_at": "2026-10-05T03:05:20Z", "age_seconds": 82.6, "stale": false},
"tab": {"updated_at": "2026-10-05T03:06:15Z", "age_seconds": 27.6, "stale": false}
},
"markets": [
{"market": "head_to_head", "name": "Match Result", "selections": [
{"selection_key": "774c72a86710", "name": "Arsenal FC", "label": "Arsenal FC",
"player": null, "line": null, "direction": null, "period": null,
"prices": {"betfair": {"odds": 1.41, "updated_at": "2026-10-05T03:06:23Z", "open_odds": 1.39},
"sportsbet": {"odds": 1.37, "updated_at": "2026-10-05T03:05:20Z", "open_odds": 1.27},
"tab": {"odds": 1.38, "updated_at": "2026-10-05T03:06:15Z", "open_odds": 1.3}}},
{"selection_key": "826ae30e03c4", "name": "Draw", "label": "Draw",
"player": null, "line": null, "direction": null, "period": null,
"prices": {"betfair": {"odds": 5.3, "updated_at": "2026-10-05T03:06:23Z", "open_odds": 5.5},
"sportsbet": {"odds": 5.0, "updated_at": "2026-10-05T03:05:20Z", "open_odds": 5.6},
"tab": {"odds": 4.75, "updated_at": "2026-10-05T03:06:15Z", "open_odds": 4.75}}},
{"selection_key": "8fd2377f4f58", "name": "Leeds United FC", "label": "Leeds United FC",
"player": null, "line": null, "direction": null, "period": null,
"prices": {"betfair": {"odds": 9.2, "updated_at": "2026-10-05T03:06:23Z", "open_odds": 9.8},
"sportsbet": {"odds": 8.0, "updated_at": "2026-10-05T03:05:20Z", "open_odds": 11.5},
"tab": {"odds": 8.0, "updated_at": "2026-10-05T03:06:15Z", "open_odds": 8.5}}}]}
],
"generated_at": "2026-10-05T03:06:42Z"
}
// A finished event (event_id=baseball/us/mlb/philadelphia_phillies_v_tampa_bay_rays/2026-09-26), trimmed:
{
"event": {"event_id": "baseball/us/mlb/philadelphia_phillies_v_tampa_bay_rays/2026-09-26", "status": "finished",
"start_time": "2026-09-26T23:15:00Z"},
"tier": "main",
"off_at": "2026-09-26T23:15:00Z", "off_source": "scheduled",
"markets": [
{"market": "head_to_head", "name": "Head to Head", "selections": [
{"selection_key": "4ec43f065e08", "name": "Philadelphia Phillies",
"prices": {
"betfair": {"odds": 1.86, "close_odds": 1.86, "closed_at": "2026-09-26T23:11:58Z",
"updated_at": "2026-09-26T23:11:58Z", "lag_seconds": 182, "source": "capture"},
"tab": {"odds": 1.8, "close_odds": 1.8, "closed_at": "2026-09-26T22:56:46Z",
"updated_at": "2026-09-26T22:56:46Z", "lag_seconds": 1094, "source": "capture"}}}]}
]
}Fields
marketis a stable key: the same key means the same bet on every book.nameis for display.- A selection is identified by selection_key.
labelis for display. line: handicap or total where the market has one.direction:over/underwhere it applies.player: for player markets.period: e.g. a half or quarter;nullis the full game.books[book].updated_atis when we last read that book's card for this event; every price from that book carries the same time.age_secondsis the same fact as an age.books[book].staleistruewhen that book has not refreshed this event within its own normal cadence. Books refresh at very different rates (seconds to about 20 minutes), so the threshold is per book: twice the age that 90% of that book's cards are younger than, at least 3 minutes and at most 1 hour (10 minutes for a book we have not measured). A stale book's prices are still served: the flag means “this book may have stopped updating this event”, not “this price is wrong”. Useage_secondsfor your own tolerance.event.competitorslists each side with itsid,name,sideandlogo_url, as on /events.books[book].urlis the book's own page for the event, when we can vouch for it. See event links.open_oddsandclose_oddssit on each price. See opening and closing prices.- A finished event returns its closing card: each price also carries
closed_at,lag_secondsandsource, each selection can carryfair_odds, and the body carriesoff_atandoff_source. See opening and closing prices. A finished event with no stored close returnsmarkets: []. - Business and Enterprise: a selection can carry
model_oddsandmodel_source, and each price anedge_pct. See 337 model price. - Business and Enterprise: the body carries a top-level
score(see /scores), and on a finished event whose score is final each selection can carryresult:won,lostorpush. Where we do not grade a selection the field isnull. See selection results. - A book missing from a selection's
pricesdoes not currently price it.
Response bodies are large on tier=extended. Send Accept-Encoding: gzip (cURL: --compressed).