Skip to main content

Documentation Index

Fetch the complete documentation index at: https://docs.agg.market/llms.txt

Use this file to discover all available pages before exploring further.

Building Market Views

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.

Data model overview

VenueEvent (group of related markets)
  ├── venueMarkets[] (individual yes/no questions)
  │     └── venueMarketOutcomes[] (Yes, No — each with a price)
  └── matchedVenueMarkets[] (same event on other venues)
Key IDs the frontend threads through:
IDWhat it identifiesWhere you get it
venueEvent.idAn event (election, game, etc.)GET /venue-events
venueMarket.idA specific market questionvenueEvent.venueMarkets[].id
venueMarketOutcome.idA tradeable outcome (Yes/No)venueMarket.venueMarketOutcomes[].id

1. Home page — event grid

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):
{
  "data": [
    {
      "id": "ve_abc",
      "title": "2024 Presidential Election",
      "image": "https://cdn.example.com/election.webp",
      "venue": "polymarket",
      "volume": 5000000,
      "status": "open",
      "venues": ["polymarket", "kalshi"],
      "venueCount": 2,
      "categories": [{ "id": "c1", "category": { "id": "c1", "name": "Politics" } }],
      "venueMarkets": [
        {
          "id": "vm_1",
          "venue": "polymarket",
          "question": "Will candidate X win?",
          "volume": 3000000,
          "venueMarketOutcomes": [
            { "id": "vmo_1", "label": "Yes", "price": 0.55 },
            { "id": "vmo_2", "label": "No", "price": 0.45 }
          ]
        },
        {
          "id": "vm_2",
          "venue": "kalshi",
          "question": "Will candidate X win the election?",
          "volume": 2000000,
          "venueMarketOutcomes": [
            { "id": "vmo_3", "label": "Yes", "price": 0.53 },
            { "id": "vmo_4", "label": "No", "price": 0.47 }
          ]
        }
      ]
    }
  ],
  "nextCursor": "ve_def",
  "hasMore": true
}
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.

2. Event detail — market list

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):
{
  "id": "ve_abc",
  "title": "2024 Presidential Election",
  "image": "https://cdn.example.com/election.webp",
  "venue": "polymarket",
  "volume": 5000000,
  "status": "open",
  "venues": ["polymarket", "kalshi"],
  "venueCount": 2,
  "venueMarkets": [
    {
      "id": "vm_1",
      "venue": "polymarket",
      "question": "Will candidate X win?",
      "volume": 3000000,
      "venueMarketOutcomes": [
        { "id": "vmo_1", "label": "Yes", "price": 0.55 },
        { "id": "vmo_2", "label": "No", "price": 0.45 }
      ],
      "matchedVenueMarkets": [
        {
          "id": "vm_2",
          "venue": "kalshi",
          "question": "Will candidate X win the election?",
          "venueMarketOutcomes": [
            { "id": "vmo_3", "label": "Yes", "price": 0.53 },
            { "id": "vmo_4", "label": "No", "price": 0.47 }
          ]
        }
      ]
    }
  ]
}
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):
{
  "data": [
    {
      "id": "vm_1",
      "venue": "polymarket",
      "question": "Will candidate X win?",
      "volume": 3000000,
      "status": "open",
      "venues": ["polymarket", "kalshi"],
      "venueCount": 2,
      "venueEvent": {
        "id": "ve_abc",
        "title": "2024 Presidential Election",
        "slug": "2024-presidential-election"
      },
      "matchedVenueMarkets": [
        {
          "id": "vm_2",
          "venue": "kalshi",
          "question": "Will candidate X win the election?",
          "venueMarketOutcomes": [
            { "id": "vmo_3", "label": "Yes", "price": 0.53 },
            { "id": "vmo_4", "label": "No", "price": 0.47 }
          ]
        }
      ],
      "venueMarketOutcomes": [
        { "id": "vmo_1", "label": "Yes", "price": 0.55 },
        { "id": "vmo_2", "label": "No", "price": 0.45 }
      ]
    }
  ],
  "nextCursor": null,
  "hasMore": false
}
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.

