Kalshi + Polymarket Prediction Markets Scraper & Monitor

Returns live prices, orderbooks, trades, settlement history and cross-venue spreads (Kalshi vs Polymarket pairs with executable edge) for Kalshi and Polymarket markets as one unified dataset: weather, politics, sports, crypto. Official public APIs, no login.

Run on Apify Pricing API docs

Categories: AGENTS, AUTOMATION, DEVELOPER_TOOLS. Pay per result, no subscription; Apify's free plan includes $5 of monthly usage.

Run it from the command line

curl -sS -X POST "https://api.apify.com/v2/acts/brenton8907~prediction-markets-data/run-sync-get-dataset-items?token=$APIFY_TOKEN" \
  -H 'Content-Type: application/json' \
  -d '{"mode": "markets", "sources": ["kalshi", "polymarket"], "status": "open", "searchQueries": ["Fed", "interest rate"], "weatherPreset": false, "minVolume24h": 0, "maxItems": 25}'

Run it from Python

from apify_client import ApifyClient
client = ApifyClient("YOUR_APIFY_TOKEN")
run = client.actor("brenton8907/prediction-markets-data").call(run_input={
  "mode": "markets",
  "sources": [
    "kalshi",
    "polymarket"
  ],
  "status": "open",
  "searchQueries": [
    "Fed",
    "interest rate"
  ],
  "weatherPreset": false,
  "minVolume24h": 0,
  "maxItems": 25
})
for item in client.dataset(run.default_dataset_id).iterate_items():
    print(item)

Use it as an MCP tool (Claude, Cursor, any MCP client)

The actor is listed in the official MCP registry as io.github.brenton-keller/prediction-markets-data and served by Apify's hosted MCP server:

{
  "mcpServers": {
    "prediction-markets-data": {
      "url": "https://mcp.apify.com/?tools=brenton8907/prediction-markets-data",
      "headers": {
        "Authorization": "Bearer YOUR_APIFY_TOKEN"
      }
    }
  }
}

Pricing (pay per event)

eventwhatpricedetails
market-recordMarket record$0.0015One normalized market row (price, volume, liquidity, close time, result).
enriched-market-recordEnriched market record$0.0040One market row with orderbook depth and/or recent trades attached.
spread-recordCross-venue spread record$0.0050One matched Kalshi/Polymarket pair with both prices, the spread and the executable edge.

Documentation

Live prices, orderbook depth, recent trades and settlement history from Kalshi and Polymarket in one unified schema. Built on both exchanges' official public APIs (no login, no browser, no proxies), so runs are fast and do not break when a website changes. Includes a changes-only monitor mode so a scheduled run gives you a clean feed of what moved.

The reliability wedge: official APIs, no scraping, no proxies, no login. Every scenario in testing (top markets, keyword search, weather brackets with orderbooks, settled history, monitor mode) completed with a 100% success rate.

What it does

Cross-venue spread mode (Kalshi vs Polymarket)

Set "mode": "spread" and the actor pairs the same question on both exchanges and prices the gap. One row per pair:

Pairing is deliberately conservative. Events are paired when their titles agree after normalisation (NYC = New York City, Sep = September, years dropped) and their dates do not conflict; markets inside a paired event are matched by bracket ("80-81", "79 or below", "80 or higher"), by 1:1 events, or by outcome label. Anything uncertain is left out. For questions the matcher cannot pair, pass them yourself:

{
  "mode": "spread",
  "pairs": ["KXHIGHNY-26SEP08-T80=will-the-highest-temperature-in-new-york-city-be-between-80-81f-on-september-8"]
}

All weather brackets for three cities, biggest gaps first:

{ "mode": "spread", "weatherPreset": true, "cities": ["NYC", "Chicago", "Miami"] }

Spread monitor, scheduled every 15 minutes, alert when a positive executable net edge moves 3+ points or a new positive edge appears:

{ "mode": "spread", "weatherPreset": true, "changesOnly": true, "minPriceMovePts": 3, "monitorStoreName": "weather-spreads" }

Add an Apify integration (Slack, email, webhook) on the actor's Integrations tab and you have an alert feed without any extra code.

Read the rules before trading a gap: the two venues do not always settle on the same source. Miami daily highs, for example, can close at 99% on different brackets because Kalshi and Polymarket read different weather stations. kalshi_settlement_station, kalshi_rules and polymarket_rules are on every row for that check. Rows are sorted by absolute spread, then net edge; minSpreadPts drops small gaps; minMatchScore (default 60) loosens or tightens auto pairing. Changes-only spread monitoring rejects non-positive edges, below-threshold matches and contracts whose stated close time has passed, and tracks movement in executable net edge rather than midpoint spread. Spread rows are billed as spread records (see Pricing).

Who it's for

Input examples

Top 100 markets by 24h volume, both venues (default):

