What a cluster is
A cluster is a set of venue events that AGG has determined are the same real-world event, plus the market- and outcome-level pairings between them.Which id to join on
GET /venue-events returns one row per cluster. Every cluster has one anchor event, and the
list collapses each cluster down to it — member rows are not returned separately.
Do not invent your own cross-venue key from titles, slugs or aggKey (see
Where aggKey fits below), and do not assume two venues’ identifiers can be
related to each other without AGG’s matching — that is the problem matching exists to solve. Storing
a single venue’s identifier as a durable handle you re-resolve from is a different thing, and is
supported: see the tip below.
Cluster fields, and where they live
Two different scopes are easy to confuse:venues[] tells you how many venues a cluster spans; matchedVenueEvents[] tells you which
events they are. Before, the only way to enumerate the members was to flatten
venueMarkets[].matchedVenueMarkets[].venueEvent — which under-reported, because the embedded
market array on list responses is a top-three preview and a member with no matched markets never
appeared at all. Use matchedVenueEvents[] instead.matchedVenueMarkets[], not off the event row. To show a best price,
collect each market’s own id plus its matchedVenueMarkets[].id, then batch them into one
GET /midpoints call, or subscribe to those ids on the
WebSocket midpoint stream.
Starting from a venue identifier
If you already hold a venue’s identifier — a Kalshi ticker, a Polymarket id, a Predict slug — you can resolve it straight to its cluster. This works at all three levels, and it is bidirectional: it does not matter whether the identifier you hold belongs to the cluster’s anchor or to one of its members. You get the same cluster either way.Events
matchedVenueEvents[] carries the rest of the
cluster:
matchedVenueEvents[]. Same cluster, four different entry points.
Markets
matchedVenueMarkets[] carries the same market on every other venue. Note that a cluster can contain
two markets from the same venue — Kalshi lists a head-to-head as one market per team, and both are
matched in, so [kalshi, kalshi, limitless, predict] is a correct result, not a duplicate.
Outcomes
matchedVenueMarketOutcomes[] gives the aligned outcome on each matched venue as
{ venueMarketId, venueMarketOutcomeId } id pairs. Alignment is by meaning, not by label position —
where two venues frame the same question in opposite directions, the pairing still points at the
outcome that means the same thing. Fetch the full objects from
GET /venue-markets if you need more than ids.
Rules
venueis required on/venue-eventsand/venue-markets, because a venue identifier is only unique within its venue. Omitting it returns400.venueis optional on/venue-market-outcomes— outcome identifiers are globally unique — where it acts as a narrowing filter only.- Batch up to 20 identifiers per request by repeating the parameter. Over 20 returns
400. - Unknown identifiers are omitted from
datarather than returned as nulls, so a batch of 20 may return fewer than 20 rows. Match responses back to your input by readingexternalIdentifieron each item. - Members are returned as themselves. These lookups deliberately do not collapse a cluster to its anchor the way the browse listing does — you asked for a specific venue’s row, so you get it.
Querying a member id
GET /venue-events/{id} does not resolve a member id up to its anchor — it returns exactly the row
you asked for. That row is still usable for rendering, because each of its markets carries the full
set of matchedVenueMarkets[] siblings regardless of which member you queried.
What differs between an anchor and a member response:
So a member row can be missing markets the anchor carries — props or extra legs with no counterpart
elsewhere — and its title and description are the member venue’s wording. For a canonical view of
the cluster, query the id you got from
GET /venue-events.
What does not differ is the cluster itself: matchedVenueEvents[], matchedVenueMarkets[] and
matchedVenueMarketOutcomes[] are complete from any member. If all you need is “which venues carry
this, and under which identifiers”, any member answers it.
Market lists: preview vs full
GET /venue-events/{id} returns the event’s markets uncapped. Both embedded arrays are deprecated:
fetch markets from GET /venue-markets?venueEventId=,
which is filterable, paginated, and returns the same matchedVenueMarkets[] siblings per market.
?venueEventId= is cluster-scoped by default: it returns every venue’s markets for that
real-world event, with matched siblings folded into a single anchor row rather than repeated, and
each row still carrying its counterparts inline in matchedVenueMarkets[]. Name any member of the
cluster and you get the same page in the same order, so you do not need to know which id is the
anchor. It is also the only way to reach markets that trade on a single venue and so have no
cross-venue counterpart.
Send cluster=false for the older behaviour — that one event’s own markets, 1:1 with the ids in
its venueMarkets[]. Use it when you are rendering one venue’s book specifically, and pass the
same value to GET /venue-events/{id}, whose marketCount follows the same flag.
marketCount counts exactly the rows GET /venue-markets?venueEventId= returns for this event
(open markets, matched siblings folded into their anchor), so you can page to it. Pass
status=open on the market list to see the same set — without a status filter the list returns
closed and resolved markets too, which marketCount does not count. The rolled-up volume is the
cluster’s total traded value and sums every market in it, including the siblings folded out of
that list — the two are not totals over the same set.Companion events: why a cluster can look empty
A venue often splits one game into several events: a base plus companions like… - More Markets, … - Exact Score, … : Spread, … : BTTS. They share one aggKey and each
carries its own id.
Two events from the same venue never share a cluster, with one exception:
Hyperliquid deployers. So a companion clusters with the other
venues’ equivalent companions — not with the game’s main cluster. Ask for a companion and you can
legitimately get an empty matchedVenueEvents[] for a game that trades on four venues:
groupParentId is non-null, which is how you tell. Follow it
and the same game looks like this:
aggKey—GET /venue-events?aggKey={aggKey}returns every event of that game, across companions and venues. This is the route the game-detail page uses; see Sports Market Detail.groupParentId—GET /venue-events/{groupParentId}for the base event, whosematchedVenueEvents[]is the game’s cross-venue cluster.
Cross-venue pricing for a companion’s markets never depended on this. Each market carries its
own
matchedVenueMarkets[] regardless of how the events cluster, so best-price and arbitrage
flows work on a companion exactly as they do on a base event — and GET /venue-markets?venueEventId= on a companion returns its markets in full, cluster scope or not.
An empty matchedVenueEvents[] never means an empty market list.grouped=true on the listing to fold companions under their base and get one tile per game —
opt-in, because it hides the companions that a game-detail page is expected to re-surface via
aggKey.
Where aggKey fits
aggKey is a deterministic canonical key computed at discovery time from each
venue’s native data. It is one input to matching and a convenient fetch filter — it is not the
mechanism that produces clusters, and it is not a join key.
- It is
nullfor a substantial share of rows. Grouping byaggKeysilently under-groups every cluster whose members could not be canonicalized. - It is guaranteed identical across venues only where every venue exposes the same normalizable inputs — sports head-to-head and crypto up/down markets. Elsewhere it is derived from each venue’s own slug or ticker and its own resolution timestamp, so two members of the same cluster can carry different keys.
aggKey to fetch related rows for a key you already hold. Use the cluster and its anchor id
for identity.
Related
Look Up by Venue Identifier
Resolve a Kalshi ticker or Polymarket token_id to its cluster, in batch, at any level.
Comparing Venue Prices
Fetch matched events and their midpoints to build a cross-venue price comparison.
Building Market Views
Compose discovery and orderbook endpoints into event grids and trading views.
Canonical Market Key (aggKey)
What aggKey is, when it is null, and the one supported way to parse it.
Settlement Differences
Surface resolution-criteria differences between venues in a cluster.