> ## 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

> How to use venue-events, venue-markets, and orderbook endpoints together to build prediction market UIs

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

<Info>
  Live discovery, orderbook, and routing surfaces include active venues only. Retired venue
  identifiers can still appear in historical positions, activity, and claim flows.
</Info>

## Data model overview

```
VenueEvent (group of related markets)
  └── venueMarkets[] (individual yes/no questions)
        ├── venueMarketOutcomes[] (Yes, No)
        └── matchedVenueMarkets[] (the same market on other venues)
```

Note that `matchedVenueMarkets` hangs off each **market**, not off the event. See
[Matched Clusters](/recipes/matched-clusters) for how cross-venue linkage works and which id to use
as a join key.

**Key IDs the frontend threads through:**

| ID                      | What it identifies                                                                                                     | Where you get it                       |
| ----------------------- | ---------------------------------------------------------------------------------------------------------------------- | -------------------------------------- |
| `venueEvent.id`         | An event (election, game, etc.). For a matched event this is the cluster anchor — use it as your cross-venue join key. | `GET /venue-events`                    |
| `venueMarket.id`        | A specific market question                                                                                             | `venueEvent.venueMarkets[].id`         |
| `venueMarketOutcome.id` | A tradeable outcome (Yes/No)                                                                                           | `venueMarket.venueMarketOutcomes[].id` |

<Warning>
  **Prices are not included in discovery responses.** `GET /venue-events` and
  `GET /venue-markets` return the market structure (events, markets, outcomes) but no
  outcome prices. Fetch live prices in a separate batch call to
  [`GET /midpoints`](/api-reference/orderbook/getmidpoints) using the `venueMarket.id`s you
  collected (see [Bulk midpoint fetch](#4-bulk-midpoint-fetch-—-matched-events-and-markets)),
  or subscribe to the [WebSocket midpoint stream](/api/websocket) for continuous updates.
</Warning>

## 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
```

The list returns one row per matched cluster, and each embedded market carries its counterparts on
other venues in `matchedVenueMarkets[]`. See [Matched Clusters](/recipes/matched-clusters) for how
the cluster is assembled and which id to join on.

**Response shape (simplified):**

```json theme={null}
{
  "data": [
    {
      "id": "ve_abc",
      "title": "2024 Presidential Election",
      "image": "https://cdn.example.com/election.webp",
      "venue": "polymarket",
      "volume": 5000000,
      "status": "open",
      "venues": ["polymarket", "limitless"],
      "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" },
            { "id": "vmo_2", "label": "No" }
          ]
        },
        {
          "id": "vm_2",
          "venue": "limitless",
          "question": "Will candidate X win the election?",
          "volume": 2000000,
          "venueMarketOutcomes": [
            { "id": "vmo_3", "label": "Yes" },
            { "id": "vmo_4", "label": "No" }
          ]
        }
      ]
    }
  ],
  "nextCursor": "ve_def",
  "hasMore": true
}
```

**What to render:** Each event becomes a card. Show `title`, `image`, and the first market's
outcomes. For the price display, batch the page's `venueMarkets[].id`s into one
`GET /midpoints` call (see [Bulk midpoint fetch](#4-bulk-midpoint-fetch-—-matched-events-and-markets))
and key the returned per-outcome midpoints by `venueMarketOutcomeId`. If multiple venues are
present, the midpoints response also carries each sibling's mark so you can show the best
price across venues. Use `venues` and `venueCount` to display venue badges (e.g. "Available
on Polymarket + Limitless") without needing to inspect nested markets.

<Warning>
  The embedded `venueMarkets` on **list** items is a preview — up to three markets chosen to
  represent the event on a card, not the complete set, and not necessarily the largest ones. Render
  counts and totals from `marketCount`, or fetch the full list from
  `GET /venue-markets?venueEventId=`. The detail endpoint (`GET /venue-events/:id`) returns markets
  uncapped.
</Warning>

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

<Warning>
  **Deprecation:** the embedded `venueMarkets` array on `GET /venue-events/:id` is deprecated and
  will be removed. Fetch an event's markets from `GET /venue-markets?venueEventId=` instead — it is
  filterable, paginated, and returns the same matched siblings per market. Markets are still embedded
  by default today; once you've migrated you can request the lean response by sending the `expand`
  query without `markets` (e.g. `?expand=`). A future release will make lean the default — send
  `?expand=markets` to keep them inline across that change. The per-item `description` on the **list**
  endpoint (`GET /venue-events`) is likewise deprecated — fetch the full event (with `description`)
  via `GET /venue-events/:id`.
</Warning>

For the 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`):**

```json theme={null}
{
  "id": "ve_abc",
  "title": "2024 Presidential Election",
  "image": "https://cdn.example.com/election.webp",
  "venue": "polymarket",
  "volume": 5000000,
  "status": "open",
  "venues": ["polymarket", "limitless"],
  "venueCount": 2,
  "venueMarkets": [
    {
      "id": "vm_1",
      "venue": "polymarket",
      "question": "Will candidate X win?",
      "volume": 3000000,
      "venueMarketOutcomes": [
        { "id": "vmo_1", "label": "Yes" },
        { "id": "vmo_2", "label": "No" }
      ],
      "matchedVenueMarkets": [
        {
          "id": "vm_2",
          "venue": "limitless",
          "question": "Will candidate X win the election?",
          "venueMarketOutcomes": [
            { "id": "vmo_3", "label": "Yes" },
            { "id": "vmo_4", "label": "No" }
          ]
        }
      ]
    }
  ]
}
```

Use `matchedVenueMarkets` on each market to collect the `venueMarket.id`s for the
`GET /midpoints` batch call (cross-venue price comparison) and the
`venueMarketOutcome.id` values for orderbook subscriptions.

