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

# Markets Stream

> Stream real-time Polymarket market rows with filter or ids mode and configurable cadence.

<Note>
  **Room ID:** `polymarket_markets_stream` \
  **Endpoint:** `wss://api.struct.to/ws` \
  **Rate:** 0.025 credits per message
</Note>

Low-latency push feed of market rows — same shape `GET /polymarket/market` returns, including per-outcome price with `latest_block` + `latest_confirmed_at` watermarks. The server maintains an in-memory cache of **open** markets only, refreshed via a slow full poll plus a fast 500ms newest-first poll, and merged live from the `prediction_condition_metrics` and `prediction_trades` Kafka streams with per-timeframe block/timestamp ordering.

No initial snapshot is pushed on subscribe. Clients seed from `GET /polymarket/market` and then apply deltas from this stream.

<Tip>
  **Related guides:** [Building a live trending feed](/guides/real-time-trending-events-and-markets) seeds from the markets list and stays warm via this stream, and [Ranking markets by CLOB rewards](/guides/clob-liquidity-markets-dashboard) tracks live reward changes through it.
</Tip>

## Subscription model

Each client has up to **8 active slots per room** (4 cadences × 2 modes). Re-subscribing to the same `(interval_ms, mode)` pair replaces the previous subscription. Unsubscribe one slot with `action: "unsubscribe"` plus `interval_ms` and `mode`, or clear everything with `action: "unsubscribe_all"`.

* **Cadence (`interval_ms`):** `500`, `1000`, `3000`, or `10000`.
* **Filter mode:** same validation as the REST list endpoint (timeframe, search length, list caps). `search` is a case-insensitive substring match on `title`. No sort / limit — you get every matching row that changed.
* **Ids mode:** any combination of `condition_ids`, `market_slugs`, and `event_slugs` (matches all child markets of those events). Max **500 ids total** per subscription.

Updates fire only when a cache row is dirtied by (a) a fresh Kafka metric snapshot with `latest_block >= cached`, (b) a confirmed trade with `block >= cached`, (c) a slow-poll field diff, or (d) the fast newest-first poll discovering a brand-new market. Quiet markets produce zero messages.

Each outcome in `outcomes[]` carries `latest_block` and `latest_confirmed_at` (Unix seconds) — the block/ts of the most recent price write from `prediction_position_metrics`. Consumers can use these to reject out-of-order price merges locally.

## Subscribe

### Message fields

| Field | Type | Required | Description |
| - | - | - | - |
| `action` | `"subscribe" \| "unsubscribe" \| "unsubscribe_all"` | Yes | Slot lifecycle action. |
| `interval_ms` | `500 \| 1000 \| 3000 \| 10000` | For subscribe / unsubscribe | Flush cadence. Defaults to `1000` if omitted. |
| `mode` | `"filter" \| "ids"` | No | Subscription mode. Defaults to `filter`. |
| `filter` | `MarketsStreamFilter` | `mode=filter` only | Filter body; all fields optional. |
| `condition_ids` | `string[]` | `mode=ids` only | 0x-prefixed lowercase 32-byte hex. |
| `market_slugs` | `string[]` | `mode=ids` only | Market slug strings. |
| `event_slugs` | `string[]` | `mode=ids` only | Event slugs — matches every child market of each event. |

### Filter fields (`mode=filter`)

Supports the same filters as the REST markets list: `search`, `categories`, `exclude_categories`, `tags`, `exclude_tags`, `min_volume` / `max_volume`, `min_txns` / `max_txns`, `min_unique_traders` / `max_unique_traders`, `min_liquidity` / `max_liquidity`, `min_holders` / `max_holders`, `start_time` / `end_time`, `has_rewards`, and `timeframe` (`1m`, `5m`, `30m`, `1h`, `6h`, `24h`, `7d`, `30d`).

`status` is **not** accepted — the cache only holds open markets.

### Example — filter mode

```json theme={null}
{
  "type": "join_room",
  "payload": {
    "room_id": "polymarket_markets_stream"
  }
}
```

```json theme={null}
{
  "type": "room_message",
  "payload": {
    "room_id": "polymarket_markets_stream",
    "message": {
      "action": "subscribe",
      "interval_ms": 1000,
      "mode": "filter",
      "filter": {
        "search": "bitcoin",
        "categories": ["crypto"],
        "min_volume": 50000,
        "timeframe": "24h",
        "has_rewards": true
      }
    }
  }
}
```

### Example — ids mode

