This guide explains how the discovery and orderbook endpoints compose to power a prediction market
UI — from the home page event grid down to the live trading view.
VenueEvent (group of related markets) ├── venueMarkets[] (individual yes/no questions) │ └── venueMarketOutcomes[] (Yes, No — each with a price) └── matchedVenueMarkets[] (same event on other venues)
Fetch the top events sorted by volume. The default sort is volume desc and the default
feed uses category-interleaved ranking for diversity.
GET /venue-events?status=open&limit=12&sortBy=volume&sortDir=desc
For matched-only events (cross-venue):
GET /venue-events?status=open&matchStatus=matched&matchStatus=verified&limit=12
When matchStatus includes confirmed statuses (matched, verified), the response
automatically includes matchTargets — markets from matched sibling events on other venues.
The venueMarkets array in the response will contain both the event’s own markets and the
grouped markets from matched events.Response shape (simplified):
What to render: Each event becomes a card. Show title, image, and the first market’s
outcomes with prices. Use venueMarkets[0].venueMarketOutcomes for the primary price display.
If multiple venues are present, show the best price across venues. Use venues and
venueCount to display venue badges (e.g. “Available on Polymarket + Kalshi”) without
needing to inspect nested markets.Pagination: Use cursor for infinite scroll. Pass nextCursor as cursor in the next request.
When the user clicks an event card, fetch the full event detail including all markets and
their cross-venue matches:
GET /venue-events/ve_abc
Each market in the response includes matchedVenueMarkets — the same market on other venues
with their outcomes and prices. This gives you everything needed for a cross-venue comparison
view without a separate API call.Alternatively, for a filterable market list, use venue-markets:
GET /venue-markets?venueEventId=ve_abc&status=open
Both endpoints return matched siblings per market.Event detail response (GET /venue-events/:id):
Use matchedVenueMarkets on each market to show cross-venue price comparisons and collect
venueMarketOutcome.id values for orderbook subscriptions.Venue markets response (GET /venue-markets):
What to render: Each market is a row or card. Show the question, outcomes with prices,
and volume. matchedVenueMarkets gives you the same market on other venues — use this for
cross-venue price comparison.
To show a merged orderbook across multiple venues for the same market, pass multiple
venueMarketIds from matched markets. You get these IDs from matchedVenueMarkets in
step 2.
GET /orderbooks?venueMarketIds=vm_1&venueMarketIds=vm_2&depth=20
The response includes per-venue orderbooks keyed by venue name, plus metadata about matched
markets:
From step 2, collect the primary market id and all matchedVenueMarkets[].id values
Pass them all as venueMarketIds to GET /orderbooks
The response groups orderbooks by venue under venueOrderbooks
Merge bids/asks client-side: combine all venue bids at the same price level, sort
descending. Same for asks ascending. Each level shows the total size and per-venue
attribution
matchedMarkets tells you which other venues have this market and whether they have
orderbook data (hasOrderbook)
The @agg-build/sdkuseLiveMarket hook and the WebSocket aggregated orderbook handle
this merging automatically when you subscribe with multiple outcome IDs.
After the initial REST load, subscribe to the WebSocket for real-time updates. Pass outcome
IDs from both the primary and matched markets to receive the aggregated cross-venue stream:
GET /venue-events?status=open&matchStatus=matched&matchStatus=verified&limit=50
Each event’s response includes venueMarkets[] (the event’s markets) and, because we passed
confirmed match statuses, the markets from sibling events on other venues. Walk the response
to gather every venueMarketId you’ll want a midpoint for:
const ids = new Set<string>();for (const event of events) { for (const market of event.venueMarkets ?? []) { ids.add(market.id); for (const matched of market.matchedVenueMarkets ?? []) { ids.add(matched.id); } }}
Pass the collected IDs to /midpoints. The endpoint proxies the live orderbook engine and
returns the current Yes-side mark per market, plus per-outcome midpoints and the sibling
markets the engine considered.
GET /midpoints?venueMarketIds=vm_1&venueMarketIds=vm_2&venueMarketIds=vm_3
Each entry includes the headline midpoint, per-outcome midpoints, and the matched siblings
the engine considered. See the API Reference for the
full response schema, including markSource provenance.
Cap each request at 200 IDs. For larger universes, chunk the IDs into batches of 200 and
fire the requests in parallel — the endpoint is read-only and idempotent.
For a worked SDK example that compares prices across venues, see
Comparing Venue Prices.
Some venues require one-time setup before a user can trade — ERC-20 + venue
contract approvals on the venue’s chain (Polymarket / Polygon, Limitless /
Base, predict.fun / BNB) and a per-market USDC token-account rent on Solana
(Kalshi). These are paid through the gas paymaster on the user’s first BUY
on each (venue, chain) pair (or per Kalshi market) and never again.Add deepEstimate=true to the route call to surface those costs in the
response:
GET /orderbook/vmo_1/route?maxSpend=100&deepEstimate=true
The response’s feeBreakdown then includes:
setupCosts — one entry per setup item the route would touch, with
kind: "chainApproval" | "venueMarketAta", the cost in USD, and an
alreadyPaid flag indicating whether the user has already settled it on a
prior fill.
setupCostsTotal — sum of costUsd for entries where alreadyPaid: false.
This is also folded into the top-level totalCostIncFees so a single number
reflects the realistic total the user will see at execution time.
Use the per-line alreadyPaid to render strike-through “first-time fee”
chips for new users without double-charging returning ones. The deep surface
is buy-only (sells never trigger first-time approvals) and is omitted on
quotes where the engine could not produce an executable plan
(status !== "ok").