**Venue markets response (`GET /venue-markets`):**

```json theme={null}
{
  "data": [
    {
      "id": "vm_1",
      "venue": "polymarket",
      "question": "Will candidate X win?",
      "volume": 3000000,
      "status": "open",
      "venues": ["polymarket", "limitless"],
      "venueCount": 2,
      "venueEvent": {
        "id": "ve_abc",
        "title": "2024 Presidential Election",
        "slug": "2024-presidential-election"
      },
      "matchedVenueMarkets": [
        {
          "id": "vm_2",
          "venue": "limitless",
          "question": "Will candidate X win the election?",
          "venueMarketOutcomes": [
            { "id": "vmo_3", "label": "Yes" },
            { "id": "vmo_4", "label": "No" }
          ]
        }
      ],
      "venueMarketOutcomes": [
        { "id": "vmo_1", "label": "Yes" },
        { "id": "vmo_2", "label": "No" }
      ]
    }
  ],
  "nextCursor": null,
  "hasMore": false
}
```

**What to render:** Each market is a row or card. Show the question, outcomes, and volume,
with prices from the batched `GET /midpoints` call. `matchedVenueMarkets` gives you the same
market on other venues — include those ids in the midpoints batch 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:

```json theme={null}
{
  "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:

```json theme={null}
{
  "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 }]
          }
        },
        "limitless": {
          "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": "limitless",
          "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`)

<Tip>
  The `@agg-build/sdk` `useLiveMarket` hook and the WebSocket aggregated orderbook handle
  this merging automatically when you subscribe with multiple outcome IDs.
</Tip>

### 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:

```json theme={null}
{
  "action": "subscribe",
  "channel": "orderbook",
  "outcomeIds": ["vmo_1", "vmo_3"]
}
```

The WebSocket aggregated orderbook snapshot already includes per-venue attribution and
merged levels:

```json theme={null}
{
  "type": "orderbook_snapshot",
  "outcomeId": "vmo_1",
  "bids": [
    [0.55, 2300, { "limitless": 800, "polymarket": 1500 }],
    [0.54, 900, { "polymarket": 900 }],
    [0.53, 800, { "limitless": 800 }]
  ],
  "asks": [
    [0.55, 600, { "limitless": 600 }],
    [0.56, 1200, { "polymarket": 1200 }],
    [0.57, 1200, { "polymarket": 800, "limitless": 400 }]
  ],
  "venueOrderbooks": {
    "limitless": { "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](/api/websocket) 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 call** — `GET /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:

```typescript theme={null}
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](/api-reference/orderbook/getmidpoints) for the
full response schema, including `markSource` provenance.

<Tip>
  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.
</Tip>

For a worked SDK example that compares prices across venues, see
[Comparing Venue Prices](/recipes/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 active venues where the market is available:

```json theme={null}
{
  "quoteId": "q_abc",
  "fills": [
    { "venue": "polymarket", "amount": 60, "price": 0.55 },
    { "venue": "limitless", "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 and venue
contract approvals on the venue's chain (Polymarket / Polygon, Limitless /
Base, predict.fun / BNB). These are paid through the gas paymaster on the
user's first BUY on each (venue, chain) pair 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. `venueMarketAta` remains in the response type for historical
  compatibility; active venue routes use `chainApproval`.
* `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.

```json theme={null}
{
  "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 (structure, no prices)│
│  GET /midpoints?venueMarketIds=…    (batched live 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

| Param         | Type               | Description                                                        |
| ------------- | ------------------ | ------------------------------------------------------------------ |
| `status`      | `string\|string[]` | Market status filter: `open`, `closed`, `resolved`                 |
| `venues`      | `string\|string[]` | Filter by active venue: `polymarket`, `limitless`, `predict`, etc. |
| `matchStatus` | `string\|string[]` | Match status: `matched`, `verified`, `pending`, etc.               |
| `categoryIds` | `string\|string[]` | Filter by category ID                                              |
| `search`      | `string`           | Full-text search on event title/description                        |
| `sortBy`      | `string`           | Sort field: `volume` (default), `createdAt`                        |
| `sortDir`     | `string`           | Sort direction: `desc` (default), `asc`                            |
| `limit`       | `number`           | Page size (1-100, default 50)                                      |
| `cursor`      | `string`           | Pagination cursor from `nextCursor`                                |

### /venue-markets

| Param          | Type               | Description                                                             |
| -------------- | ------------------ | ----------------------------------------------------------------------- |
| `venueEventId` | `string`           | Filter markets by parent event                                          |
| `venue`        | `string`           | Filter by active venue, such as `polymarket`, `limitless`, or `predict` |
| `status`       | `string\|string[]` | Market status filter: `open`, `closed`, `resolved`                      |
| `matchStatus`  | `string`           | Match status filter                                                     |
| `search`       | `string`           | Search on market question or event title                                |
| `categoryIds`  | `string\|string[]` | Filter by category ID                                                   |
| `expand`       | `string`           | Comma-separated: `match_details`, `series`                              |
| `limit`        | `number`           | Page size (1-100, default 50)                                           |
| `cursor`       | `string`           | Pagination cursor                                                       |

## Related

<CardGroup cols={2}>
  <Card title="Comparing Venue Prices" icon="scale-balanced" href="/recipes/comparing-venue-prices">
    Fetch matched events and their midpoints to build a cross-venue price comparison.
  </Card>

  <Card title="WebSocket Protocol" icon="bolt" href="/api/websocket">
    Live orderbook, trades, and order events over WebSocket.
  </Card>

  <Card title="Real-Time Orderbook" icon="code" href="/recipes/websocket-orderbook">
    SDK and hooks for live orderbook rendering.
  </Card>
</CardGroup>