{ "maxItems": 100 }

NYC and Chicago weather brackets with orderbook and last 5 trades:

{
  "weatherPreset": true,
  "cities": ["NYC", "Chicago"],
  "includeOrderbook": true,
  "orderbookDepth": 10,
  "includeRecentTrades": true,
  "tradesLimit": 5
}

Everything mentioning the Fed or interest rates:

{ "searchQueries": ["Fed", "interest rate"], "maxItems": 50 }

A specific Kalshi series and a specific Polymarket event:

{
  "kalshiSeriesTickers": ["KXHIGHNY"],
  "polymarketEventSlugs": ["presidential-election-winner-2028"]
}

Settled weather markets from the last 30 days (with results):

{ "status": "settled", "weatherPreset": true, "settledLookbackDays": 30 }

Monitor mode: schedule every 15 minutes, only report moves of 3+ points:

{
  "polymarketTags": ["politics"],
  "kalshiCategories": ["Politics", "Elections"],
  "changesOnly": true,
  "minPriceMovePts": 3,
  "monitorStoreName": "politics-watch"
}

The first monitor run outputs every market (all are "new") and seeds the store. Later runs output only changes. Use a different monitorStoreName per watchlist.

Output

One row per market. Prices are fractions of a dollar (0 to 1); implied_probability is the same number and can be read as P(yes).

Field Description
source kalshi or polymarket
id Kalshi market ticker or Polymarket condition ID
url Link to the market's series page (Kalshi) or the market page (Polymarket)
event_url Kalshi only: link to the event page holding all brackets of this market. Built from the tickers; kalshi.com blocks automated link checks, so report a dead link in Issues
title, outcome_label Market question and the specific outcome (e.g. temperature bracket)
event_id, event_title, series_id, series_title Grouping above the market
category, tags Exchange category and tags
status open, closed (no longer trading, not yet resolved) or settled
market_type Kalshi market type, or binary / multi for Polymarket
yes_bid, yes_ask, spread, last_price Top of book and last trade
yes_price, no_price, implied_probability Mid price (or last price when no book)
volume, volume_24h, open_interest, liquidity Activity. Kalshi volume is contracts; Polymarket volume is USD
open_time, close_time, expiration_time, settled_time ISO 8601 timestamps
result, settlement_value yes / no (or scalar) once settled
strike_type, floor_strike, cap_strike, settlement_station Kalshi bracket definition and the weather station used for settlement
rules Settlement rules text (Kalshi) or market description (Polymarket)
price_change_24h_pts Polymarket 24h move in probability points
outcomes, outcome_prices, yes_token_id Polymarket outcome list, prices and CLOB token for the YES side
orderbook With includeOrderbook: { "bids": [{"price", "size"}, ...], "asks": [{"price", "size"}, ...] } on the YES side, best price first
recent_trades With includeRecentTrades: list of { "time", "yes_price", "size", "taker_side" }, newest first
is_new, previous_yes_price, previous_seen_at, price_move_pts Monitor mode only (spread mode: previous_net_edge_pts, net_edge_move_pts)
spread_pts, arb_edge_pts, arb_direction, kalshi_fee_est_pts, net_edge_pts, match_score, match_method, kalshi_*, polymarket_* Spread mode only; see the spread section
enrichment_error Set if an orderbook/trades call failed; the base row is still returned
raw With includeRaw: the untouched upstream object
fetched_at When the row was fetched (UTC)

Export as JSON, CSV, Excel or via the API like any Apify dataset.

Pricing

Pay per result. You are charged only for rows written to the dataset.

Event Price When
Market record $1.50 per 1,000 rows Standard run
Enriched market record $4.00 per 1,000 rows Run with includeOrderbook and/or includeRecentTrades
Cross-venue spread record $5.00 per 1,000 rows mode: spread; one row per matched Kalshi/Polymarket pair

Examples: a top-500 board costs $0.75. A monitor run that finds 12 moved markets costs $0.018. All NYC + Chicago weather brackets with orderbooks costs about $0.16.

Rows are billed by what they contain: a row whose orderbook/trades call failed (it carries enrichment_error) is billed at the base rate, never the enriched rate.

The first run in monitor mode charges every row, because every market is new to the store. Later runs charge only the rows that changed.

Set Maximum total charge on the run to cap spend; the actor stops cleanly at the cap.

Limits and notes

Use from an AI agent (MCP)

Every Apify actor is available as a tool through the Apify MCP server. Add it to Claude, Cursor, or any MCP client and call this actor by name with the JSON input above. Suggested agent pattern: run once with changesOnly: true on a schedule, then act only on the rows returned.

Support

Open an issue on the actor page with the run ID and input. Both exchanges' APIs are versioned; if a field disappears upstream, the actor keeps the row and nulls the field rather than failing the run.