# Kalshi + Polymarket Prediction Markets Scraper & Monitor (`brenton8907/prediction-markets-data`) Actor

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.

- **URL**: https://apify.com/brenton8907/prediction-markets-data.md
- **Developed by:** [Brenton Keller](https://apify.com/brenton8907) (community)
- **Categories:** Agents, Automation, Developer tools
- **Stats:** 2 total users, 1 monthly users, 100.0% runs succeeded, 0 bookmarks
- **User rating**: No ratings yet

## Pricing

from $1.05 / 1,000 market records

This Actor is paid per event. You are not charged for the Apify platform usage, but only a fixed price for specific events.
Since this Actor supports Apify Store discounts, the price gets lower the higher subscription plan you have.

Learn more: https://docs.apify.com/actors/running/actors-in-store.md#pay-per-event

## What's an Apify Actor?

Actors are web data automations that power AI and operations. They run on the Apify platform to scrape websites, process data, connect APIs, and automate workflows.
In Batch mode, an Actor accepts a well-defined JSON input, performs an action which can take anything from a few seconds to a few hours,
and optionally produces a well-defined JSON output, datasets with results, or files in key-value store.
In Standby mode, an Actor provides a web server which can be used as a website, API, or an MCP server.
Actors are written with capital "A".

## How to integrate an Actor?

If asked about integration, you help developers integrate Actors into their projects.
You adapt to their stack and deliver integrations that are safe, well-documented, and production-ready.
The best way to integrate Actors is as follows.

- **AI agents and MCP clients** — the [Apify MCP server](https://docs.apify.com/integrations/mcp.md) at `https://mcp.apify.com` (remote, streamable HTTP, OAuth on first use).
- **Agentic workflows and local Actor development** — [Agent Skills](https://apify.com/.well-known/agent-skills/index.json) with the [Apify CLI](https://docs.apify.com/cli/docs.md): `npm install -g apify-cli`, then `apify login`.
- **JavaScript/TypeScript projects** — the official [JS/TS client](https://docs.apify.com/api/client/js/docs.md): `npm install apify-client`.
- **Python projects** — the official [Python client](https://docs.apify.com/api/client/python/docs.md): `pip install apify-client`.
- **Any other language** — the [REST API](https://docs.apify.com/api/v2.md).

For usage examples, see the [API](#api) section below.

For more details, see Apify documentation as [Markdown index](https://docs.apify.com/llms.txt) and [Markdown full-text](https://docs.apify.com/llms-full.txt).

# README

## Prediction Markets Data: Kalshi + Polymarket

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

- Pulls open, settled, or all markets from Kalshi and/or Polymarket in a single run.
- Normalizes both venues into the same columns: `yes_price`, `implied_probability`, `yes_bid`, `yes_ask`, `spread`, `volume_24h`, `liquidity`, `open_interest`, `close_time`, `result`, and more.
- Targets markets by keyword, Kalshi category, series/event/market ticker, Polymarket tag, event slug, or market slug.
- Optional enrichment per market: top-of-book **orderbook** (configurable depth) and the most recent **trades** (configurable count).
- **Settlement history**: fetch resolved markets from the last N days with their result.
- **Weather preset**: Kalshi daily high/low temperature brackets, rain and hurricane series (plus Polymarket's weather tag), filterable by city.
- **Monitor mode**: remembers the last-seen YES price per market in a named key-value store and outputs only markets that are new or moved at least N probability points.

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

- `kalshi_yes_price`, `polymarket_yes_price`, `spread_pts` (Kalshi minus Polymarket, in probability points)
- `arb_edge_pts`: the executable edge before fees, from the books: buy YES at the cheaper venue's ask and buy NO at the other venue (1 minus its YES bid); both legs together pay $1 at settlement, so edge = bid minus ask. `arb_direction` says which way (`yes_kalshi_no_polymarket` or `yes_polymarket_no_kalshi`); null when there is no positive edge
- `kalshi_fee_est_pts` (Kalshi taker fee, 7% x P x (1-P) rounded up to the cent) and `net_edge_pts` = edge minus that fee
- `match_score` (0-1) and `match_method` (`auto` or `explicit`), both venues' links, volumes, close times, settlement rules

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:

```json
{
  "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:**

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

**Spread monitor, scheduled every 15 minutes, alert when a gap moves 3+ points or a new pair appears:**

```json
{ "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. Spread rows are billed as spread records (see Pricing).

### Who it's for

- Traders and quant researchers comparing prices across venues: spread mode gives the matched pairs, the gap and the executable edge, ready to schedule.
- Weather-market traders who need every temperature bracket for a city with orderbook depth, in one call, reliably.
- Analysts and journalists tracking probability moves on elections, Fed decisions, earnings, sports and crypto.
- AI agents and alerting workflows that want a scheduled "what changed" feed instead of re-reading the whole board.

### Input examples

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

```json
{ "maxItems": 100 }
```

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

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

**Everything mentioning the Fed or interest rates:**

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

**A specific Kalshi series and a specific Polymarket event:**

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

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

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

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

```json
{
  "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_spread_pts`, `spread_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

- Kalshi exposes tens of thousands of markets. A keyword search with no category, series or ticker filter scans the whole board (roughly 100+ requests, 20 to 30 seconds). Use `kalshiCategories`, `kalshiSeriesTickers`, or the weather preset to make runs fast.
- `settledLookbackDays` is capped at 365. Kalshi settlement history is fetched per series; Polymarket via closed markets.
- Volume units differ by venue: Kalshi `volume` and `volume_24h` are contracts, Polymarket's are USD. Sorting both venues by volume in one run compares different units; filter by `source` first if that matters.
- `kalshiCategories` values are the exact strings Kalshi uses (dropdown in the UI): Politics, Elections, Economics, Financials, Companies, Crypto, Commodities, Climate and Weather, Science and Technology, Health, World, Sports, Entertainment, Mentions, Social, Transportation, Exotics.
- Polymarket `open_interest` is not published by the API and is always null.
- Kalshi `result` is empty until settlement; `settlement_value` holds the observed value for scalar (temperature, index) markets.
- Enrichment makes one or two extra API calls per market. With 500+ enriched markets, expect a minute or two.
- The `url` for Kalshi points at the series page, which lists all its markets.
- Monitor state is per `monitorStoreName`. Do not run two monitor runs against the same store at the same time.
- Data is provided as-is from the exchanges' public APIs. Not financial advice.

### Use from an AI agent (MCP)

Every Apify actor is available as a tool through the [Apify MCP server](https://mcp.apify.com). 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.

# Actor input Schema

## `mode` (type: `string`):

'spread' pairs the same question on both exchanges (weather brackets, Fed, elections, sports...) and reports the price gap and the executable edge. Both sources are always fetched in spread mode.

## `sources` (type: `array`):

Which exchanges to pull from.

## `status` (type: `string`):

Open markets (tradeable now), settled/closed markets (with results), or both.

## `searchQueries` (type: `array`):

Case-insensitive keywords matched against market title, event title and series title. Leave empty to return the top markets by 24h volume.

## `weatherPreset` (type: `boolean`):

Kalshi 'Climate and Weather' series (daily high/low temperature brackets, rain, hurricanes) plus Polymarket weather tag. Combine with 'Cities' to narrow.

## `cities` (type: `array`):

Filter weather series by city name in the series title, e.g. NYC, Chicago, Miami, Austin, Denver, Los Angeles.

## `kalshiCategories` (type: `array`):

Restrict Kalshi to these categories.

## `kalshiSeriesTickers` (type: `array`):

e.g. KXHIGHNY (NYC daily high temp), KXFEDDECISION. Fastest way to target a market family.

## `kalshiEventTickers` (type: `array`):

e.g. KXHIGHNY-26SEP02. Returns all bracket markets in the event.

## `kalshiMarketTickers` (type: `array`):

Exact market tickers, e.g. KXHIGHNY-26SEP02-T82.

## `polymarketTags` (type: `array`):

Tag slugs or numeric tag IDs, e.g. politics, crypto, fomc, weather, nba.

## `polymarketEventSlugs` (type: `array`):

From the URL polymarket.com/event/<slug>. Returns every market in the event.

## `polymarketMarketSlugs` (type: `array`):

Exact market slugs.

## `minVolume24h` (type: `integer`):

Drop markets below this 24h volume (contracts for Kalshi, USD for Polymarket).

## `minLiquidity` (type: `integer`):

Drop markets below this resting liquidity.

## `minOpenInterest` (type: `integer`):

Drop Kalshi markets with open interest below this many contracts.

## `settledLookbackDays` (type: `integer`):

For status 'settled' or 'all': how far back to fetch resolved markets.

## `sortBy` (type: `string`):

Order of rows before the "Max markets" cap is applied.

## `maxItems` (type: `integer`):

Cap on dataset rows. Applied after filtering and sorting.

## `includeOrderbook` (type: `boolean`):

Attach top-of-book depth (bids/asks) per market. One extra API call per market. Billed as enriched record.

## `includeRecentTrades` (type: `boolean`):

Attach the most recent fills per market. One extra API call per market. Billed as enriched record.

## `tradesLimit` (type: `integer`):

How many recent fills to attach per market when "Include recent trades" is on.

## `orderbookDepth` (type: `integer`):

Price levels per side to attach when "Include orderbook" is on.

## `changesOnly` (type: `boolean`):

Remember values between runs and output only rows that are new or moved at least 'Min price move' (YES price in markets mode, spread in spread mode). Schedule this actor to get a clean feed of what changed.

## `minPriceMovePts` (type: `integer`):

In monitor mode: minimum move in probability points (0-100) of the YES price (markets mode) or of the spread (spread mode) to be reported.

## `monitorStoreName` (type: `string`):

Named key-value store that holds the last-seen prices. Use different names for different watchlists.

## `includeRaw` (type: `boolean`):

Attach the untouched upstream market object under 'raw'.

## `pairs` (type: `array`):

KALSHI\_TICKER=polymarket-market-slug (or =0x condition id). Always paired, no matching needed. Example: KXHIGHNY-26SEP08-T80=will-the-highest-temperature-in-new-york-city-be-between-80-81f-on-september-8

## `minSpreadPts` (type: `integer`):

Only report pairs whose absolute Kalshi-minus-Polymarket gap is at least this many probability points.

## `minMatchScore` (type: `integer`):

Title similarity (0-100) required to auto-pair an event across venues. Lower to see more, less certain pairs; every row carries its match\_score.

## `spreadPoolSize` (type: `integer`):

How many top-volume Polymarket markets to consider for pairing when no Polymarket filter is set.

## Actor input object example

```json
{
  "mode": "markets",
  "sources": [
    "kalshi",
    "polymarket"
  ],
  "status": "open",
  "searchQueries": [
    "Fed",
    "interest rate"
  ],
  "weatherPreset": false,
  "minVolume24h": 0,
  "minLiquidity": 0,
  "minOpenInterest": 0,
  "settledLookbackDays": 7,
  "sortBy": "volume_24h",
  "maxItems": 500,
  "includeOrderbook": false,
  "includeRecentTrades": false,
  "tradesLimit": 25,
  "orderbookDepth": 10,
  "changesOnly": false,
  "minPriceMovePts": 2,
  "monitorStoreName": "prediction-markets-monitor",
  "includeRaw": false,
  "minSpreadPts": 0,
  "minMatchScore": 60,
  "spreadPoolSize": 2000
}
```

# Actor output Schema

## `markets` (type: `string`):

One record per market, unified across venues (see the 'source' field). In monitor mode only new or changed markets are emitted.

# API

You can run this Actor programmatically using our API. Below are code examples in JavaScript, Python, and CLI, as well as the OpenAPI specification and MCP server setup.

## JavaScript example

```javascript
import { ApifyClient } from 'apify-client';

// Initialize the ApifyClient with your Apify API token
// Replace the '<YOUR_API_TOKEN>' with your token
const client = new ApifyClient({
    token: '<YOUR_API_TOKEN>',
});

// Prepare Actor input
const input = {
    "sources": [
        "kalshi",
        "polymarket"
    ],
    "searchQueries": [
        "Fed",
        "interest rate"
    ]
};

// Run the Actor and wait for it to finish
const run = await client.actor("brenton8907/prediction-markets-data").call(input);

// Fetch and print Actor results from the run's dataset (if any)
console.log('Results from dataset');
console.log(`💾 Check your data here: https://console.apify.com/storage/datasets/${run.defaultDatasetId}`);
const { items } = await client.dataset(run.defaultDatasetId).listItems();
items.forEach((item) => {
    console.dir(item);
});

// 📚 Want to learn more 📖? Go to → https://docs.apify.com/api/client/js/docs

```

## Python example

```python
from apify_client import ApifyClient

# Initialize the ApifyClient with your Apify API token
# Replace '<YOUR_API_TOKEN>' with your token.
client = ApifyClient("<YOUR_API_TOKEN>")

# Prepare the Actor input
run_input = {
    "sources": [
        "kalshi",
        "polymarket",
    ],
    "searchQueries": [
        "Fed",
        "interest rate",
    ],
}

# Run the Actor and wait for it to finish
run = client.actor("brenton8907/prediction-markets-data").call(run_input=run_input)

# Fetch and print Actor results from the run's dataset (if there are any)
print(f"💾 Check your data here: https://console.apify.com/storage/datasets/{run.default_dataset_id}")
for item in client.dataset(run.default_dataset_id).iterate_items():
    print(item)

# 📚 Want to learn more 📖? Go to → https://docs.apify.com/api/client/python/docs/quick-start

```

## CLI example

```bash
echo '{
  "sources": [
    "kalshi",
    "polymarket"
  ],
  "searchQueries": [
    "Fed",
    "interest rate"
  ]
}' |
apify call brenton8907/prediction-markets-data --silent --output-dataset

```

## MCP server setup

```json
{
    "mcpServers": {
        "apify": {
            "type": "http",
            "url": "https://mcp.apify.com/?tools=fetch-actor-details,brenton8907/prediction-markets-data"
        }
    }
}

```

The hosted server signs you in with OAuth on first connect, so no API token belongs in this config. Clients without OAuth support can send an `Authorization: Bearer <APIFY_API_TOKEN>` header instead, using a token from API & Integrations in Apify Console (https://console.apify.com/settings/integrations).

## OpenAPI specification

Download the OpenAPI definition: https://api.apify.com/v2/actors/e1Oqqfom1pukh1dh3/builds/HicJC17ssQZlgvCVY/openapi.json
