Kalshi & Polymarket Prediction Market Odds Scraper avatar

Kalshi & Polymarket Prediction Market Odds Scraper

Pricing

$0.0015 / market

Go to Apify Store
Kalshi & Polymarket Prediction Market Odds Scraper

Kalshi & Polymarket Prediction Market Odds Scraper

Live odds, volume, liquidity and settlement from Kalshi and Polymarket in one schema — with weather markets resolved to city, station and temperature bracket.

Pricing

$0.0015 / market

Rating

0.0

(0)

Developer

ScrapeBench

ScrapeBench

Maintained by Community

Actor stats

0

Bookmarked

2

Total users

1

Monthly active users

5 hours ago

Last modified

Categories

Share

Pain points

  • Kalshi renamed its price and size fields upstream — last_price became last_price_dollars, volume became volume_fp, and the values changed from integer cents to decimal strings. The old names are simply absent now, so a scraper written against them returns a full, correctly-shaped dataset in which every price is null.
  • Kalshi's obvious markets endpoint is not usable as a market list. On 2026-09-07 GET /trade-api/v2/markets?status=open returned 25,000 rows of which 24,992 were auto-generated multivariate parlay legs ('yes Kaede Katayama,yes Eric Jr Olivarez,...'), leaving 8 real markets, all with zero volume — and paging does not escape them.
  • Kalshi accepts a category parameter on its events endpoint and then ignores it: requests for Crypto and for Climate and Weather return byte-identical pages, so a filter that looks like it worked did not.
  • Polymarket sends outcomes and outcomePrices as JSON-encoded strings rather than arrays, caps limit at 100 however much you ask for, and refuses paging past offset 2000.
  • Weather markets are the most-traded retail contracts on Kalshi and the hardest to use raw: the question says only 'Will the maximum temperature be 81-82°?', with the city and the settlement station stated nowhere but the prose of the resolution rules.
  • Prices in this category run from $0.99 to $100 per 1,000 rows for broadly the same public, keyless data (Apify Store, 2026-09-07).

What we solve

  • Kalshi is read through its events tree, which returns the same exchange with zero parlay legs — 11,206 events and 100,643 markets on 2026-09-07.
  • The current field names are read, and the numeric strings coerced to numbers.
  • If no market in a run carries a recognised price field, the run says so with a free source_changed row instead of delivering a dataset of nulls — the one failure this category actually has.
  • Categories are filtered here rather than at the API, because the API's own filter does nothing.
  • Temperature and rain markets are parsed into city, settlement station and bracket bounds.
  • Naming a Kalshi series turns a full-exchange walk into a single request: about 0.5 seconds instead of 17.

Summary

Reads both major prediction exchanges and returns them in a single row shape, so the same question listed on each can be compared directly: question, venue, yes/no price, implied probability, bid/ask, volume, 24h volume, open interest, liquidity, open and close time, and the settled result. Temperature and rain markets additionally carry the city, the observation station that settles the contract, and the bracket bounds — none of which appear in the market's own question text. Both venues are public, keyless APIs, so there is no login, no API key and no browser rendering; a full sweep of ~100,000 open Kalshi markets plus Polymarket takes about 20 seconds. Built for traders comparing venues, quant researchers building history, and anyone whose current feed quietly stopped carrying prices.

Who it's for

  • Traders comparing the same question's price across Kalshi and Polymarket
  • Quant researchers and backtesters building prediction-market history
  • Weather desks and energy traders reconciling Kalshi brackets against a station forecast
  • Journalists and analysts citing market-implied probabilities

How to use

Set the input, run the actor, and collect results from the run's dataset (export to JSON/CSV/Excel, or pull via the Apify API). Example input:

{
"venue": "both",
"queries": [
"fed",
"bitcoin"
],
"minVolume": 1000,
"maxResults": 500
}

See Inputs below for every available field.

What you get

One row per record:

