# Kalshi Markets Scraper & Price Monitor - Odds, Volume, Changes (`neverempty/kalshi-markets-scraper`) Actor

For prediction-market traders and researchers: Kalshi markets as flat rows with bid, ask, last price, volume, open interest, close time and result, by ticker, category or keyword across 10,878 open events (2026-09-21). Monitoring returns only markets whose price or volume moved. No API key.

- **URL**: https://apify.com/neverempty/kalshi-markets-scraper.md
- **Developed by:** [NeverEmpty](https://apify.com/neverempty) (community)
- **Categories:** Developer tools, Automation, Other
- **Stats:** 2 total users, 1 monthly users, 100.0% runs succeeded, 0 bookmarks
- **User rating**: No ratings yet

## Pricing

from $1.46 / 1,000 market returneds

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?

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 Markets Scraper & Price Monitor - Odds, Volume, Changes

Get **Kalshi prediction markets in every category** (politics, elections, economics, financials, crypto, sports, entertainment, science, world) as one clean row per market: yes/no bid and ask, last price, volume, 24-hour volume, open interest, close time, status, result and settlement value. Pick markets by **series, event or market ticker**, or find them by **category and keyword**. A **monitoring mode** returns only the markets whose price moved by at least the number of cents you choose, or whose volume changed, with the earlier values in the same row.

It reads Kalshi's official public market-data API (`api.elections.kalshi.com/trade-api/v2`). No login, no API key, no proxy.

### Why this Actor

- **Price and volume monitor with a threshold.** Run it on a schedule with `monitoringMode` and `minPriceChangeCents` (for example 3): you get only the markets that moved 3 cents or more since the last time you received them, with `lastPriceBefore`, `lastPriceChange`, `yesBidBefore` and `volumeBefore` next to the new values. Small moves add up, because each market is compared with the values you last received, not with the previous run.
- **Search the whole exchange.** `categories` and `keywords` search all of Kalshi's events with the chosen status (10,878 open events on 2026-09-21) and return the markets of every match, with the event title and category on each row.
- **Reads Kalshi's current field names.** Kalshi now sends prices as `yes_bid_dollars` ("0.4400") and quantities as `volume_fp` ("618693.88"); the old integer fields such as `yes_bid` are no longer in the response (checked 2026-09-21). This Actor reads both the new and the old names, and returns `null` (never 0) when a value is missing.
- **Honest empty results.** An unknown ticker, a series with no open market, a search with no match, a refusal, a rate limit and a cut-off page each come back as a separate free row that says what happened. Kalshi answers an unknown or lowercase series or event with an empty list (HTTP 200), so the Actor checks the ticker itself before saying "no markets".

For weather markets (daily high and low temperature, rain, snow) with the National Weather Service forecast of each market's settlement station on the same row, use the sister Actor **Kalshi Weather Markets API**.

### Input

| Field | Type | Default | What it does |
|---|---|---|---|
| `seriesTickers` | list | (prefilled: KXFEDDECISION) | Kalshi series tickers, one per line (KXFEDDECISION, KXCPIYOY, KXNFLGAME, KXPRESPARTY ...). Lowercase is accepted. If every field is left empty with monitoring off, the example series KXFEDDECISION and KXCPIYOY are read. |
| `eventTickers` | list | (none) | Kalshi event tickers, such as KXFEDDECISION-26OCT. |
| `marketTickers` | list | (none) | Single market tickers, such as KXFEDDECISION-26OCT-H0. Returned whatever their status. |
| `categories` | list | (none) | Kalshi categories: Politics, Elections, Economics, Financials, Crypto, Commodities, Companies, Business, Sports, Entertainment, Mentions, Social, Science and Technology, AI, Health, World, Climate and Weather, Transportation, Education, Exotics. |
| `keywords` | list | (none) | Words matched against the event title, subtitle and ticker (case-insensitive; any keyword). With categories, an event must match both. |
| `marketStatus` | select | open | `open` (tradable now; Kalshi reports them as `active`), `settled` (with `result`, `expirationValue` and `settlementValueUsd`), `closed` (trading ended, not settled yet) or `all`. |
| `closedWithinDays` | integer | (none) | For settled, closed and all: only markets whose close time is within the last N days. Ignored for open. Required with categories, keywords or monitoring when the status is not open. |
| `maxResults` | integer | 1000 | Stop after this many market rows (monitoring off). A free row says what was left out. |
| `monitoringMode` | boolean | false | Return only markets that moved by at least the amounts below, whose status or result changed, or that are new since the last run. |
| `minPriceChangeCents` | integer | 1 | Monitoring: minimum move of the yes bid, yes ask or last price, in cents. |
| `minVolumeChange` | integer | 1 | Monitoring: minimum move of volume or open interest, in contracts. `0` = do not watch volume and open interest. |
| `resetMonitoringState` | boolean | false | Forget what monitoring remembered, so the next monitoring run returns everything again. Turn it off after one run. |

Examples:

```json
{ "seriesTickers": ["KXFEDDECISION", "KXNFLGAME"] }
```

```json
{ "categories": ["Economics"], "keywords": ["fed", "cpi", "gdp"] }
```

```json
{ "keywords": ["bitcoin"], "monitoringMode": true, "minPriceChangeCents": 3, "minVolumeChange": 0 }
```

```json
{ "seriesTickers": ["KXFEDDECISION"], "marketStatus": "settled", "closedWithinDays": 30 }
```

### Output

One row per market. Example (KXNFLGAME, read 2026-09-21):

```json
{
  "inputType": "series",
  "input": "KXNFLGAME",
  "category": "Sports",
  "seriesTicker": "KXNFLGAME",
  "seriesTitle": "Professional Football Game",
  "eventTicker": "KXNFLGAME-26SEP28PHICHI",
  "eventTitle": "Philadelphia vs Chicago",
  "eventSubtitle": "PHI vs CHI (Sep 28)",
  "marketTicker": "KXNFLGAME-26SEP28PHICHI-PHI",
  "marketTitle": "Philadelphia wins",
  "yesLabel": "Philadelphia",
  "marketStatus": "active",
  "yesBid": 0.65,
  "yesAsk": 0.66,
  "noBid": 0.34,
  "noAsk": 0.35,
  "yesMid": 0.655,
  "spread": 0.01,
  "lastPrice": 0.66,
  "previousPrice": 0.48,
  "volume": 36302.11,
  "volume24h": 30694.86,
  "openInterest": 24662.32,
  "closeTime": "2026-10-01T00:15:00Z",
  "result": null
}
```

| Column | Meaning |
|---|---|
| `yesBid`, `yesAsk`, `noBid`, `noAsk`, `lastPrice`, `previousPrice` | USD per contract (a contract pays $1.00). `0` means Kalshi reports no resting bid at any price; `null` means the field was not in the response. `previousPrice` is Kalshi's own previous price field. |
| `yesMid`, `spread` | Calculated from yes bid and ask, only when both sides have resting orders and the market is not settled; otherwise `null`. When nobody is selling, Kalshi reports the yes ask as 1.00 with size 0 (and the yes bid as 0 with size 0 when nobody is buying), so check `yesAskSize` / `yesBidSize` before reading those prices as quotes. |
| `volume`, `volume24h`, `openInterest`, `yesBidSize`, `yesAskSize` | Contracts. Kalshi allows fractional contracts, so these can have decimals. |
| `category`, `eventTitle`, `eventSubtitle`, `seriesTitle` | From the event (or the series). `seriesTitle` is filled when a series ticker was given. `null` when Kalshi's event list could not be read; the market row is still returned. |
| `strikeType`, `floorStrike`, `capStrike` | Kalshi's strike type as sent (for example `greater`, `greater_or_equal`, `less`, `between`, `custom`, `structured`) and the floor and cap numbers of ranged markets; `null` when Kalshi sends none. |
| `result`, `settlementValueUsd`, `expirationValue`, `settledAt` | For settled markets: `yes`/`no`, what one Yes contract of this market paid (`1` or `0`), Kalshi's expiration value for the whole event as text (for example `Hike 25bps` or `68.72`; the same on every market of the event) and when it settled. |
| `inputType`, `input` | What found the row: `series`, `event`, `market`, `search` (with the categories and keywords) or `example-series`. |
| `monitorStatus`, `changedFields` | Monitoring mode only: `first-check`, `changed` (with the columns that moved) or `new-market`. |
| `yesBidBefore`, `yesAskBefore`, `lastPriceBefore`, `lastPriceChange`, `volumeBefore`, `openInterestBefore` | Monitoring mode only: the values when you last received this market, and the change of the last price. |

Rows whose `status` is not `ok` are free and explain themselves in `note`: `unknown-series`, `unknown-event`, `unknown-market`, `no-markets`, `no-events-matched`, `duplicate`, `blocked`, `unreadable`, `refused`, `incomplete`, `not-checked`, `budget-reached`, `no-change-since-last-run`, `invalid-input`.

### Pricing

- **$2.00 per 1,000 market rows** returned.
- Monitoring mode adds **$0.30 per 1,000 checks**: one check per series, event or market ticker whose markets are read, and one per event found by categories or keywords, whether or not anything moved. Unknown tickers, items with no market and failed requests are not charged.
- Free rows (the ones that explain why something was not returned) cost nothing.
- The run stops before it would go over the maximum total charge you set, and a free row says what was left out.

### Monitoring mode

Turn on `monitoringMode` and run the Actor on a schedule (for example every 10 minutes). The first run returns every market once as a baseline. Later runs return only markets whose yes bid, yes ask or last price moved by at least `minPriceChangeCents`, whose volume or open interest moved by at least `minVolumeChange` contracts, whose status or result changed, plus markets that opened since. A run with no such market returns one free `no-change-since-last-run` row. Markets are remembered per ticker (and per status, closedWithinDays and thresholds, so a schedule for open markets and one for settled markets of the same series do not overwrite each other) in a named key-value store; markets you did not receive because the maximum total charge was reached are not remembered, so the next run returns them if they still differ. Do not put the same tickers or search in two schedules that can run at the same time (Apify's key-value store has no atomic update).

A search by category or keyword charges one check per matching event and reads each event, so a broad search is slower and more expensive to monitor than a few series: Economics had 535 open events on 2026-09-21, which is 535 checks ($0.16) and took about 4 to 6 minutes per run.

### Notes and limits

- Kalshi's public API is read at about 6 requests per second; a rate limit (HTTP 429) is waited out and retried, and reported as a free `blocked` row if it persists. A search lists every event of the chosen status first (about 20 seconds for open events), then reads each matching event.
- If you list a series and also an event or market inside it, each market is returned once; the later entry gets a free `duplicate` row naming the entry that already covered it.
- Markets that settled before Kalshi's archive cut-off (2026-07-22 when checked on 2026-09-21; it moves forward over time) are no longer served by Kalshi's live API, so settled history reaches back only to that date.
- Combination markets (Kalshi's multi-leg parlays, tickers starting with KXMVE) are not in Kalshi's event list, so a category or keyword search does not return them.
- This Actor returns market data. It does not place orders and is not trading advice.

# Actor input Schema

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

Kalshi series to read, one per line, for example KXFEDDECISION (Fed rate decision), KXCPIYOY (CPI inflation), KXNFLGAME (NFL games) or KXPRESPARTY (party winning the presidency). Every market of the series with the chosen status is returned. Lowercase is accepted. A ticker Kalshi does not have comes back as a free 'unknown-series' row. If you leave every field empty with monitoring off, the example series KXFEDDECISION and KXCPIYOY are read. Up to 500 tickers per run across the three ticker lists.

## `eventTickers` (type: `array`):

Kalshi events to read, one per line, for example KXFEDDECISION-26OCT (the October 2026 Fed decision). Every market of the event with the chosen status is returned.

## `marketTickers` (type: `array`):

Single Kalshi markets, one per line, for example KXFEDDECISION-26OCT-H0. These are returned whatever their status (marketStatus and closedWithinDays do not apply to them).

## `categories` (type: `array`):

Find markets by Kalshi category: the Actor lists Kalshi's events with the chosen status and keeps those in these categories (together with keywords, an event must match both). Weather markets with the NWS forecast of the settlement station are in the sister Actor Kalshi Weather Markets API.

## `keywords` (type: `array`):

Find markets by words in the event title, subtitle or ticker, for example fed, bitcoin, election or a team name (case-insensitive; an event matching any keyword counts). The Actor lists Kalshi's events with the chosen status (about 11,000 open events on 2026-09-21, read in about 20 seconds) and reads the markets of each matching event.

## `marketStatus` (type: `string`):

open = markets you can trade now (Kalshi reports them as 'active'). settled = finished markets with the result and the settlement value. closed = trading ended, not yet settled ('closed' or 'determined'). all = every market. For settled, closed and all, closedWithinDays limits how far back to go (required with categories, keywords or monitoring).

## `closedWithinDays` (type: `integer`):

Only markets whose close time is within the last N days (markets closing later, such as open ones, are kept with all). Ignored when marketStatus is open. Markets settled before Kalshi's archive cut-off (2026-07-22 on 2026-09-21, moving forward over time) are no longer served by Kalshi's live API and are not returned.

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

With monitoring off, the run stops once this many market rows have been returned, and a free row says what was not returned. Free rows do not count. In monitoring mode it does not limit the changed markets returned.

## `monitoringMode` (type: `boolean`):

Off = every market comes back, charged per row. On = the Actor remembers, per ticker or search, the yes bid, yes ask, last price, volume, open interest, status and result of each market it returned, and later runs return only markets that moved by at least the amounts below, whose status or result changed, or that are new. The first run returns every market once as a baseline. Each run costs $0.30 per 1,000 checks, changes or not: one check per series, event or market ticker whose markets are read, and one per event found by categories or keywords (unknown tickers, items with no market and failed requests are free), plus the row price for the markets returned. Example: 5 series every 10 minutes = 21,600 checks a month = $6.48, plus the markets that moved. The Actor reads only as many items as the run's maximum total charge can pay for with a check and one row each. Do not put the same tickers or search in two schedules that can run at the same time.

## `minPriceChangeCents` (type: `integer`):

Monitoring mode: a market is returned when its yes bid, yes ask or last price moved by at least this many cents (1 cent = $0.01 per contract) since the last time it was returned. Small moves add up: two 1-cent moves count as 2 cents.

## `minVolumeChange` (type: `integer`):

Monitoring mode: a market is also returned when its volume or open interest moved by at least this many contracts since the last time it was returned. 0 = do not watch volume and open interest (only prices, status and result).

## `resetMonitoringState` (type: `boolean`):

Clears every remembered market for this Actor, so the next monitoring run returns every market again as a first check. This affects all your monitoring runs. Turn it off again after one run: left on in a schedule, every run returns every market again and charges for it.

## Actor input object example

```json
{
  "seriesTickers": [
    "KXFEDDECISION"
  ],
  "marketStatus": "open",
  "maxResults": 1000,
  "monitoringMode": false,
  "minPriceChangeCents": 1,
  "minVolumeChange": 1,
  "resetMonitoringState": false
}
```

# Actor output Schema

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

One row per Kalshi market: category, series, event and market ticker and title, status, yes/no bid and ask, last price, volume, 24-hour volume, open interest, close time, result and settlement value; in monitoring mode only markets whose price or volume moved, with the earlier values. Unknown tickers, searches with no match, refusals and limits come back as free rows that say why.

# 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 = {
    "seriesTickers": [
        "KXFEDDECISION"
    ]
};

// Run the Actor and wait for it to finish
const run = await client.actor("neverempty/kalshi-markets-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 = { "seriesTickers": ["KXFEDDECISION"] }

# Run the Actor and wait for it to finish
run = client.actor("neverempty/kalshi-markets-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 '{
  "seriesTickers": [
    "KXFEDDECISION"
  ]
}' |
apify call neverempty/kalshi-markets-scraper --silent --output-dataset

```

## MCP server setup

```json
{
    "mcpServers": {
        "apify": {
            "type": "http",
            "url": "https://mcp.apify.com/?tools=fetch-actor-details,neverempty/kalshi-markets-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/J6TDMrn2qXx6MVVF2/builds/KRwD5n3K4g4mExg2p/openapi.json
