> ## Documentation Index
> Fetch the complete documentation index at: https://docs.struct.to/llms.txt
> Use this file to discover all available pages before exploring further.

# Building a live trending feed

> Build a trending feed that seeds from the events or markets list and stays warm via the events stream and markets stream rooms.

A trending feed is a list ranked by recent activity (volume, trade count, unique traders) that updates as activity changes. Struct exposes the data in two layers: REST listings to seed initial state, and the corresponding stream rooms to push deltas. Polymarket data is split into two surfaces, events (multi-market questions) and markets (single conditions), so this recipe covers both side by side.

## When to use this

* Discovery surfaces: homepage rails, "what's hot now" widgets, category browsers.
* Live leaderboards ranked by 24h volume, trade count, or unique traders.
* Any UI where a row's metrics dictate ranking and the ranking itself shifts in real time.

## Choose your surface

| Surface | When to use                                                                                    | REST                        | Stream                      |
| ------- | ---------------------------------------------------------------------------------------------- | --------------------------- | --------------------------- |
| Events  | Multi-market questions (election, tournament, weekly series). Rows include nested `markets[]`. | `client.events.getEvents`   | `polymarket_events_stream`  |
| Markets | Single condition rows (one outcome pair). Rows include `outcomes[]` and `clob_rewards[]`.      | `client.markets.getMarkets` | `polymarket_markets_stream` |

Both stream rooms push **full rows** (not deltas) for whichever rows changed since the last flush tick, so client merging is by primary key (`id` for events, `condition_id` for markets) and re-ranking happens on each tick.

## Step 1: seed from REST

<CodeGroup>
  ```typescript SDK theme={null}
  import { StructClient } from "@structbuild/sdk";

  const client = new StructClient({ apiKey: "sk_live_xxx" });

  const { data: events } = await client.events.getEvents({ limit: 50 });
  const { data: markets } = await client.markets.getMarkets({ limit: 50 });

  const trendingEvents = [...events].sort(
    (a, b) => (b.metrics?.["24h"]?.volume ?? 0) - (a.metrics?.["24h"]?.volume ?? 0),
  );

  const trendingMarkets = [...markets].sort(
    (a, b) => (b.metrics?.["24h"]?.volume ?? 0) - (a.metrics?.["24h"]?.volume ?? 0),
  );
  ```

  ```bash cURL theme={null}
  curl "https://api.struct.to/v1/polymarket/events?limit=50" \
    -H "X-API-Key: YOUR_API_KEY"

  curl "https://api.struct.to/v1/polymarket/market?limit=50" \
    -H "X-API-Key: YOUR_API_KEY"
  ```

  ```python Python theme={null}
  import requests

  events = requests.get(
      "https://api.struct.to/v1/polymarket/events",
      headers={"X-API-Key": "YOUR_API_KEY"},
      params={"limit": 50},
  ).json()["data"]

  markets = requests.get(
      "https://api.struct.to/v1/polymarket/market",
      headers={"X-API-Key": "YOUR_API_KEY"},
      params={"limit": 50},
  ).json()["data"]

  trending_events = sorted(events, key=lambda e: (e.get("metrics", {}).get("24h", {}).get("volume") or 0), reverse=True)
  trending_markets = sorted(markets, key=lambda m: (m.get("metrics", {}).get("24h", {}).get("volume") or 0), reverse=True)
  ```
</CodeGroup>

Both row shapes carry a `metrics` map keyed by timeframe (`1m`, `5m`, `30m`, `1h`, `6h`, `24h`, `7d`, `30d`) with `volume`, `fees`, `txns`, and `unique_traders` per window. Pick the timeframe that matches the rail's framing and sort by it client-side.

## Step 2: subscribe to deltas

Both stream rooms accept the same filters as their REST list counterparts and push every matching row that changed.