FieldDescription
venuekalshi or polymarket
market_idKalshi's ticker or Polymarket's numeric id — stable, so it de-duplicates across runs
event_id / seriesThe parent event, and the recurring series ticker where the venue has one
question / subtitleThe question as the venue words it, and which outcome this row is
categoryKalshi's own category. Null on Polymarket rows, which publish none — not inferred
statusactive, closed or settled
outcomes / outcome_pricesOutcome names and their prices, aligned
yes_price / no_priceCost of one contract, 0-1. A contract settles at $1, so price is also probability
yes_bid / yes_ask / no_bid / no_askTop of book on each side
last_pricePrice of the most recent trade
implied_probabilityLast trade where there is one, else the mid of the book. Null — never 0 — when the market has neither
volume / volume_24hTotal and 24-hour traded volume
open_interestContracts outstanding. Kalshi only — Polymarket publishes it per event, not per market
liquidityResting size on the book
open_time / close_timeWhen trading opened, and when it stops
resultThe settled outcome, on settled markets only
weather_city / weather_stationFor temperature and rain markets: the city and the observation station that settles the contract, read from the resolution rules
bracket_low / bracket_high / bracket_typeThe outcome bracket and whether it is greater, less or between
market_urlThe venue's own page. Polymarket only — see the FAQ
source_urlThe venue endpoint this exact row came from

Sample:

{
"source_url": "https://api.elections.kalshi.com/trade-api/v2/markets/KXHIGHMIA-26SEP07-B86.5",
"venue": "kalshi",
"market_id": "KXHIGHMIA-26SEP07-B86.5",
"event_id": "KXHIGHMIA-26SEP07",
"series": "KXHIGHMIA",
"question": "Will the maximum temperature be 86-87\u00b0 on Sep 7, 2026?",
"subtitle": "86\u00b0 to 87\u00b0",
"category": "Climate and Weather",
"status": "active",
"outcomes": [
"Yes",
"No"
],
"outcome_prices": [
0.03,
0.98
],
"yes_price": 0.03,
"no_price": 0.98,
"yes_bid": 0.02,
"yes_ask": 0.03,
"no_bid": 0.97,
"no_ask": 0.98,
"last_price": 0.02,
"implied_probability": 0.02,
"volume": 6136.62,
"volume_24h": 6136.62,
"open_interest": 4570.47,
"liquidity": 0.0,
"open_time": "2026-09-06T14:00:00Z",
"close_time": "2026-09-08T05:00:00Z",
"result": null,
"weather_city": "Miami",
"weather_station": "CLIMIA",
"bracket_low": 86.0,
"bracket_high": 87.0,
"bracket_type": "between",
"market_url": null
}

Inputs