3. Market detail — live orderbook

When the user selects a specific outcome to trade, fetch the live orderbook.

Single-outcome orderbook

For a single outcome on a single venue:
GET /orderbook/outcome/vmo_1
Returns per-venue bid/ask levels:
{
  "venueMarketOutcomeId": "vmo_1",
  "venueMarketId": "vm_1",
  "venue": "polymarket",
  "orderbook": {
    "bids": [{ "price": 0.55, "size": 1500 }, { "price": 0.54, "size": 900 }],
    "asks": [{ "price": 0.56, "size": 1200 }, { "price": 0.57, "size": 800 }]
  },
  "midpoint": 0.555,
  "spread": 0.01,
  "timestamp": 1710000000000
}

Merged cross-venue orderbook (aggregated view)

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:
{
  "data": [
    {
      "venueMarketId": "vm_1",
      "status": "ok",
      "error": null,
      "requestedMarket": {
        "venueMarketId": "vm_1",
        "venue": "polymarket",
        "marketStatus": "open",
        "tickSize": 0.01,
        "endDate": "2024-11-06T00:00:00Z",
        "resolutionDate": null
      },
      "venueOrderbooks": {
        "polymarket": {
          "venueMarketId": "vm_1",
          "tickSize": 0.01,
          "orderbook": {
            "bids": [{ "price": 0.55, "size": 1500 }, { "price": 0.54, "size": 900 }],
            "asks": [{ "price": 0.56, "size": 1200 }]
          }
        },
        "kalshi": {
          "venueMarketId": "vm_2",
          "tickSize": 0.01,
          "orderbook": {
            "bids": [{ "price": 0.53, "size": 800 }],
            "asks": [{ "price": 0.55, "size": 600 }, { "price": 0.57, "size": 400 }]
          }
        }
      },
      "matchedMarkets": [
        {
          "venue": "kalshi",
          "venueMarketId": "vm_2",
          "marketStatus": "open",
          "tickSize": 0.01,
          "hasOrderbook": true
        }
      ]
    }
  ],
  "meta": {
    "requestedCount": 1,
    "okCount": 1,
    "errorCount": 0
  }
}
How to build a merged view:
  1. From step 2, collect the primary market id and all matchedVenueMarkets[].id values
  2. Pass them all as venueMarketIds to GET /orderbooks
  3. The response groups orderbooks by venue under venueOrderbooks
  4. 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
  5. matchedMarkets tells you which other venues have this market and whether they have orderbook data (hasOrderbook)
The @agg-build/sdk useLiveMarket hook and the WebSocket aggregated orderbook handle this merging automatically when you subscribe with multiple outcome IDs.

WebSocket orderbook (live updates)

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:
{
  "action": "subscribe",
  "channel": "orderbook",
  "outcomeIds": ["vmo_1", "vmo_3"]
}
The WebSocket aggregated orderbook snapshot already includes per-venue attribution and merged levels:
{
  "type": "orderbook_snapshot",
  "outcomeId": "vmo_1",
  "bids": [
    [0.55, 2300, { "kalshi": 800, "polymarket": 1500 }],
    [0.54, 900, { "polymarket": 900 }],
    [0.53, 800, { "kalshi": 800 }]
  ],
  "asks": [
    [0.55, 600, { "kalshi": 600 }],
    [0.56, 1200, { "polymarket": 1200 }],
    [0.57, 1200, { "polymarket": 800, "kalshi": 400 }]
  ],
  "venueOrderbooks": {
    "kalshi": { "bids": [[0.55, 800], [0.53, 800]], "asks": [[0.55, 600], [0.57, 400]] },
    "polymarket": { "bids": [[0.55, 1500], [0.54, 900]], "asks": [[0.56, 1200], [0.57, 800]] }
  },
  "midpoint": 0.555,
  "spread": 0.005,
  "timestamp": 1710000000000
}
You’ll receive a full snapshot followed by incremental deltas. See the WebSocket Protocol docs for sequencing, checksums, and resync logic.

4. Bulk midpoint fetch — matched events and markets

