Building a Sports Market Detail Page
Compose the discovery, orderbook, and chart endpoints into a cross-venue game detail page
A sports game's markets are spread across companion events (moneyline, spreads, totals, props)
and venues, all sharing one canonical game key (aggKey, e.g. agg_fifwc_bih_che_260618).
Build the detail page with three steps and no client-side matching.
Listing the games (one tile per game)
GET /venue-events?grouped=true returns one tile per game: a venue's companion events
(… - More Markets, … - Exact Score, … : Spread) fold under their base, and cross-venue
siblings collapse to the cluster anchor. Each tile carries the game aggKey and
groupMarketCount (open markets across the whole game). Tap a tile, take its id, and load the
detail page below.
grouped is opt-in — only pass grouped=true once you have the game-detail page below,
because grouping hides the companion events from the listing and the detail page is what
re-surfaces their markets (spreads, exact score, props) via aggKey. Omit grouped (the
default) and companion events list as separate tiles.
1. Resolve the game (any member event → the same page)
GET /venue-events/:id → read structureType (sport → render the game page) and aggKey
(the game handle).
The page is keyed on the game (aggKey), not on the event you opened. A game's companion
events (… - Halftime Result, … - Exact Score, … : Spread) each have their own id but share
the same aggKey. Opening any of them must render the same game page. Derive the tabs and
markets below from the aggKey — NOT from the opened event's own venueMarkets. If you key off the
opened event, the base event shows only game lines and the "Halftime Result" companion shows only
halves, even though both are the same game.
2. Build the tabs from the game aggKey
Fetch the game's markets by aggKey and group them client-side by marketGroup:
GET /venue-markets?aggKey={gameAggKey}&status=open&limit=100&cursor=…
aggKeyis game-scoped: you get every market of the game across companion events + venues (moneyline, spreads, totals, props), deduped to one row per logical market (cross-venue siblings onmatchedVenueMarkets[]).- The visible tab set is the
marketGroups present in this result — not the groups of the opened event. Hide groups with no markets. Paginate large games withcursor/limit. - To load tabs lazily instead of all at once, add a
marketGroup(+period) filter per tab — but the visible tab set still comes from the game, so probe which groups exist up front (one unfilteredaggKeypage is usually enough; a game is dozens of markets, not hundreds). - Other enums for sub-grouping/ordering within a tab:
sportsMarketType(moneyline/spread/total/prop/to_advance/completion/other),period(see the full vocabulary below —full/1h/2h/q1..q4/set_N/map_N/inning_N/…),lineValue(number),sectionRank(order).
Game Lines moneyline — source it from the game root
The aggKey set carries moneyline rows from every sibling venue-event, so the raw set can show
duplicate "Draw"s or drop an outcome. For the match-result N-way, take the moneyline markets from
the opened game event's own markets (marketGroup=game_lines on the event you navigated to)
and dedupe; keep the spread/total ladders from the full aggKey set. Listing endpoints already
surface group roots (?grouped=true), so the event you open IS the root in the normal flow.
Spread / total ladders — group by the aggKey line-family, NOT the question
A spread or total is one card with a line selector (O/U 0.5 / 1.5 / 2.5 …), and the same line is
offered by several venues. Do not group these by question or marketSubtype — each venue
phrases and labels them differently for the same market (e.g. "Argentina O/U 0.5" /
"Will over 0.5 goals be scored?" / null), which fragments one ladder into separate cards.
Group by the line-family key: the market aggKey with the line token removed (_<line>, e.g.
_2p5). All lines of one family share it, and it is identical across venues, while still keeping
distinct subjects apart — a match total (…_tot), a team total (…_tot_team_arg), and corners
(…_tot_corners) each get their own family. Within a family, order the ladder by lineValue and
union the venues per line. (This is the one supported structural read of aggKey; see the
aggKey recipe — everything else stays opaque.)
Tab → filter mapping
- Game Lines —
marketGroup ∈ {game_lines, spreads, totals}(moneyline from the root member, above) plussportsMarketType=to_advance(see "Team to Advance" below — it carriesmarketGroup=specialsbut belongs here). - Halves —
marketGroup=period_lines,period ∈ {1h, 2h}(basketball / american_football also expose quartersq1–q4, hockeyperiod_1–period_3— sameperiod_linesgroup, sub-grouped byperiod). Do NOT also filter onsportsMarketType: half-result legs ("France leading at halftime?") aresportsMarketType=otherand an AND'd type filter drops them. - Exact Score —
marketGroup=exact_score - Props —
marketGroup ∈ {team_props, player_props, specials, completion}, excludingsportsMarketType=to_advance(those render under Game Lines). - Other —
marketGroup=other
Team to Advance — a Game Lines market, above the moneyline
Knockout games (World Cup, playoff series, etc.) carry a "Team to Advance" market:
sportsMarketType=to_advance. It is a primary game line — venues render it above the moneyline
— but its marketGroup is specials, so filter it in by sportsMarketType, not marketGroup:
source to_advance from the game set alongside the moneyline and place it first in the Game Lines
tab. For a venue whose only market on a game is the advance single (it lists nothing else), this
is what makes the Game Lines tab non-empty.
Some venues decompose it into per-team Yes/No legs (…_adv_{team}) while others list one 2-outcome
single (…_adv, team-name pills). Group the whole family with the line-family key (below); anchor
the card on the bare-_adv single when present.
Prop tabs via marketCategory (deprecated — use marketGroup instead)
marketCategory is deprecated in favor of marketGroup (see the Fields reference below).
It remains populated for back-compat — the soccer-only prop tabs (corners, goals, assists,
shots) still appear there — but new code should filter by marketGroup.
Each market carries a normalized marketCategory — the subject grouping for the prop tabs,
orthogonal to sportsMarketType:
game_lines— moneyline / spreads / totals (sub-group withsportsMarketType+period)exact_score,corners,goals,assists,shots— the dedicated prop tabs (soccer only)other— recognized sports markets without a dedicated tab;nullfor non-sports markets
Load one prop tab the same way you load Game Lines:
GET /venue-markets?aggKey={gameAggKey}&marketCategory=corners&limit=50
As with every other enum, the API exposes the category; your UI owns the tab label.
- Compose your own tabs/labels from these enums — the API exposes data, not presentation. Group by
marketGroup(see the Tab → filter mapping in step 2); usesportsMarketType/periodonly for sub-grouping within a tab, never to gate a tab (e.g. aperiod_lines"Halves" tab must not filtersportsMarketType, or half-result legs typedothervanish). - Paginate large sections with
cursor/limit.
3. Prices, order ticket, chart
- Live cross-venue prices / best price:
GET /orderbook/midpoints?venueMarketIds=…(the market list carries no prices; midpoints are the price source). - Order ticket / smart routing (split across venues):
GET /orderbook/{venueMarketOutcomeId}/route?maxSpend=…&compareVenues=true→ per-venuefills[]+venueSoloQuotes[]. - Price chart:
GET /charts/bars?venueMarketOutcomeId=…&resolution=5.
Tab labels and grouping are yours to define. The API gives you normalized enums
(sportsMarketType, marketGroup, period, lineValue, sectionRank); map them to whatever
tabs your UI needs. The reference mapping lives in our demo app.
Fields reference
The following fields are available on /venue-events and /venue-markets for sports content.
sport (on VenueEvent)
Normalized cross-venue sport classification. null for non-sports events.
Closed vocabulary:
| Value | Description |
|---|---|
soccer | Association football |
basketball | Basketball (NBA, WNBA, college, international) |
baseball | Baseball (MLB, international) |
american_football | American football (NFL, college) |
hockey | Ice hockey (NHL, international) |
tennis | Tennis (ATP, WTA, Grand Slams) |
cricket | Cricket (Test, ODI, T20) |
golf | Golf (PGA, LIV, majors) |
mma | Mixed martial arts (UFC, Bellator, etc.) |
boxing | Boxing |
motorsport | Racing (F1, NASCAR, IndyCar, etc.) |
esports | Competitive gaming (CS2, Dota 2, LoL, Valorant, Honor of Kings, etc.) |
table_tennis | Table tennis / ping pong |
rugby | Rugby (union & league — Top 14, NRL, Six Nations, etc.) |
volleyball | Volleyball |
pickleball | Pickleball (MLP, PPA) |
lacrosse | Lacrosse (PLL, NLL) |
aussie_rules | Australian rules football (AFL/AFLW) |
other_sport | Any sport not in the list above |
marketGroup (on VenueMarket)
Sport-aware FE grouping. Use this to build per-sport tab sets. Supersedes marketCategory
for sports tabs (see note above). The marketGroup query parameter on /venue-markets accepts
any value from this set.
Closed vocabulary:
| Value | Description |
|---|---|
game_lines | Full-game moneyline / winner markets |
spreads | Handicap / point-spread markets |
totals | Over/under total-points or total-goals markets |
period_lines | Intra-game moneyline, spread, or total scoped to a period (quarter, half, inning, set, map) |
exact_score | Exact final score markets |
team_props | Team-level proposition markets (NRFI, first-5-innings winner, both-teams-to-score, etc.) |
player_props | Player-level proposition markets (points, rebounds, strikeouts, etc.) |
specials | Novelty / event-level specials (toss winner, method of victory, championship outright, team to advance — the last is sportsMarketType=to_advance and belongs in the Game Lines tab, not Props; see above) |
completion | "Will this match complete?" propositions (tennis, cricket) |
other | Sports markets that don't fit the groups above |
marketSubtype (on VenueMarket)
Lossless raw venue market type string preserved from discovery. Examples:
cricket_toss_winner, nrfi, tennis_set_winner, cs2_odd_even_total_kills,
baseball_team_first_five_winner, soccer_halftime_result, map_handicap.
Use marketSubtype to label or disambiguate rows within a marketGroup (e.g. render the
nrfi row with its proper label rather than a generic "Team Props" heading). null when the
venue supplies no subtype.
period (on VenueMarket)
Temporal scope of the market. Expanded from the original set to cover all sports:
| Tokens | Description |
|---|---|
full | Full game / match |
match | Alias for full match (tennis, cricket) |
series | Multi-game series result |
1h / 2h | First / second half (soccer, basketball, american_football) |
q1 / q2 / q3 / q4 | Quarters (basketball, american_football) |
period_1 / period_2 / period_3 | Periods (hockey) |
inning_1 … inning_9 | Individual innings (baseball) |
first_5_innings | First-five-innings scope (baseball) |
first_3_innings / first_7_innings | First-three / first-seven-innings scope (baseball) |
set_1 … set_5 | Individual sets (tennis) |
map_1 … map_5 | Individual maps (esports — CS2, Dota 2, etc.) |
game_1 … game_5 | Individual games within a series (esports, some tennis) |
ot | Overtime / extra time |
Per-sport tab matrix
Use this table as the starting point for building per-sport tab UIs. Render only the
marketGroup values that appear for a given sport; hide empty tabs.
Coverage reflects what venues currently list. other and other_sport are catch-alls. The set
is venue-driven and grows as new market types are listed.
| Sport | marketGroups present | Notable subtypes / periods |
|---|---|---|
soccer | game_lines, spreads, totals, period_lines, exact_score, team_props, specials | soccer_halftime_result (periods 1h/2h), soccer_exact_score, both_teams_to_score; knockout to_advance (in specials, rendered under Game Lines); legacy corners/goals/assists/shots via marketCategory |
basketball | game_lines, spreads, totals, period_lines, player_props | quarters q1–q4; player points / rebounds / assists props |
baseball | game_lines, spreads, totals, team_props, player_props | nrfi (inning_1), baseball_team_first_five_winner (first_5_innings), strikeout / home-run player props |
american_football | game_lines, spreads, totals, period_lines | quarters q1–q4, halves 1h/2h |
hockey | game_lines, spreads, totals, period_lines | period_1–period_3 |
tennis | game_lines, totals, spreads, completion | tennis_set_winner (set_1–set_5), tennis_first_set_totals (set_1), tennis_match_totals (match), tennis_completed_match |
cricket | game_lines, totals, team_props, player_props, specials, completion | cricket_toss_winner (specials), cricket_team_top_batter / cricket_most_sixes (player_props), cricket_completed_match |
golf | game_lines, specials | Outright winner, matchup markets, MVP / championship specials |
mma | game_lines, specials | Fight winner, method-of-victory / round specials |
boxing | game_lines, specials | Fight winner, method / round specials |
motorsport | game_lines, specials | Race winner, podium specials |
esports | game_lines, spreads, totals, player_props, specials | map_handicap (spreads), cs2_odd_even_total_kills / kill_over_under_game (player_props), first_blood_game / dota2_rampage (specials); periods map_1–map_5 / game_1–game_5 |
rugby | game_lines, totals, specials | Match winner, draw, handicap; mostly moneyline today |
volleyball | game_lines, totals, specials | Match/set winner, set totals |
pickleball | game_lines, specials | Match winner; mostly moneyline today |
lacrosse | game_lines, spreads, totals | Moneyline / spread / total |
aussie_rules | game_lines, spreads, totals | Moneyline / line / total (AFL) |
sport is resolved from the venue's league code first, then its tags (which carry a clean
sport name like "Rugby" or "Volleyball"), then the title. Sports a venue lists but that aren't yet
in the vocabulary fall to other_sport (still classified into marketGroups) until added.
Filter examples
Baseball — team props for a specific game
GET /venue-markets?aggKey=agg_mlb_nyy_bos_260618&marketGroup=team_props
Returns the team-prop markets for that game: NRFI (nrfi, period=inning_1), first-5-innings
winner (baseball_team_first_five_winner, period=first_5_innings), and any other team-level
propositions available across venues. Each row's matchedVenueMarkets[] contains the cross-venue
pairings so you can show the best price without a second request.
Tennis — set and match totals
GET /venue-markets?aggKey=agg_atp_djokovic_sinner_260618&marketGroup=totals
Returns all over/under totals for the match: per-set totals (period=set_1, period=set_2, …)
and full-match totals (period=match). Use marketSubtype to distinguish the rendering:
tennis_first_set_totals gets a "First Set" label; tennis_match_totals gets "Match Total".