<CodeGroup>
  ```typescript SDK theme={null}
  import { StructWebSocket } from "@structbuild/sdk";

  const ws = new StructWebSocket({ apiKey: "sk_live_xxx" });
  await ws.connect();

  await ws.subscribe("polymarket_events_stream", {
    interval_ms: 1000,
    mode: "filter",
    filter: { timeframe: "24h", min_volume: 10000 },
  });

  await ws.subscribe("polymarket_markets_stream", {
    interval_ms: 1000,
    mode: "filter",
    filter: { timeframe: "24h", min_volume: 50000 },
  });

  ws.on("events_stream_update", (event) => {
    for (const row of event.data) eventsById.set(row.id, row);
    rerankEvents();
  });

  ws.on("markets_stream_update", (event) => {
    for (const row of event.data) marketsByCondition.set(row.condition_id, row);
    rerankMarkets();
  });
  ```

  ```bash cURL theme={null}
  > {"type":"join_room","payload":{"room_id":"polymarket_events_stream"}}
  > {"type":"room_message","payload":{"room_id":"polymarket_events_stream","message":{"action":"subscribe","interval_ms":1000,"mode":"filter","filter":{"timeframe":"24h","min_volume":10000}}}}

  > {"type":"join_room","payload":{"room_id":"polymarket_markets_stream"}}
  > {"type":"room_message","payload":{"room_id":"polymarket_markets_stream","message":{"action":"subscribe","interval_ms":1000,"mode":"filter","filter":{"timeframe":"24h","min_volume":50000}}}}
  ```

  ```python Python theme={null}
  import json

  await ws.send(json.dumps({"type": "join_room", "payload": {"room_id": "polymarket_events_stream"}}))
  await ws.send(json.dumps({
      "type": "room_message",
      "payload": {
          "room_id": "polymarket_events_stream",
          "message": {
              "action": "subscribe",
              "interval_ms": 1000,
              "mode": "filter",
              "filter": {"timeframe": "24h", "min_volume": 10000},
          },
      },
  }))
  ```
</CodeGroup>

See [Events Stream](/websockets/rooms/events-stream) and [Markets Stream](/websockets/rooms/markets-stream) for the full payload schemas.

## Step 3: re-rank on each tick

Keep an in-memory map keyed by primary key, merge each pushed row in place, then re-sort. Quiet rows produce zero messages, so re-ranking is bounded by how many rows actually shifted.

```typescript theme={null}
const eventsById = new Map<string, PolymarketEvent>();
const marketsByCondition = new Map<string, MarketResponse>();

function rerankEvents() {
  const ranked = [...eventsById.values()].sort(
    (a, b) => (b.metrics?.["24h"]?.volume ?? 0) - (a.metrics?.["24h"]?.volume ?? 0),
  );
  renderTrendingEvents(ranked.slice(0, 20));
}

function rerankMarkets() {
  const ranked = [...marketsByCondition.values()].sort(
    (a, b) => (b.metrics?.["24h"]?.volume ?? 0) - (a.metrics?.["24h"]?.volume ?? 0),
  );
  renderTrendingMarkets(ranked.slice(0, 20));
}
```

## Choosing a cadence

Each stream room offers four flush slots: `interval_ms` of `500`, `1000`, `3000`, or `10000`. Pick `1000` for an interactive trending rail, `3000` for a sidebar widget, `10000` for a low-traffic dashboard. Each `(interval_ms, mode)` pair is its own slot; up to 8 slots per room.

## Common combinations

| Goal                   | Filter                                                                                     |
| ---------------------- | ------------------------------------------------------------------------------------------ |
| Crypto trending        | `categories=["crypto"], timeframe="24h", min_volume=50000`                                 |
| New and busy           | `start_time={now-86400}, min_unique_traders=50`                                            |
| Search-driven trending | `search="election", timeframe="24h"` (3 to 100 chars, case-insensitive substring on title) |
| Rewarded markets only  | `has_rewards=true, timeframe="24h"` (markets stream only)                                  |
| Track by id            | `mode="ids", event_slugs=[...]` or `mode="ids", condition_ids=[...]`                       |

## Follow-on

If trending also needs to highlight markets paying CLOB rewards, the [Markets Stream](/websockets/rooms/markets-stream) row carries a `clob_rewards[]` array and a `total_daily_rate` summary field per row.
