Skip to main content
Room ID: polymarket_markets_stream
Endpoint: wss://api.struct.to/ws
Rate: 0.025 credits per message
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.
Related guides: Building a live trending feed seeds from the markets list and stays warm via this stream, and Ranking markets by CLOB rewards tracks live reward changes through it.

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

FieldTypeRequiredDescription
action"subscribe" | "unsubscribe" | "unsubscribe_all"YesSlot lifecycle action.
interval_ms500 | 1000 | 3000 | 10000For subscribe / unsubscribeFlush cadence. Defaults to 1000 if omitted.
mode"filter" | "ids"NoSubscription mode. Defaults to filter.
filterMarketsStreamFiltermode=filter onlyFilter body; all fields optional.
condition_idsstring[]mode=ids only0x-prefixed lowercase 32-byte hex.
market_slugsstring[]mode=ids onlyMarket slug strings.
event_slugsstring[]mode=ids onlyEvent 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

{
  "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": {
        "search": "bitcoin",
        "categories": ["crypto"],
        "min_volume": 50000,
        "timeframe": "24h",
        "has_rewards": true
      }
    }
  }
}

Example — ids mode

{
  "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

{
  "type": "room_message",
  "payload": {
    "room_id": "polymarket_markets_stream",
    "message": {
      "action": "unsubscribe",
      "interval_ms": 500,
      "mode": "ids"
    }
  }
}

Response

{
  "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

FieldTypeDescription
type"markets_stream_update"Envelope discriminator.
room_id"polymarket_markets_stream"Room identifier.
mode"filter" | "ids"The mode this subscription was created with.
interval_ms500 | 1000 | 3000 | 10000The cadence slot this event is flushed under.
dataMarketResponse[]Full market rows, same shape as GET /polymarket/market.

MarketResponse

FieldTypeDescription
condition_idstring0x-prefixed condition ID. Required.
idstring | nullMarket ID.
market_slugstring | nullURL-safe market slug.
questionstring | nullMarket question.
titlestring | nullMarket title.
descriptionstring | nullLong description.
image_urlstring | nullCDN image URL.
oraclestring | nullOracle contract address.
statusstringMarket status. Required (cache holds only open markets).
created_timeinteger | nullUnix seconds.
start_timeinteger | nullUnix seconds.
game_start_timeinteger | nullUnix seconds (sports markets).
closed_timeinteger | nullUnix seconds.
end_timeinteger | nullUnix seconds.
accepting_ordersboolean | nullWhether CLOB is accepting new orders.
uma_resolution_statusstring | nullUMA oracle resolution status.
is_neg_riskboolean | nullWhether this market uses the neg-risk exchange.
market_maker_addressstring | nullMarket maker contract.
creatorstring | nullWallet address of creator.
categorystring | nullCategory label.
volume_usdnumber | nullLifetime USD volume.
liquidity_usdnumber | nullCurrent USD liquidity.
highest_probabilitynumber | nullHighest outcome probability (0 – 1).
total_holdersinteger | nullUnique holder count.
total_daily_ratenumber | nullCombined daily reward rate across sponsors.
winning_outcomeMarketOutcome | nullResolved winning outcome, when applicable.
outcomesMarketOutcome[]Market outcomes.
clob_rewardsClobReward[]Active reward configs.
tagsstring[]Tag labels attached to the market.
event_slugstring | nullParent event slug.
resolution_sourcestring | nullResolution source URL.
metricsRecord<Timeframe, SimpleTimeframeMetrics>Keyed by timeframe (1m, 5m, 30m, 1h, 6h, 24h, 7d, 30d).
relevance_scorenumber | nullSearch relevance score, when applicable.

MarketOutcome

FieldTypeDescription
namestringOutcome label (e.g. "Yes").
pricenumber | nullLatest price (0 – 1).
position_idstring | nullERC-1155 outcome token ID (decimal string).
outcome_indexinteger | null0-indexed outcome position.
latest_blockinteger | nullBlock of the most recent price update for this outcome.
latest_confirmed_atinteger | nullUnix seconds of the most recent price update.

SimpleTimeframeMetrics

FieldTypeDescription
volumenumberUSD volume within the window.
feesnumberUSD fees within the window.
txnsintegerTrade count within the window.
unique_tradersintegerUnique wallet count within the window.

ClobReward

FieldTypeDescription
idstringReward config ID. Required.
condition_idstringMarket condition ID. Required.
asset_addressstring | nullReward token contract.
rewards_amountnumber | nullTotal rewards remaining.
rewards_daily_ratenumber | nullRewards emitted per day.
start_datestring | nullISO date the reward starts emitting.
end_datestring | nullISO date the reward stops emitting.
rewards_max_spreadnumber | nullMax spread eligible for rewards (probability).
rewards_min_sizenumber | nullMin order size eligible for rewards (USD).
native_daily_ratenumber | nullNative (Polymarket) daily rate.
sponsored_daily_ratenumber | nullSponsored daily rate.
total_daily_ratenumber | nullCombined daily rate.
sponsors_countinteger | nullNumber of active sponsors.

Example

{
  "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
    }
  ]
}
Last modified on June 8, 2026