# Kalshi & Polymarket Election Odds History Scraper (`scrapebench/kalshi-election-odds-history-scraper`) Actor

The whole odds history of settled Kalshi and Polymarket election markets: daily or hourly candles with bid/ask, volume and open interest, every candidate and the result. 2024, 2026 and live 2028 races.

- **URL**: https://apify.com/scrapebench/kalshi-election-odds-history-scraper.md
- **Developed by:** [ScrapeBench](https://apify.com/scrapebench) (community)
- **Categories:** News
- **Security:** Limited permissions
- **Stats:** 3 total users, 2 monthly users, 100.0% runs succeeded, 0 bookmarks
- **User rating**: No ratings yet

## Pricing

from $0.0003 / candle

This Actor is paid per event. You are not charged for the Apify platform usage, but only a fixed price for specific events.

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

## What's an Apify Actor?

An Actor is a serverless cloud program that runs on the Apify platform. It has two run modes.
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.

Apify vocabulary and the platform model are defined once, in the agent quickstart at https://apify.com/agents.md.

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

Do not guess an integration path. Every one of them is in the agent quickstart at https://apify.com/agents.md: the Apify MCP server, Agent Skills with the Apify CLI, the JavaScript and Python clients, the REST API, and the account-free path for an agent with no human to sign in. It also carries the rule on stating cost before the first paid run.

For examples already wired to this Actor's own input schema, see the [API](#api) section below.

Each client library has reference documentation the quickstart does not restate: [JavaScript/TypeScript](https://docs.apify.com/api/client/js/docs.md) (`npm install apify-client`) and [Python](https://docs.apify.com/api/client/python/docs.md) (`pip install apify-client`).

# README

## Kalshi & Polymarket Election Odds History Scraper

### Pain points

- A race's market runs for months: Oregon's 2026 governor market listed on 2025-01-26 and settled on 2026-11-04 (Kalshi, measured 2026-11-06). A history that reaches back a fixed number of days from today misses the primaries and the summer.
- Kalshi's 2024 election markets are no longer in its live market list: their events list no markets, and the live candle endpoint answers 404. They sit under a separate archive path.
- Kalshi's candle fields are named differently on the two paths: `close_dollars` and `volume_fp` on live markets, plain `close` and `volume` in the archive. Both are dollars, so reading the plain ones as cents makes a 15-cent price 0.15 cents.
- Polymarket serves a market's whole daily history in one call, but its hourly history comes only in windows of at most 15 days; a longer window answers 400 'interval is too long'.
- Polymarket's elections tag holds more events than its 2,100-row paging limit reaches; 'Arizona Senate Election Winner' (2024) was not in the reachable part on 2026-11-06.

### What we solve

- History runs from the day the market opened to the day it settled, unless you set your own dates.
- Settled races come with the result: the winner on the race row, yes or no and the settlement value on each candidate.
- 2024 races are read from Kalshi's archive, and a market that moves there between runs is retried on the archive path.
- Both of Kalshi's field namings are read as dollars.
- Polymarket hourly history is fetched in 15-day windows and joined; daily comes in one call.
- Polymarket races are found through its own search, then kept only when the title holds every word of your keyword.

### Summary

Pulls an election market's odds from the day it listed to the day it settled, with the result attached. Each race comes back as one race row (title, cycle, winner, volume), one row per candidate or party (result, final price, volume, open interest) and one candle per candidate per day or hour. Kalshi candles carry the trade price's open, high, low, close and mean, the yes bid and ask open/high/low/close, volume and open interest; Polymarket candles carry its price, the only history it publishes. Kalshi's 2024 races, which have left its live market list, are read from its archive. The Kalshi trade tape (time, price, size, taker side) is optional. Both venues are public, keyless APIs: no login, no API key, no browser.

### Who it's for

- Researchers measuring how well election markets forecast results
- Quant traders backtesting on prediction-market history
- Journalists charting how a race's odds moved
- Campaigns, PACs and analysts tracking 2028 races as they trade

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

```json
{
  "queries": [
    "arizona senate"
  ],
  "venues": "both",
  "status": "settled",
  "granularity": "daily"
}
```

See **Inputs** below for every available field.

### What you get

One row per record:

| Field | Description |
|---|---|
| `row_type` | race, candidate, candle or trade |
| `venue` | kalshi or polymarket |
| `race_id` | Kalshi event ticker (SENATEAZ-24) or Polymarket event id; on every row, so rows join to their race |
| `market_id` | One candidate's market; on candidate, candle and trade rows |
| `title / series / cycle / tags` | Race rows: the race as the venue titles it, its series, election year and tags |
| `winner` | Race rows: the candidate(s) whose market settled yes |
| `candidate / result / settlement_value / last_price` | Candidate rows: who, yes or no, what the contract paid, the last traded price |
| `volume / open_interest` | Contracts traded and outstanding (open interest from Kalshi only) |
| `open_time / close_time / settled_time` | When trading opened, closed and the market settled |
| `period / period_end` | Candle rows: daily or hourly, and the candle's end (UTC) |
| `open / high / low / close / mean` | Candle rows: trade price over the period, 0-1 (an implied probability). Polymarket gives close only |
| `yes_bid_* / yes_ask_*` | Candle rows, Kalshi: open/high/low/close of the best yes bid and ask |
| `trade_id / time / yes_price / no_price / size / taker_side` | Trade rows (Kalshi, optional) |
| `source_url` | The venue endpoint the row came from |

Sample:

```json
{
  "source_url": "https://api.elections.kalshi.com/trade-api/v2/historical/markets/SENATEAZ-24-R/candlesticks",
  "row_type": "candle",
  "venue": "kalshi",
  "race_id": "SENATEAZ-24",
  "market_id": "SENATEAZ-24-R",
  "candidate": "Kari Lake",
  "period": "daily",
  "period_end": "2024-10-08T04:00:00Z",
  "open": 0.14,
  "high": 0.16,
  "low": 0.14,
  "close": 0.15,
  "mean": 0.1503,
  "yes_bid_open": 0.1,
  "yes_bid_high": 0.15,
  "yes_bid_low": 0.1,
  "yes_bid_close": 0.13,
  "yes_ask_open": 1.0,
  "yes_ask_high": 1.0,
  "yes_ask_low": 0.14,
  "yes_ask_close": 0.15,
  "volume": 1447.0,
  "open_interest": 967.0
}
```

### Inputs

| Field | Required | Type | Default | Description |
|---|---|---|---|---|
| `queries` | no | array | `["arizona senate"]` | Races to pull, as words in the race's title: 'arizona senate', 'oregon governor', 'presidential election winner'. Every word must appear. Kalshi is matched on its election series titles, Polymarket through its own search. Polymarket is only read when you give a keyword. |
| `query` | no | string | — | One keyword. Kept alongside 'Keywords' for saved tasks and API callers. |
| `seriesTickers` | no | array | `[]` | Exact Kalshi series, e.g. SENATEAZ, GOVPARTYOR, HOUSEPA7, SENATENM. One series holds every cycle of that race (2024, 2026, 2028...). |
| `seriesTicker` | no | string | — | One series ticker. Kept alongside 'Kalshi series tickers' for saved tasks and API callers. |
| `tags` | no | array | `[]` | Every Kalshi election series carrying any of these tags: Senate, House, Governor, Primaries, US Elections, Referendums, Local, 2028, International. Broad: combine with 'Cycle' and 'Maximum races per venue'. |
| `venues` | no | string | `"both"` | Kalshi, Polymarket or both. Both returns the same race from each venue as separate rows with a 'venue' field. |
| `cycle` | no | string | `"any"` | Election year. Kalshi's year comes from the race ticker (SENATEAZ-24), Polymarket's from the market's end date. |
| `status` | no | string | `"settled"` | Settled races (the default) carry the result and their whole history. Open races give the history so far. |
| `granularity` | no | string | `"daily"` | One row per day or per hour of each candidate's odds. Hourly is 24 times the rows (and the cost). |
| `from` | no | string | — | Only candles and trades from this date (YYYY-MM-DD). Empty = from the day the market opened. |
| `to` | no | string | — | Only candles and trades up to this date (YYYY-MM-DD). Empty = until the market settled, or today if it is open. |
| `includeTrades` | no | boolean | `false` | Every Kalshi trade: time, yes/no price, size and taker side. A busy race has tens of thousands; set dates or 'Maximum rows' to bound it. |
| `maxRaces` | no | integer | `10` | Stop after this many races from each venue. |
| `maxResults` | no | integer | `5000` | A cap on the whole RUN for candles plus trades: your spend control. Race and candidate rows are not counted. When it cuts the run short, a free 'truncated' row says so. |
| `proxyConfiguration` | no | object | `{"useApifyProxy": false}` | Optional and off by default: both venues are public, keyless APIs. |

### Pricing (Pay Per Event)

You pay per result (`candle`) — **no charge for empty runs**. Example: **952 candles** at *$0.0003/result* ≈ **$0.32**.

Four row types, four prices: $0.0003 per candle, $0.002 per race, $0.002 per candidate, $0.0001 per trade. The example input ('arizona senate', both venues, daily) returned 5 races, 14 candidates and 952 candles on 2026-11-06: about $0.32. 'Maximum candle and trade rows' caps the run; marker rows are free. Apify platform usage (compute) is billed separately per your plan.

### Use cases

- Accuracy studies: every 2026 Senate, House and governor race with its final-week odds next to the result. Tags Senate, House, Governor; cycle 2026.
- Backtesting: hourly candles with bid and ask for one race, plus the trade tape. A series ticker, candle size hourly, trade tape on.
- Charting a race: the daily odds of every candidate from listing to result, for one keyword such as 'arizona senate'.
- Tracking 2028: the open 2028 races, refreshed on a schedule. Tag 2028, status open.
- Comparing venues: the same race on Kalshi and Polymarket, one row per venue per day. Venues both, one keyword.
- Beyond the winner: primaries, margins of victory, chamber control, ballot measures and AP-call timing markets, through their Kalshi tags or a keyword.

### Why this actor

- A race's whole life by default, not a window counted back from today.
- Settled races with the result and every candidate joined, 2024 included.
- Kalshi candles with open/high/low/close/mean, the yes bid and ask, volume and open interest.
- Kalshi's trade tape, optional.
- Free marker rows when something stops the run: a cap reached, a venue that did not answer, a race that does not exist, or fields that have been renamed.

### Limitations & updates

Covers Kalshi's election series and Polymarket events found by keyword. History starts when the venue listed the market: the Kalshi 2024 races checked (SENATEAZ-24, GOVPARTYMO-24) opened in October 2024. Polymarket candles carry the price only. The trade tape is Kalshi only. Races from the two venues are not merged into one row. Prices are the venues' own; nothing is modelled. Public market data only — no accounts or traders — and not investment advice.

### FAQ

**How far back does the history go?**

To the day the market opened, unless you set 'From date'. Kalshi's Oregon 2026 governor market ran from 2025-01-26 to 2026-11-04, and the daily candles cover all of it. Kalshi's 2024 Senate and governor markets opened in October 2024, so their history starts there. Polymarket's 2024 presidential market starts in January 2024.

**Why do some Kalshi candles have no open, high, low or close?**

No trade happened in that period. The bid, ask and open interest are still there, so the candle is kept. Candles from before the market had any price or any holder are dropped and not charged: Oregon's market opened with an empty book for weeks.

**What does Polymarket give compared to Kalshi?**

Polymarket publishes a price per point in time, so its candles carry 'close' only: no open/high/low, no bid/ask, no open interest, no trade tape here. Its race and candidate rows carry the result and volume like Kalshi's.

**How are races found?**

Kalshi: by series ticker, by tag (Senate, House, Governor, Primaries, 2028...) or by keyword against its 2,465 election series titles. Polymarket: through its own search, keeping only titles that contain every word of your keyword. Polymarket is read only when you give a keyword, because it has no series tickers or tags in Kalshi's sense.

**Are the same races on both venues joined into one row?**

No. Each venue's race comes back as its own rows with 'venue' set, because the two venues word and split races differently (Polymarket also lists margins and primaries as separate events). Match them on the title and cycle in your own sheet.

**What happens if Kalshi renames its fields again?**

The run returns a free row with notice_type 'source_changed' instead of race rows full of empty prices.

**Do I need an account or a proxy?**

No. Both venues are public, keyless APIs. The proxy input is off by default.

### Which actor to choose

Other Actors in the portfolio that pair with this one:

- **Kalshi & Polymarket Prediction Market Odds Scraper** — You want today's odds across every market on both venues, not one race's history.

### Guides & use cases

Written up on **[scrapebench.dev](https://scrapebench.dev)** — the bench that runs and verifies this actor against the live source every night:

- **How-to:** [How to run Kalshi & Polymarket Election Odds History Scraper](https://scrapebench.dev/guides/how-to-kalshi-election-odds-history-scraper/)
- **Use case:** [2026 Senate Races: Kalshi Odds History and Results](https://scrapebench.dev/use-cases/kalshi-2026-senate-odds-history/)

More actors, coverage and nightly verification results: **[scrapebench.dev](https://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](https://apify.com/scrapebench/kalshi-election-odds-history-scraper/api/mcp) 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 full daily odds history of the Arizona Senate race on Kalshi and Polymarket, with the winner."

The keyword 'arizona senate' returned 5 races (1 Kalshi, 4 Polymarket) and 952 daily candles on 2026-11-06. Add a cycle or a Kalshi series ticker (SENATEAZ) to narrow it. Runs started this way bill exactly like any other run.

### Data & lawful use

This actor collects publicly available information that Kalshi or Polymarket publishes for public
lookup — licence records, business listings and the contact details professionals list there.
Some of it identifies individuals (sole proprietors, licence holders, agents), so treat any
names, phone numbers and emails in your results as personal data.

When you run this actor, you decide what is collected and what it is used for, so you are the
controller of that data and we act only as your processor (Apify Standard Actor Contract).
You need a lawful basis for your use, must honour opt-out and deletion requests, and must
follow the marketing rules that apply to you (CAN-SPAM, TCPA, GDPR, CCPA and similar).
Don't use the output for unsolicited bulk messaging, for screening covered by the FCRA, or
for anything the source's own terms forbid.

Your results are stored in your own Apify account. We don't keep a copy of your results and
don't sell or share them.

### Disclaimer

This is an independent tool. It is not affiliated with, endorsed by or sponsored by
Kalshi or Polymarket. Names and marks are used only to describe the data source. Records are
provided as published by the source and may be out of date or incomplete. For anything
consequential (hiring, a permit, a legal decision), confirm the record with the issuing
agency. You are responsible for making sure your use complies with applicable law and the
source's terms.

### Need help?

Open an issue on this actor's **Issues** tab and we'll respond there. Bug reports, missing
fields and feature requests are all welcome. Guides and examples: [scrapebench.dev](https://scrapebench.dev).

If you are a person named in this data and want your record removed from our copy, email
apify-help@scrapebench.dev; the source agency remains the official record.

Your run may be used to improve our services. If something looks off, or you want this actor to fit your use better, please share your run data with us: [Apify Console → Settings → Security → Share run data with developers](https://console.apify.com/settings/security). It lets us see exactly what went wrong and keep improving the actor.

# Actor input Schema

## `queries` (type: `array`):

Races to pull, as words in the race's title: 'arizona senate', 'oregon governor', 'presidential election winner'. Every word must appear. Kalshi is matched on its election series titles, Polymarket through its own search. Polymarket is only read when you give a keyword.

## `query` (type: `string`):

One keyword. Kept alongside 'Keywords' for saved tasks and API callers.

## `seriesTickers` (type: `array`):

Exact Kalshi series, e.g. SENATEAZ, GOVPARTYOR, HOUSEPA7, SENATENM. One series holds every cycle of that race (2024, 2026, 2028...).

## `seriesTicker` (type: `string`):

One series ticker. Kept alongside 'Kalshi series tickers' for saved tasks and API callers.

## `tags` (type: `array`):

Every Kalshi election series carrying any of these tags: Senate, House, Governor, Primaries, US Elections, Referendums, Local, 2028, International. Broad: combine with 'Cycle' and 'Maximum races per venue'.

## `venues` (type: `string`):

Kalshi, Polymarket or both. Both returns the same race from each venue as separate rows with a 'venue' field.

## `cycle` (type: `string`):

Election year. Kalshi's year comes from the race ticker (SENATEAZ-24), Polymarket's from the market's end date.

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

Settled races (the default) carry the result and their whole history. Open races give the history so far.

## `granularity` (type: `string`):

One row per day or per hour of each candidate's odds. Hourly is 24 times the rows (and the cost).

## `from` (type: `string`):

Only candles and trades from this date (YYYY-MM-DD). Empty = from the day the market opened.

## `to` (type: `string`):

Only candles and trades up to this date (YYYY-MM-DD). Empty = until the market settled, or today if it is open.

## `includeTrades` (type: `boolean`):

Every Kalshi trade: time, yes/no price, size and taker side. A busy race has tens of thousands; set dates or 'Maximum rows' to bound it.

## `maxRaces` (type: `integer`):

Stop after this many races from each venue.

## `maxResults` (type: `integer`):

A cap on the whole RUN for candles plus trades: your spend control. Race and candidate rows are not counted. When it cuts the run short, a free 'truncated' row says so.

## `proxyConfiguration` (type: `object`):

Optional and off by default: both venues are public, keyless APIs.

## Actor input object example

```json
{
  "queries": [
    "arizona senate"
  ],
  "seriesTickers": [],
  "tags": [],
  "venues": "both",
  "cycle": "any",
  "status": "settled",
  "granularity": "daily",
  "includeTrades": false,
  "maxRaces": 10,
  "maxResults": 5000,
  "proxyConfiguration": {
    "useApifyProxy": false
  }
}
```

# Actor output Schema

## `results` (type: `string`):

Race and candidate rows: winner, result, final price, volume.

## `candles` (type: `string`):

One row per candidate per day or hour: OHLC, bid/ask, volume, open interest.

# 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 = {
    "queries": [
        "arizona senate"
    ]
};

// Run the Actor and wait for it to finish
const run = await client.actor("scrapebench/kalshi-election-odds-history-scraper").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 = { "queries": ["arizona senate"] }

# Run the Actor and wait for it to finish
run = client.actor("scrapebench/kalshi-election-odds-history-scraper").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 '{
  "queries": [
    "arizona senate"
  ]
}' |
apify call scrapebench/kalshi-election-odds-history-scraper --silent --output-dataset

```

## MCP server setup

```json
{
    "mcpServers": {
        "apify": {
            "type": "http",
            "url": "https://mcp.apify.com/?tools=fetch-actor-details,scrapebench/kalshi-election-odds-history-scraper"
        }
    }
}
```

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/OwICnOQC2bVlJneFP/builds/OBxTQd7dKUzPK6JU8/openapi.json
