Skip to main content
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=…
  • aggKey is 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 on matchedVenueMarkets[]).
  • 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 with cursor/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 unfiltered aggKey page 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 LinesmarketGroup ∈ {game_lines, spreads, totals} (moneyline from the root member, above) plus sportsMarketType=to_advance (see “Team to Advance” below — it carries marketGroup=specials but belongs here).
  • HalvesmarketGroup=period_lines, period ∈ {1h, 2h} (basketball / american_football also expose quarters q1q4, hockey period_1period_3 — same period_lines group, sub-grouped by period). Do NOT also filter on sportsMarketType: half-result legs (“France leading at halftime?”) are sportsMarketType=other and an AND’d type filter drops them.
  • Exact ScoremarketGroup=exact_score
  • PropsmarketGroup ∈ {team_props, player_props, specials, completion}, excluding sportsMarketType=to_advance (those render under Game Lines).
  • OthermarketGroup=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 with sportsMarketType + period)
  • exact_score, corners, goals, assists, shots — the dedicated prop tabs (soccer only)
  • other — recognized sports markets without a dedicated tab; null for 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); use sportsMarketType/period only for sub-grouping within a tab, never to gate a tab (e.g. a period_lines “Halves” tab must not filter sportsMarketType, or half-result legs typed other vanish).
  • 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-venue fills[] + 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 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 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

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

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”.