FieldRequiredTypeDefaultDescription
venuenostring"both"Which exchange to read. 'Both' returns one combined, directly comparable dataset — the same question is often listed on each at a different price.
querynostring"fed"Keep only markets whose question, subtitle, series, ticker or weather city contains this text. Matching the city matters: a New York temperature market's question says only 'Will the maximum temperature be 81-82°?', so 'new york' is the only way to find it. Kept forever alongside 'Keywords' — saved tasks and API callers send it.
queriesnoarray["fed", "bitcoin", "hurricane"]Several keywords in ONE run. A market is kept if it matches any of them. This does not re-fetch per keyword — the exchange is read once and filtered in memory, so ten keywords cost exactly what one does.
categoriesnoarray[]Kalshi's own categories, e.g. Politics, Economics, Climate and Weather, Crypto, Sports, Financials. Filtered here rather than at the API, because Kalshi accepts a category parameter and then ignores it — /events?category=Crypto and ?category=Climate%20and%20Weather return byte-identical pages. Polymarket publishes no category per market, so this filter excludes its rows when set. Left empty by default: every filter here narrows, and pre-filling two of them together is how an Actor returns nothing on its first run.
seriesnoarray[]Read only these Kalshi series, e.g. KXHIGHNY (New York daily high), KXHIGHCHI, KXHIGHMIA, KXFEDDECISION. This one IS honoured by the API, so naming a series turns a full-exchange walk into a single request — about 0.5s instead of 17s. Leave empty to read everything; a series narrows to that series alone, so combining it with keywords usually returns nothing.
minVolumenointeger0Drop markets that have traded less than this. Most of any prediction exchange is long-tail listings nobody has touched; set 1000 or more to get only markets with a real book behind the price.
includeClosednobooleanfalseOff by default, so you get the live board. Turn it on to pull settled markets with their result, which is what you want for backtesting rather than trading.
maxResultsnointeger1000A cap on the whole RUN, not per keyword — this Actor sweeps entire exchanges rather than looking up one thing at a time, so the cap is your spend control. Markets are ranked by volume before it applies, so a cap keeps the ones people are actually trading. The run log always states how many matched, so a cap can never read as completeness.
proxyConfigurationnoobject{"useApifyProxy": false}Optional and off by default. Both venues are public, documented, keyless APIs that answer a plain client in under a second, and a proxy only adds latency (#145: never reach for one before the plain path is proven to fail).

Pricing (Pay Per Event)

You pay per result (market) — no charge for empty runs. Example: 1000 markets at $0.0015/result$1.50.

The default input returns 1,000 markets, so a default run costs $1.50. 'Maximum markets per run' is your spend control and applies to the whole run; markets are ranked by volume before it applies. Apify platform usage (compute) is billed separately per your plan.

Use cases

  • Cross-venue pricing — pull both exchanges in one run and compare the implied probability of the same question on each.
  • Weather bracket monitoring — read KXHIGHNY, KXHIGHCHI and KXHIGHMIA on a schedule and line each bracket up against your own forecast for that station.
  • Liquidity screening — set a minimum volume to strip out the long tail and keep only markets with a real book behind the price.
  • Settlement history — turn on closed markets to pull settled questions with their result for backtesting.

Why this actor

  • Reads Kalshi through the events tree, so the dataset is real markets rather than auto-generated parlay legs — 24,992 of 25,000 rows on the obvious endpoint were parlays on 2026-09-07.
  • Reads Kalshi's current field names, and fails loudly with a free marker row if they move again rather than quietly returning null prices.
  • One row shape across both venues, so Kalshi and Polymarket sort, filter and diff together.
  • Weather markets carry the city, the settlement station and the bracket bounds, none of which are in the question text.
  • Implied probability is null, never 0, for a market that has never traded and has no book — 0 would assert the market calls the outcome impossible.
  • $1.50 per 1,000 markets, against $0.99 to $100 across this category on the Store (2026-09-07).

Limitations & updates

Covers Kalshi and Polymarket. Kalshi coverage is its open events tree — about 100,643 markets on 2026-09-07 — and settled markets only when 'Include closed and settled markets' is on. Polymarket is limited by the platform's own paging ceiling to roughly 2,100 active markets per sweep, reported on the run rather than passed off as the whole exchange. Category comes from Kalshi only, because Polymarket publishes none per market. Open interest comes from Kalshi only, because Polymarket publishes it per event. Kalshi rows carry no page link, because its API does not state the slug that URL needs. Weather city and station are parsed from the resolution rules and are present where the venue words them in the form 'at

FAQ

Why does this read Kalshi's events endpoint instead of its markets endpoint?

Because the markets endpoint cannot be used as a market list. On 2026-09-07, GET /trade-api/v2/markets?limit=200&status=open returned 25,000 rows of which 24,992 were auto-generated multivariate parlay legs — combination contracts with titles like 'yes Kaede Katayama,yes Eric Jr Olivarez,yes Aniketh Venkataraman' — leaving 8 real markets, every one of them with zero volume. Paging does not get past them; they fill the whole cursor. GET /events?with_nested_markets=true returns the same exchange with none of them: 11,206 events and 100,643 markets on the same day. You can check both in a browser; no key is needed. If a prediction-market dataset you already have is full of comma-separated player names, this is why.

What are the '_dollars' and '_fp' field names about?

Kalshi renamed its price and size fields and changed their type. last_price is now last_price_dollars, volume is volume_fp, open_interest is open_interest_fp, and the values are decimal strings ('0.9510') rather than integer cents. The previous names are gone, not deprecated — a request returns no such key at all. Anything written against the old names therefore reads null for every price while still producing rows of the right shape, which is why a broken feed in this category tends to look healthy rather than throw an error.

What happens if Kalshi renames the fields again?

The run returns a single free row with notice_type: source_changed saying so, and no market rows. That is deliberate: this category's characteristic failure is a silent rename, and a dataset of correctly-shaped rows with null prices is worse than no dataset, because nothing downstream can tell the difference. The check is across the whole fetch, not per row — an individual market legitimately has no bid.

Why is the category filter applied after fetching rather than by the API?

Because Kalshi's own category parameter does not work. /events?status=open&category=Crypto and /events?status=open&category=Climate%20and%20Weather return byte-identical pages — same events, same order. Passing it through would produce a filter that appears to work and silently does not. Series tickers are different: series_ticker= is honoured, and naming one turns a 17-second full walk into a single 0.5-second request.

What do the weather fields give me that the question does not?

The city and the station. A Kalshi temperature market's question is 'Will the maximum temperature be 86-87° on Sep 7, 2026?' — it never says Miami, and it never says CLIMIA, which is the observation station whose reading actually settles the contract. Both are stated only in the prose of the resolution rules, and both are what you need to line a bracket up against a forecast. The bracket bounds come through as numbers (bracket_low, bracket_high, bracket_type) rather than as text you would have to parse out of a title.

Why is implied_probability sometimes null rather than 0?

Because the market has not said anything. A contract that settles at $1 has a price that is also its probability, so where there is a last trade, or a book to take a mid from, that number is the market's own estimate. Where a market has never traded and has no resting orders, there is no estimate — and 0 would assert that the market considers the outcome impossible, which is a much stronger claim than silence. Filtering for long shots gives the same answer either way; the difference shows when you average, where a wall of false zeros would drag the mean down.

Why do Kalshi rows have no market_url?

Because Kalshi's public market page is at kalshi.com/markets/

How many markets can one run return?

As many as you ask for. On 2026-09-07 there were 100,643 open Kalshi markets and about 2,100 reachable active Polymarket markets, and a full sweep of both took roughly 20 seconds. 'Maximum markets per run' caps the whole run rather than each keyword, because this Actor sweeps exchanges rather than looking things up one at a time — so the cap is your spend control. Markets are sorted by volume before the cap applies, and the run log always states how many matched, so a cap can never read as completeness.

Why can't I get all of Polymarket?

Polymarket refuses paging past offset 2000 with 'offset too large', and caps a page at 100 rows however much you request, so about 2,100 active markets are reachable per sweep. When that ceiling is hit the run adds a free truncated row saying so rather than presenting a partial sweep as the whole exchange. Narrowing with a keyword reaches markets beyond it.

Do I need a proxy or an account on either venue?

No. Both APIs are public, documented and keyless, and they answer a plain client in well under a second, so the proxy input is off by default and turning it on only adds latency. No Kalshi or Polymarket account is involved and nothing here is authenticated.

Is any of this data inferred or modelled?

No. Every field is read from the venue's own response. Implied probability is arithmetic on the venue's own last trade or book, the weather city and station are quoted out of the venue's own resolution rules, and the bracket bounds are the venue's own numeric strike fields. Nothing is estimated by a model, and no field is filled in where the venue said nothing.

Which actor to choose

Other Actors in the portfolio that pair with this one:

  • Zillow & Redfin Property Scraper — You are pricing a different kind of market and want listing-level property data.
  • ATS & Career Page Jobs Scraper — You track hiring as an economic signal alongside market-implied probabilities.

Guides & use cases

Written up on scrapebench.dev — the bench that runs and verifies this actor against the live source every night:

More actors, coverage and nightly verification results: scrapebench.dev

Works with AI assistants (MCP)

Callable as an MCP tool, so Claude, Cursor, VS Code Copilot and other MCP clients can run it directly. Grab the config from the MCP tab on this page — Apify hosts the server and keeps that snippet current, and OAuth signs you in on first connect, so no API token goes in your config file.

Then just ask:

"Get the Kalshi and Polymarket markets about the Fed with at least 1,000 volume, and show the implied probability on each venue."

Keywords match the question, the series and the weather city, so 'new york' finds the daily temperature brackets whose question says only 'the maximum temperature'. Name a series (KXHIGHNY) to skip the full-exchange walk. Runs started this way bill exactly like any other run.