Prematch endpoints
REST endpoints for prematch odds. Available on both plans.
GET /api/v1/sports
/api/v1/sportsReturns all sports with their competition and match counts. icon is a relative path — see Icons.
{
"ok": true,
"data": [
{
"id": "soccer",
"name": "Soccer",
"slug": "soccer",
"icon": "/icons/sports/1.svg",
"competitionCount": 123,
"updatedAt": "2026-04-17T12:00:00Z"
}
]
}GET /api/v1/sports/:sportId/competitions
/api/v1/sports/soccer/competitionsList all competitions for a sport.
GET /api/v1/competitions/:sportId/:competitionId/matches
/api/v1/competitions/soccer/england-premier-league/matches?page=1&pageSize=50Paginated matches for a competition. Query params:
page(default 1)pageSize(default 50, max 200)includePast(default false)
GET /api/v1/matches/:fixtureId
/api/v1/matches/191691460Single match with full market data (all markets + selections).
GET /api/v1/matches
/api/v1/matches?sport=soccer&competition=england-premier-league&upcoming=1Filter matches across all competitions. Query params:
sport— sport id or namecompetition— competition id or namedateFrom— YYYY-MM-DDdateTo— YYYY-MM-DDupcoming— 1 to hide past matchespage,pageSize
Match detail response
Every match detail response returns two complementary views of the same odds:marketGroups[] (typed, hierarchical — best for rendering) andodds[] (flat, row-per-outcome — best for aggregation and odds comparison, compatible with the row format used by The Odds API / OpticOdds).
{
"ok": true,
"data": {
"fixtureId": "191027752",
"sportIcon": "/icons/sports/1.svg",
"competitionIcon": "/icons/flags/England.svg",
"home": "Crystal Palace",
"away": "Newcastle",
"homeIcon": "/icons/silks/CrystalPalace_Home_Front_25_26.svg",
"awayIcon": "/icons/silks/Newcastle_Away_Front_25_26.svg",
"kickoff": "2026-04-13T15:00:00.000Z",
"mainOdds": { "market": "match_result", "selections": [...] },
"marketCategories": [ { "name": "Popular" }, { "name": "Goals" } ],
"categories": [
{ "category": "popular", "count": 6, "groups": [ ... ] }
],
"marketGroups": [ /* typed shape — see below */ ],
"odds": [ /* flat rows — see below */ ]
}
}Market groups (typed)
Every market group has a type (determines which fields are present) plus canonical identifiers:
key— bet365-normalized snake_case (over_under_2_5,first_half_result)industryKey— The Odds API-compatible alias (totals,h2h,spreads,btts,player_points, suffixed by period:h2h_h1,totals_q1).nullwhen no industry equivalent exists.period—full_game/1h/2h/q1..q4/p1..p3/s1..s5/5_innings/innings_1/nullcategory— UI tab (popular,goals,player, …)
Layout types:
simple— flatselections[](1X2, BTTS, Double Chance)line—line+selections[]with Over/Under (Total Goals, Asian Handicap)grouped—groups[]of named sub-markets (Correct Score)grid—columns×rows+cells[](Result/BTTS, HT/FT)player_grid—players[]×statTypes[]+cells[](Goalscorers, Player Props)range_grid—ranges[]×outcomes[]+cells[](Match Goals Range)promo— BetBoost / Bet Builderoutright— tournament / group winner
Every selection / cell carries canonical metadata so you don't need to re-parse display names:
{
"name": "Over 2.5",
"odds": 1.91,
"side": "over", // home|away|draw|over|under|yes|no|null
"line": 2.5, // numeric line/handicap
"team": null, // "home"|"away"|null
"isMain": true, // false for alternate lines
"suspended": false
}Simple example (Full Time Result):
{
"id": "40",
"name": "Full Time Result",
"key": "full_time_result",
"industryKey": "h2h_3_way",
"period": "full_game",
"category": "popular",
"type": "simple",
"loaded": true,
"selections": [
{ "name": "Crystal Palace", "odds": 3.00, "side": "home", "team": "home", "isMain": true },
{ "name": "Draw", "odds": 3.30, "side": "draw", "team": null, "isMain": true },
{ "name": "Newcastle", "odds": 2.40, "side": "away", "team": "away", "isMain": true }
]
}Line example (Over/Under 2.5 Goals):
{
"name": "Over/Under 2.5 Goals",
"key": "over_under_2_5",
"industryKey": "totals",
"period": "full_game",
"type": "line",
"line": 2.5,
"selections": [
{ "name": "Over 2.5", "odds": 1.91, "line": 2.5, "side": "over", "isMain": true },
{ "name": "Under 2.5", "odds": 1.91, "line": 2.5, "side": "under", "isMain": true }
]
}Player grid example: every player has a stable id (slug(name@team)), and cells carry playerId / statKey:
{
"name": "Player Shots on Target",
"key": "player_shots_on_target",
"industryKey": "player_shots_on_target",
"period": "full_game",
"type": "player_grid",
"players": [
{ "id": "anthony_gordon@newcastle", "name": "Anthony Gordon", "team": "Newcastle", "side": "away" }
],
"statTypes": ["Over 0.5", "Over 1.5"],
"statKeys": ["over_0_5", "over_1_5"],
"cells": [
{ "player": 0, "playerId": "anthony_gordon@newcastle", "stat": 0, "statKey": "over_0_5", "odds": 1.50, "line": 0.5, "isMain": true }
]
}Flat odds[] view
OpticOdds-style row-per-outcome array. Every row carries enough canonical metadata to diff, compare, and aggregate without walking marketGroups. groupingKey ties complementary pairs together (Over/Under at the same line, home/away spread, Yes/No BTTS).
{
"id": "191027752:over_under_2_5:full_game:over:2.5",
"marketKey": "over_under_2_5",
"marketName": "Over/Under 2.5 Goals",
"industryKey": "totals",
"category": "goals",
"period": "full_game",
"selection": "Over 2.5",
"side": "over",
"team": null,
"line": 2.5,
"isMain": true,
"playerId": null,
"playerName": null,
"statKey": null,
"price": 1.91,
"suspended": false,
"groupingKey": "over_under_2_5:full_game:2.5"
}