# Kalshi Prediction Markets Scraper (`mina_safwat/kalshi-scraper`) Actor

Scrapes Kalshi prediction markets by category, series, event, ticker or kalshi.com link: YES/NO bid, ask and last prices with implied probability, volume, open interest, status, close and settlement times, plus optional trades and price history.

- **URL**: https://apify.com/mina_safwat/kalshi-scraper.md
- **Developed by:** [Mina](https://apify.com/mina_safwat) (community)
- **Categories:** Business, News, Automation
- **Stats:** 2 total users, 1 monthly users, 100.0% runs succeeded, 0 bookmarks
- **User rating**: No ratings yet

## Pricing

$0.80 / 1,000 rows

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

### What does Kalshi Prediction Markets Scraper do?

**Kalshi Prediction Markets Scraper** collects **live and historical prediction-market data from [Kalshi](https://kalshi.com)**, the US-regulated event exchange. Paste a kalshi.com link, a ticker or a category name and get one clean row per market with **YES/NO bid, ask and last prices, implied probability, volume, 24-hour volume, open interest, status, close and settlement times, the result once settled, and the market rules**. Optionally add each market's **recent trades** and **price history (candlesticks)**.

It reads Kalshi's official public API, so there is no login, no browser and no scraping of web pages. Try it with the prefilled Economics category: click **Start** and about 30 markets are ready in a few seconds. Because it runs on the Apify platform, you also get API access, scheduled runs, integrations (Google Sheets, Make, Zapier, Slack, webhooks) and run monitoring.

### Why scrape Kalshi?

- **Event probabilities as data.** A YES price of $0.62 means the market puts the outcome at about 62%. Track Fed decisions, CPI prints, elections, weather, sports and company metrics as numbers you can chart.
- **Research and backtesting.** Settled markets come with their result and settlement time, and price history reaches up to a year back from the close, so you can test how well the crowd predicted.
- **Trading and arbitrage monitoring.** Compare Kalshi's odds with other prediction markets, sportsbooks or your own models on a schedule.
- **News and media.** Quote live odds for an article, a newsletter or a dashboard.
- **Liquidity checks.** Volume, 24-hour volume, open interest and top-of-book sizes show which markets are actually traded.

### How to scrape Kalshi prediction markets

1. Open [kalshi.com](https://kalshi.com) and go to a category, a market page, or any event you care about.
2. Copy the link from your browser's address bar, for example `https://kalshi.com/markets/kxfeddecision/fed-meeting/kxfeddecision-26oct` or `https://kalshi.com/category/economics/fed`.
3. Paste it into **Kalshi links, tickers or categories**, one per line. Tickers (`KXFEDDECISION`) and category names (`Politics`) work too.
4. Choose the **market status** (open by default) and how many markets to keep per line.
5. Optionally turn on **recent trades** or **price history**.
6. Click **Start**, then download the results when the run finishes.

#### What you can paste

| You paste | You get |
|---|---|
| A category link or name: `https://kalshi.com/category/politics`, `Crypto`, `Climate` | Markets from the category's most traded series first. |
| A sub-category link: `https://kalshi.com/category/economics/fed`, `https://kalshi.com/category/commodities/natural-gas`, `https://kalshi.com/category/crypto/frequency/daily` | Markets of that sub-category. A link that names one of Kalshi's tags (`fed`, `soccer`) reads that tag, and a league below a tag (`soccer/epl`) reads the tag's series whose names match it, or the whole tag if none do. A topic without a tag (`natural-gas`, `midterms`) reads the series whose names match it; if none match, the link is listed in `FAILED_INPUTS` with the reason and nothing is saved or charged for it (paste a series link from that page instead). A `frequency/...` link reads the series that repeat that often. |
| A series link or ticker: `https://kalshi.com/markets/kxfeddecision`, `KXFEDDECISION` | Every market of the series, all of its events. |
| An event link or ticker: `https://kalshi.com/markets/kxfeddecision/fed-meeting/kxfeddecision-26oct`, `KXFEDDECISION-26OCT` | Every market (outcome) of that event. |
| A market ticker: `KXFEDDECISION-26OCT-H0` | That one market, whatever its status. |

Category names that work: Politics, Elections, Sports, Culture (Entertainment), Crypto, Climate (Climate and Weather), Economics, Mentions, Companies, Financials, Science (Science and Technology), Commodities, Health, World, Social, Transportation. Links without `https://` work as well.

### Input

Everything is set on the Input tab. In JSON, the input looks like this:

```json
{
    "sources": [
        "https://kalshi.com/category/economics",
        "https://kalshi.com/markets/kxfeddecision/fed-meeting/kxfeddecision-26oct",
        "KXBTCD"
    ],
    "status": "open",
    "max_markets": 50,
    "include_trades": false,
    "include_price_history": true,
    "price_history_interval": "day",
    "price_history_days": 30
}
```

| Field | What it does |
|---|---|
| `sources` | Kalshi links, tickers or category names, one per line (see the table above). API callers can also send `{"url": "..."}` objects. |
| `status` | `open` (default), `closed` (trading ended, result not settled yet), `settled` (result known) or `all`. Applies to categories, series and events; a market ticker is always saved. |
| `max_markets` | Stops after this many markets for each line. Default: 200. A category reads its series with the most total volume first; for open markets, each series gives its markets traded most in the last 24 hours first. With `closed`, a category gives the markets that closed most recently first. |
| `include_trades` | Adds each market's newest trades as rows with `"type": "trade"`. Default: off. |
| `max_trades_per_market` | How many of the newest trades per market. Default: 100. |
| `include_price_history` | Adds each market's price history as rows with `"type": "candle"`. Default: off. |
| `price_history_interval` | `day` (default), `hour` or `minute`. |
| `price_history_days` | How many days of history, counting back from now, or from the close for a closed market. Default: 30. |
| `proxyConfiguration` | Leave empty. Kalshi's API answers directly; if it ever refuses, the Actor retries through Apify Proxy on its own. |

### Output

Each market becomes one row. Here is a real example:

```json
{
    "type": "market",
    "ticker": "KXFEDDECISION-26OCT-H0",
    "event_ticker": "KXFEDDECISION-26OCT",
    "series_ticker": "KXFEDDECISION",
    "title": "Will the Federal Reserve Hike rates by 0bps at their October 2026 meeting?",
    "subtitle": "Fed maintains rate",
    "event_title": "Fed decision in Oct 2026?",
    "event_subtitle": "On Oct 28, 2026",
    "series_title": "Fed meeting",
    "category": "Economics",
    "status": "active",
    "yes_bid": 0.84,
    "yes_ask": 0.85,
    "no_bid": 0.15,
    "no_ask": 0.16,
    "last_price": 0.85,
    "last_price_24h_ago": 0.83,
    "implied_probability": 0.845,
    "yes_bid_size": 1520.0,
    "yes_ask_size": 310.27,
    "volume": 3963721.18,
    "volume_24h": 70951.01,
    "open_interest": 1475569.95,
    "result": null,
    "settlement_value": null,
    "open_time": "2025-09-29T14:00:00Z",
    "close_time": "2026-10-28T17:59:00Z",
    "expected_expiration_time": "2026-10-28T18:05:00Z",
    "settlement_time": null,
    "can_close_early": true,
    "rules": "If the Federal Reserve does a Hike of 0bps on October 28, 2026, then the market resolves to Yes.",
    "url": "https://kalshi.com/markets/kxfeddecision/fed-meeting/kxfeddecision-26oct",
    "scraped_at": "2026-10-10T14:12:56Z"
}
```

With trades or price history on, rows like these are saved after the market they belong to (match them on `ticker`):

```json
{"type": "trade", "ticker": "KXELONMARS-99", "event_ticker": "KXELONMARS-99", "trade_id": "6da6bc8c-b367-a775-5f6c-9e70e98df6d4", "created_time": "2026-10-07T20:20:17Z", "yes_price": 0.09, "no_price": 0.91, "count": 35.0, "taker_side": "no", "is_block_trade": false}
{"type": "candle", "ticker": "KXFEDDECISION-26OCT-H0", "event_ticker": "KXFEDDECISION-26OCT", "interval": "1h", "period_end": "2026-10-10T14:00:00Z", "price_open": 0.85, "price_high": 0.85, "price_low": 0.84, "price_close": 0.85, "price_mean": 0.8457, "yes_bid_close": 0.84, "yes_ask_close": 0.85, "volume": 135.29, "open_interest": 1475569.95}
```

You can download the dataset in various formats such as JSON, HTML, CSV, or Excel. The Output tab has four views: **Markets**, **Market details**, **Trades** and **Price history**. Filter on the `type` column to separate markets, trades and candles in a spreadsheet.

### Data fields

**Prices are in US dollars per contract, from 0 to 1.** A YES contract pays $1 if the market resolves YES, so a price of 0.62 is 62 cents and also a 62% implied chance. Volumes and open interest are numbers of contracts (Kalshi allows fractional contracts). Times are ISO 8601 in UTC.

#### Market rows (`"type": "market"`)

| Field | Description |
|---|---|
| `ticker` | The market's ticker. |
| `event_ticker`, `series_ticker` | The event and series the market belongs to. |
| `title` | The market's question. |
| `subtitle` | The outcome the YES side stands for, such as "Above 4.50%" or "Fed maintains rate". |
| `event_title`, `event_subtitle` | The event's title and date or detail line. |
| `series_title` | The series name, such as "Fed meeting". |
| `category` | Kalshi's category, such as Economics or Sports. |
| `status` | Kalshi's status: `active` (open for trading), `initialized`, `inactive`, `closed`, `determined`, `disputed`, `amended` or `finalized` (settled). |
| `yes_bid`, `yes_ask` | Best price to sell and to buy YES, in dollars. 0 means nobody is bidding. |
| `no_bid`, `no_ask` | Best price to sell and to buy NO, in dollars. |
| `last_price` | Last traded YES price, in dollars. |
| `last_price_24h_ago` | The last traded YES price as it stood 24 hours ago. |
| `implied_probability` | 0 to 1. The settled result (1 for YES, 0 for NO) once known; otherwise the middle of the YES bid and ask when both are quoted; otherwise the last traded price. |
| `yes_bid_size`, `yes_ask_size` | Contracts waiting at the best YES bid and ask. |
| `volume`, `volume_24h` | Contracts traded in total and in the last 24 hours. |
| `open_interest` | Contracts currently held. |
| `result` | `yes`, `no` or `scalar` once the market is determined; empty before. |
| `settlement_value` | What one YES contract paid out, in dollars, once settled. |
| `open_time`, `close_time` | When trading opened and closes (or closed). |
| `expected_expiration_time` | When Kalshi expects the outcome to be known. |
| `settlement_time` | When the market settled. |
| `can_close_early` | Whether trading can stop before `close_time` if the event happens. |
| `rules` | The main resolution rule in plain language. |
| `url` | The event's page on kalshi.com. |
| `scraped_at` | When the prices were read. |

#### Trade rows (`"type": "trade"`)

`ticker`, `event_ticker`, `trade_id`, `created_time`, `yes_price` and `no_price` (dollars), `count` (contracts), `taker_side` (`yes` or `no`) and `is_block_trade`. Newest trades come first.

#### Price history rows (`"type": "candle"`)

`ticker`, `event_ticker`, `interval` (`1d`, `1h` or `1m`), `period_end`, `price_open`, `price_high`, `price_low`, `price_close` and `price_mean` (traded YES prices in the period, empty when nothing traded), `yes_bid_close` and `yes_ask_close` (the quotes at the end of the period), `volume` and `open_interest`. Kalshi leaves out periods in which nothing changed, so an hourly history can have fewer than 24 rows a day.

### How much does it cost to scrape Kalshi?

The Actor uses **pay-per-event** pricing: you pay for each row saved, and the exact price per row is on the **Pricing** tab. Sources that return nothing are never charged, and if you set a maximum cost for a run, the Actor stops as soon as it is reached.

Because each trade and each price-history period is its own row, **trades and price history multiply the row count**. Plan with these examples:

| Run | Rows |
|---|---|
| 30 open markets, no extras | 30 |
| 200 markets + 100 newest trades each | up to 20,200 |
| 50 markets + 30 days of daily price history | up to 1,550 |
| 50 markets + 30 days of hourly price history | up to 36,050 |
| 10 markets + 7 days of minute price history | up to 100,810 |

The Actor itself is light: it calls Kalshi's free public API with no proxy and 512 MB of memory, and a 30-market run finishes in under 10 seconds.

### Tips for scraping Kalshi

- **Start narrow.** A sub-category link (`/category/economics/inflation`) or a series ticker gives you exactly the markets you want, and fewer rows to pay for.
- **Use daily price history** unless you need intraday moves; hourly and minute histories are far larger.
- **Schedule runs** (for example every hour) to build your own time series of odds; `scraped_at` tells the runs apart.
- **Settled markets:** set `status` to `settled` with a series ticker to collect the full record of past outcomes, including markets Kalshi has moved to its archive.
- **Combo (parlay) markets** are left out of category and series lists; paste a combo's market ticker to get it.
- The same market reached through two lines is saved once.

### FAQ, disclaimers and support

**Do I need a Kalshi account or API key?** No. The Actor reads Kalshi's public market data API, which needs no login.

**Why does my event return no markets?** With the default `open` status, an event whose markets have all closed or settled has nothing open. Set `status` to `all` (or `settled`). Sources that return nothing are listed with the reason in the `FAILED_INPUTS` record of the run's key-value store, and the run's status message gives the counts. A run fails only when Kalshi could not be reached for every source.

**Why is `liquidity` not a field?** Kalshi no longer publishes a liquidity figure. Use `yes_bid_size`, `yes_ask_size`, `volume_24h` and `open_interest` instead.

**Is it legal to scrape Kalshi?** This Actor reads public market data that Kalshi publishes through its official API. It collects no personal data: trades are anonymous. You are still responsible for making sure your use complies with Kalshi's terms and the laws that apply to you. This Actor is not affiliated with or endorsed by Kalshi, and its data is not trading advice.

**Found a problem or need a custom solution?** Open an issue on the **Issues** tab and we will look into it. Custom data feeds are available on request.

# Actor input Schema

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

One per line. Paste a kalshi.com link to a category (https://kalshi.com/category/economics, or a sub-category such as https://kalshi.com/category/economics/fed), a series (https://kalshi.com/markets/kxfeddecision) or an event (https://kalshi.com/markets/kxfeddecision/fed-meeting/kxfeddecision-26oct). You can also type a ticker (series KXFEDDECISION, event KXFEDDECISION-26OCT or market KXFEDDECISION-26OCT-H0) or a category name (Politics, Elections, Sports, Culture, Crypto, Climate, Economics, Mentions, Companies, Financials, Science, Commodities, Health, World). Categories read their most traded series first.

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

Which markets of a category, series or event to save. A market ticker you paste is always saved, whatever its status.

## `max_markets` (type: `integer`):

Stops after this many markets for each line in Kalshi links, tickers or categories. A category reads its series with the most total volume first; for open markets, each series gives its markets traded most in the last 24 hours first. With the closed status, a category gives the markets that closed most recently first.

## `include_trades` (type: `boolean`):

Adds the newest trades of every saved market as rows with type "trade": price, number of contracts, taker side and time.

## `max_trades_per_market` (type: `integer`):

How many of the newest trades to save for each market, when trades are on.

## `include_price_history` (type: `boolean`):

Adds price history (candlesticks) of every saved market as rows with type "candle": open, high, low, close and mean traded price, closing bid and ask, volume and open interest per period. Kalshi skips periods in which nothing changed.

## `price_history_interval` (type: `string`):

Length of one price-history period. Shorter periods mean many more rows: 30 days is up to 30 daily rows, 720 hourly rows or 43,200 minute rows per market.

## `price_history_days` (type: `integer`):

How many days of price history to save, counting back from now, or from the close for a market that has already closed.

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

Leave this empty: Kalshi's public API answers the Actor directly, and if it ever refuses, the Actor retries through Apify Proxy on its own. Choose a proxy here only to send every request through it.

## Actor input object example

```json
{
  "sources": [
    "https://kalshi.com/category/economics"
  ],
  "status": "open",
  "max_markets": 30,
  "include_trades": false,
  "max_trades_per_market": 100,
  "include_price_history": false,
  "price_history_interval": "day",
  "price_history_days": 30,
  "proxyConfiguration": {
    "useApifyProxy": false
  }
}
```

# Actor output Schema

## `dataset` (type: `string`):

No description

# 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": [
        "https://kalshi.com/category/economics"
    ],
    "max_markets": 30
};

// Run the Actor and wait for it to finish
const run = await client.actor("mina_safwat/kalshi-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 = {
    "sources": ["https://kalshi.com/category/economics"],
    "max_markets": 30,
}

# Run the Actor and wait for it to finish
run = client.actor("mina_safwat/kalshi-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 '{
  "sources": [
    "https://kalshi.com/category/economics"
  ],
  "max_markets": 30
}' |
apify call mina_safwat/kalshi-scraper --silent --output-dataset

```

## MCP server setup

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