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.
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).
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)
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
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:
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:
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:
Per-sport tab matrix
Use this table as the starting point for building per-sport tab UIs. Render only themarketGroup 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 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
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
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”.