```json theme={null}
{
  "type": "room_message",
  "payload": {
    "room_id": "polymarket_markets_stream",
    "message": {
      "action": "subscribe",
      "interval_ms": 500,
      "mode": "ids",
      "condition_ids": ["0xabc123..."],
      "market_slugs": ["will-bitcoin-hit-100k"],
      "event_slugs": ["bitcoin-price-markets"]
    }
  }
}
```

### Unsubscribe one slot

```json theme={null}
{
  "type": "room_message",
  "payload": {
    "room_id": "polymarket_markets_stream",
    "message": {
      "action": "unsubscribe",
      "interval_ms": 500,
      "mode": "ids"
    }
  }
}
```

### Response

```json theme={null}
{
  "type": "markets_stream_subscribe_response",
  "room_id": "polymarket_markets_stream",
  "data": {
    "mode": "ids",
    "interval_ms": 500,
    "condition_ids": ["0xabc123..."],
    "market_slugs": ["will-bitcoin-hit-100k"],
    "event_slugs": ["bitcoin-price-markets"],
    "rejected": [],
    "error": null
  }
}
```

## Events

### `markets_stream_update`

Server-pushed event fired only for rows that changed AND matched this subscription since the last flush tick. `data` contains **full** market rows — not deltas — so clients should merge by `condition_id`. Each outcome in `data[i].outcomes` carries `latest_block` + `latest_confirmed_at` price-update watermarks.

#### Envelope

| Field | Type | Description |
| - | - | - |
| `type` | `"markets_stream_update"` | Envelope discriminator. |
| `room_id` | `"polymarket_markets_stream"` | Room identifier. |
| `mode` | `"filter" \| "ids"` | The mode this subscription was created with. |
| `interval_ms` | `500 \| 1000 \| 3000 \| 10000` | The cadence slot this event is flushed under. |
| `data` | `MarketResponse[]` | Full market rows, same shape as `GET /polymarket/market`. |

#### `MarketResponse`

| Field | Type | Description |
| - | - | - |
| `condition_id` | `string` | 0x-prefixed condition ID. **Required.** |
| `id` | `string \| null` | Market ID. |
| `market_slug` | `string \| null` | URL-safe market slug. |
| `question` | `string \| null` | Market question. |
| `title` | `string \| null` | Market title. |
| `description` | `string \| null` | Long description. |
| `image_url` | `string \| null` | CDN image URL. |
| `oracle` | `string \| null` | Oracle contract address. |
| `status` | `string` | Market status. **Required** (cache holds only open markets). |
| `created_time` | `integer \| null` | Unix seconds. |
| `start_time` | `integer \| null` | Unix seconds. |
| `game_start_time` | `integer \| null` | Unix seconds (sports markets). |
| `closed_time` | `integer \| null` | Unix seconds. |
| `end_time` | `integer \| null` | Unix seconds. |
| `accepting_orders` | `boolean \| null` | Whether CLOB is accepting new orders. |
| `uma_resolution_status` | `string \| null` | UMA oracle resolution status. |
| `is_neg_risk` | `boolean \| null` | Whether this market uses the neg-risk exchange. |
| `market_maker_address` | `string \| null` | Market maker contract. |
| `creator` | `string \| null` | Wallet address of creator. |
| `category` | `string \| null` | Category label. |
| `volume_usd` | `number \| null` | Lifetime USD volume. |
| `liquidity_usd` | `number \| null` | Current USD liquidity. |
| `highest_probability` | `number \| null` | Highest outcome probability (0 – 1). |
| `total_holders` | `integer \| null` | Unique holder count. |
| `total_daily_rate` | `number \| null` | Combined daily reward rate across sponsors. |
| `winning_outcome` | `MarketOutcome \| null` | Resolved winning outcome, when applicable. |
| `outcomes` | `MarketOutcome[]` | Market outcomes. |
| `clob_rewards` | `ClobReward[]` | Active reward configs. |
| `tags` | `string[]` | Tag labels attached to the market. |
| `event_slug` | `string \| null` | Parent event slug. |
| `resolution_source` | `string \| null` | Resolution source URL. |
| `metrics` | `Record<Timeframe, SimpleTimeframeMetrics>` | Keyed by timeframe (`1m`, `5m`, `30m`, `1h`, `6h`, `24h`, `7d`, `30d`). |
| `relevance_score` | `number \| null` | Search relevance score, when applicable. |

#### `MarketOutcome`