When you want a price-comparison view across many events at once (e.g. a homepage grid that shows live “best price across venues”), you typically:
  1. List matched events — only events confirmed to exist on more than one venue.
  2. Collect every venueMarketId — the event’s own markets plus their matchedVenueMarkets.
  3. Fetch midpoints in one batch callGET /midpoints accepts up to 200 IDs.

List matched events

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);
    }
  }
}

Batch midpoints

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.

5. Trading — smart route and execution

When the user wants to place a trade, compute the optimal route across venues:
GET /orderbook/vmo_1/route?maxSpend=100&slipCapBps=50
This returns a quote with the best fills across all venues where the market is available:
{
  "quoteId": "q_abc",
  "fills": [
    { "venue": "polymarket", "amount": 60, "price": 0.55 },
    { "venue": "kalshi", "amount": 40, "price": 0.53 }
  ],
  "totalCost": "5340000",
  "estimatedAvgPrice": 0.534
}
Execute the quote:
POST /execution/fill
{ "quoteId": "q_abc" }
Track progress via WebSocket order_event messages (step progress, fill confirmation, errors).

Deep cost estimate (deepEstimate=true)

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.
{
  "feeBreakdown": {
    "rawExecCost": 53.40,
    "venueFees": 0.21,
    "bridgeFees": 0.40,
    "executionGas": 0.05,
    "totalCost": 54.06,
    "setupCosts": [
      { "kind": "chainApproval", "venue": "polymarket", "chainId": 137,
        "costUsd": 0.05, "alreadyPaid": true },
      { "kind": "chainApproval", "venue": "limitless", "chainId": 8453,
        "costUsd": 0.05, "alreadyPaid": false }
    ],
    "setupCostsTotal": 0.05
  }
}
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").

End-to-end data flow

┌─────────────────────────────────────────────────────────┐
│  Home Page                                              │
│  GET /venue-events?status=open&sortBy=volume&sortDir=desc│
│  → Event cards with nested markets + prices             │
└──────────────┬──────────────────────────────────────────┘
               │ user clicks event

┌─────────────────────────────────────────────────────────┐
│  Event Detail                                           │
│  GET /venue-markets?venueEventId=ve_abc&status=open     │
│  → Market list with outcomes + cross-venue matches      │
└──────────────┬──────────────────────────────────────────┘
               │ user clicks outcome

┌─────────────────────────────────────────────────────────┐
│  Trading View                                           │
│  GET /orderbooks?venueMarketIds=vm_1&venueMarketIds=vm_2│
│    → merged cross-venue orderbook (REST initial load)   │
│  WS subscribe orderbook vmo_1,vmo_3  (live aggregated)  │
│  GET /orderbook/vmo_1/route          (compute quote)    │
│  POST /execution/fill                (execute trade)    │
│  WS order_event                      (track progress)   │
└─────────────────────────────────────────────────────────┘

Filters reference

/venue-events

ParamTypeDescription
statusstring|string[]Market status filter: open, closed, resolved
venuesstring|string[]Filter by venue: polymarket, kalshi, etc.
matchStatusstring|string[]Match status: matched, verified, pending, etc.
categoryIdsstring|string[]Filter by category ID
searchstringFull-text search on event title/description
sortBystringSort field: volume (default), createdAt
sortDirstringSort direction: desc (default), asc
limitnumberPage size (1-100, default 50)
cursorstringPagination cursor from nextCursor

/venue-markets

ParamTypeDescription
venueEventIdstringFilter markets by parent event
venuestringFilter by venue
statusstring|string[]Market status filter: open, closed, resolved
matchStatusstringMatch status filter
searchstringSearch on market question or event title
categoryIdsstring|string[]Filter by category ID
expandstringComma-separated: match_details, series
limitnumberPage size (1-100, default 50)
cursorstringPagination cursor

Comparing Venue Prices

Fetch matched events and their midpoints to build a cross-venue price comparison.

WebSocket Protocol

Live orderbook, trades, and order events over WebSocket.

Real-Time Orderbook

SDK and hooks for live orderbook rendering.