| Field | Type | Description |
| - | - | - |
| `name` | `string` | Outcome label (e.g. `"Yes"`). |
| `price` | `number \| null` | Latest traded price of this outcome token (0 – 1). |
| `position_id` | `string \| null` | ERC-1155 outcome token ID (decimal string). |
| `outcome_index` | `integer \| null` | 0-indexed outcome position. |
| `latest_block` | `integer \| null` | Block of the most recent price update for this outcome. |
| `latest_confirmed_at` | `integer \| null` | Unix seconds of the most recent price update. |

#### `SimpleTimeframeMetrics`

| Field | Type | Description |
| - | - | - |
| `volume` | `number` | USD volume within the window. |
| `fees` | `number` | USD fees within the window. |
| `txns` | `integer` | Trade count within the window. |
| `unique_traders` | `integer` | Unique wallet count within the window. |

#### `ClobReward`

| Field | Type | Description |
| - | - | - |
| `id` | `string` | Reward config ID. **Required.** |
| `condition_id` | `string` | Market condition ID. **Required.** |
| `asset_address` | `string \| null` | Reward token contract. |
| `rewards_amount` | `number \| null` | Total rewards remaining. |
| `rewards_daily_rate` | `number \| null` | Rewards emitted per day. |
| `start_date` | `string \| null` | ISO date the reward starts emitting. |
| `end_date` | `string \| null` | ISO date the reward stops emitting. |
| `rewards_max_spread` | `number \| null` | Max spread eligible for rewards (probability). |
| `rewards_min_size` | `number \| null` | Min order size eligible for rewards (USD). |
| `native_daily_rate` | `number \| null` | Native (Polymarket) daily rate. |
| `sponsored_daily_rate` | `number \| null` | Sponsored daily rate. |
| `total_daily_rate` | `number \| null` | Combined daily rate. |
| `sponsors_count` | `integer \| null` | Number of active sponsors. |

#### Example

```json theme={null}
{
  "type": "markets_stream_update",
  "room_id": "polymarket_markets_stream",
  "mode": "ids",
  "interval_ms": 500,
  "data": [
    {
      "condition_id": "0xabc123...",
      "id": "m_1",
      "market_slug": "will-bitcoin-hit-100k",
      "question": "Will Bitcoin hit $100k by Dec 31, 2026?",
      "title": "Bitcoin $100k by EOY 2026",
      "description": "Resolves YES if BTC closes above $100,000 on any day before Dec 31, 2026.",
      "image_url": "https://cdn.struct.to/markets/btc-100k.png",
      "oracle": "0xoracle...",
      "status": "open",
      "created_time": 1743400000,
      "start_time": 1743500000,
      "game_start_time": null,
      "closed_time": null,
      "end_time": 1798761600,
      "accepting_orders": true,
      "uma_resolution_status": null,
      "is_neg_risk": false,
      "market_maker_address": "0xmm...",
      "creator": "0xcreator...",
      "category": "crypto",
      "volume_usd": 125000.5,
      "liquidity_usd": 42000.0,
      "highest_probability": 0.65,
      "total_holders": 312,
      "total_daily_rate": 150.0,
      "winning_outcome": null,
      "outcomes": [
        {
          "name": "Yes",
          "price": 0.65,
          "position_id": "12345678901234567",
          "outcome_index": 0,
          "latest_block": 65000000,
          "latest_confirmed_at": 1743500000
        },
        {
          "name": "No",
          "price": 0.35,
          "position_id": "98765432109876543",
          "outcome_index": 1,
          "latest_block": 65000000,
          "latest_confirmed_at": 1743500000
        }
      ],
      "clob_rewards": [
        {
          "id": "1",
          "condition_id": "0xabc123...",
          "asset_address": "0x2791Bca1f2de4661ED88A30C99A7a9449Aa84174",
          "rewards_amount": 18250.0,
          "rewards_daily_rate": 50.0,
          "start_date": "2026-01-01",
          "end_date": "2026-12-31",
          "rewards_max_spread": 0.04,
          "rewards_min_size": 20.0,
          "native_daily_rate": 100.0,
          "sponsored_daily_rate": 50.0,
          "total_daily_rate": 150.0,
          "sponsors_count": 1
        }
      ],
      "tags": ["crypto", "bitcoin"],
      "event_slug": "bitcoin-price-markets",
      "resolution_source": "https://example.com/btc-resolution",
      "metrics": {
        "24h": { "volume": 3200.0, "fees": 6.4, "txns": 42, "unique_traders": 18 },
        "7d": { "volume": 22000.0, "fees": 44.0, "txns": 310, "unique_traders": 121 }
      },
      "relevance_score": null
    }
  ]
}
```


This documentation is built and hosted on [Mintlify](https://mintlify.com), a developer documentation platform.