# Kalshi Weather Markets Scraper (`utopicvision/kalshi-weather-markets-scraper`) Actor

Kalshi temperature, rain, hurricane and tornado markets as clean, spreadsheet-ready rows. Parses the city, weather station, storm name, bracket bounds and settled outcome into their own columns. Filter by city, temperature, date, volume or implied probability. No API key, no proxies.

- **URL**: https://apify.com/utopicvision/kalshi-weather-markets-scraper.md
- **Developed by:** [George Semaan](https://apify.com/utopicvision) (community)
- **Categories:**
- **Stats:** 2 total users, 1 monthly users, 100.0% runs succeeded, 0 bookmarks
- **User rating**: No ratings yet

## Pricing

from $2.00 / 1,000 market 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?

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

## Kalshi Weather Markets Scraper: Temperature, Rain, Hurricanes & Tornadoes, 28 Cities + Weather Stations

**$2.00 per 1,000 rows. No API key. No proxies. Runs in under two minutes.**

Pulls every open weather market on Kalshi (daily high and low temperature, hourly
temperature, heat streaks, rain, snow, hurricanes, tropical storms, tornadoes,
earthquakes and climate markets) and returns them as flat, spreadsheet-ready rows
with the city, weather station, storm name, bracket bounds and settled outcome
already parsed into their own columns.

It also reads **settled** markets, including the observed temperature that decided
them, and can walk Kalshi's deep archive back to **August 2021**.

***

### What you get

A single flat row per market. No nested JSON to unpick, no cent-vs-dollar guessing,
no reading rules text to find out which city a ticker belongs to.

| ticker | city | station | date | bracket | yes\_bid | yes\_ask | implied\_prob | volume |
|---|---|---|---|---|---|---|---|---|
| KXHIGHNY-26AUG29-T79 | New York City | CLINYC | 2026-08-29 | <79 | 0.56 | 0.57 | 0.565 | 2242.64 |
| KXHIGHNY-26AUG29-B79.5 | New York City | CLINYC | 2026-08-29 | 79-80 | 0.36 | 0.37 | 0.365 | 715.79 |
| KXRAIN-26AUG28-CHI | Chicago | CLIORD | 2026-08-28 | >0 inches | 0.00 | 0.01 | 0.005 | 4188.00 |

Storm markets, which have no city at all, come back keyed on the storm instead:

| ticker | storm\_name | storm\_metric | category | wind threshold | basin | yes\_ask |
|---|---|---|---|---|---|---|
| KXHURCAT-26DOLLY-T1 | Dolly | hurricane\_category | 1 | 74 mph | Atlantic | 0.09 |
| KXHURCAT-26DOLLY-T2 | Dolly | hurricane\_category | 2 | 96 mph | Atlantic | 0.02 |

And settled markets carry what actually happened:

| ticker | date | bracket | result | observed | settlement |
|---|---|---|---|---|---|
| KXHIGHNY-26AUG27-T87 | 2026-08-27 | >87 | no | 77 °F | $0.00 |

***

### 30-second quickstart

1. Click **Start**. The defaults fetch three representative series, about 226 rows,
   in a few seconds: a temperature ladder, a multi-city rain event and a storm bloc.
2. To widen it, clear **Series tickers** entirely for every weather series with open
   markets, about 1,280 rows in roughly six minutes. To narrow it instead, list the
   tickers you want. Series are discovered at runtime, so new ones appear without an
   update.
3. To get results instead of prices, set **What to fetch** to `settled`.

***

### Coverage

Measured on a live full run:

- **109 series** with open markets, out of 360 in Kalshi's catalogue; the other
  251 are renamed duplicates and unlaunched tickers, filtered out automatically.
- **28 cities**, **41 distinct weather stations**, **57 named storms**,
  **17 regions and ocean basins**.
- **99.7%** of rows resolve a city, a named storm or a region. The remainder are
  markets whose rules genuinely state no location.

**Every city Kalshi lists is in the United States.** These are the 28, and there
are no others. The scraper reads whatever the exchange publishes, so if a city
is missing it is missing from Kalshi, not filtered out here:

Atlanta, Austin, Boston, Chicago, Dallas, Dallas/Fort Worth, Denver,
Hoover Dam, Houston, Houston Hobby Airport, Las Vegas, Los Angeles, Louisville,
Miami, Minneapolis, New Orleans, New York City, Newark, Oklahoma City,
Philadelphia, Phoenix, San Antonio, San Diego, San Francisco, San Jose, Seattle,
Trenton, Washington DC.

Kalshi writes several of these two ways: `Austin` and `Austin, TX`,
`Central Park, New York City` and `New York City`. Both spellings are preserved
in `city` and unified in `city_canonical`, and the `cities` filter matches
either, so you never have to know which wording a given market used.

#### Outside the United States

There is international coverage, but it is not city-level. Those markets are
about a basin, a country or the whole planet, so they carry a `region` instead
of a city. Filter them with `regions`, not `cities`:

| `region` | What it covers |
|---|---|
| `Atlantic`, `Eastern Pacific`, `Central Pacific` | Hurricanes, named storms, season totals |
| `Worldwide` | Global temperature records, CO2 concentration, earthquakes, volcanoes |
| `Japan` | Earthquake markets |
| `European Union`, `India` | Climate-policy markets |
| `Arctic` | Sea ice extent |
| `Pacific` | ENSO / El Niño (RONI) |

```jsonc
// Atlantic hurricane season markets
{ "mode": "live", "regions": ["Atlantic"] }
```

Market types, by open-market count on a recent run: storms 347, low temperature
291, high temperature 288, rain 156, hourly temperature 63, heat streaks 50,
global temperature 17, earthquakes 17, ENSO 16, sea ice 9, CO2 5, volcanoes 4,
climate policy 4.

***

### Input

Every field is optional.

| Field | Default | What it does |
|---|---|---|
| `mode` | `live` | `live` for open markets and prices, `settled` for outcomes, `both`. |
| `seriesFilter` | `KXHIGHNY`, `KXRAIN`, `KXHURCAT` | Which series to fetch. Clear it entirely to fetch every weather series that currently has open markets. |
| `includeStorms` | `true` | Include hurricane, tropical storm and tornado markets. |
| `includeOrderbook` | `false` | Add resting bid depth, a derived yes-ask and the spread. Batched, so it costs very few extra requests. |
| `includeRecentTrades` | `false` | Add recent executed trades. Costs one request per market. |
| `historyDays` | `7` | Settlement lookback window. |
| `includeHistoricalArchive` | `false` | Also walk Kalshi's deep archive, back to 2021. |
| `incremental` | `false` | Remember the newest settlement seen and fetch only newer ones next run. |
| `maxResults` | `5000` | Safety cap on rows per run. |
| `requestsPerSecond` | `2` | Politeness limit. |

#### Filters

Ask for the slice you want instead of downloading everything and grepping it.
Filters run **before rows are written**, so a narrower request returns fewer rows
and costs less.

| Field | What it does |
|---|---|
| `cities` | Keep only these cities: `["Chicago", "New York City"]`. Case- and punctuation-insensitive, and matches both the city as Kalshi wrote it and its canonical name, so archive rows written "Central Park, New York City" still match "New York City". |
| `regions` | Keep only these regions, basins or countries: `["Atlantic"]`. This is how you filter storm markets, which have no city. |
| `stations` | Keep only these weather station codes: `["CLINYC", "CLIORD"]`. |
| `stormNames` | Keep only these named storms: `["Yolanda"]`. |
| `metrics` | Keep only certain market types (`high_temp`, `rain`, `storm`, …). |
| `minStrike` / `maxStrike` | Strike window in the market's own unit: °F for temperature, inches for rain, category for hurricanes. A market is kept when **any part of its range** overlaps the window, so open-ended markets like "below 79" are not silently dropped. |
| `dateFrom` / `dateTo` | Weather-date window, `YYYY-MM-DD`. This is the date the market is *about*, not its close date. |
| `minVolume` | Drop markets that have traded less than this. The most effective single lever for cutting a run down to markets anyone is actually trading. |
| `minOpenInterest` | Drop markets with fewer open contracts than this. |
| `minProbability` / `maxProbability` | Implied-probability window, `0`–`1`. `0.4`–`0.6` finds near coin-flips; `maxProbability: 0.05` finds longshots. |
| `results` | Settled modes only. Keep only `yes`, `no` or `scalar` outcomes. |

```jsonc
// Every Chicago rain market anyone is actually trading
{ "mode": "live", "cities": ["Chicago"], "metrics": ["rain"], "minVolume": 100 }

// Near coin-flips across every weather market
{ "mode": "live", "minProbability": 0.4, "maxProbability": 0.6 }

// New York highs between 70 °F and 80 °F this week
{ "mode": "live", "cities": ["New York City"], "minStrike": 70, "maxStrike": 80 }
```

***

### Scheduling recipe

Kalshi's public data is cached for 15 seconds, so polling faster than that returns
identical bytes. A sensible schedule:

- **Prices:** hourly, `mode: live`, with a `seriesFilter` covering only the cities
  you trade. A three-series run is about 10 requests and 60 rows.
- **Results:** once daily after settlement, `mode: settled` with `incremental: true`.
  The first run fetches your lookback window; every run after that fetches only
  what is new, and finishes in seconds.

Pipe the dataset straight into Google Sheets, S3 or a webhook using Apify's
built-in integrations, no extra code.

***

### What makes this different

**Storm markets are covered.** Hurricanes, tropical storms and tornadoes are the
single largest bloc of open weather markets on Kalshi. They carry a storm name and
an ocean basin rather than a city, so tools built around "city + temperature" return
empty columns for all of them. This one parses the storm name, basin, category,
wind threshold and season window.

**Multi-city rain markets are decomposed.** Kalshi packs 22 cities into a single
rain event, and the city appears only on the individual market. Rows come back with
the right city and station on each.

**The observed temperature is recovered.** Over half of Kalshi's archived settled
markets have an empty observed-value field. Because a daily temperature ladder has
exactly one winning bracket, the real reading can be bounded from it. The actor
fills `observed_value_min` and `observed_value_max`, and marks whether the value was
reported or derived. That lifts observed-value coverage on a five-year New York pull
from 45% to 90%.

**Five years of history joins cleanly.** Kalshi's rules wording changed over time
("Central Park, New York" in 2022, "New York City (CLINYC)" in 2026), and its tickers
were renamed (`HIGHNY` → `KXHIGHNY`, `KXLOW` → `KXLOWT`). `city` keeps the published
text; `city_canonical` gives one name you can group by across the whole archive.

**Nothing is hardcoded.** Series, cities and stations are all discovered at runtime.
Kalshi has renamed tickers twice and moved its settlement source once; a scraper with
a baked-in city list breaks on the next change.

**Strike direction is read, not guessed.** A `-T79` ticker is a *less-than* market
and a `-T86` is a *greater-than* one. The letter means nothing. Bounds come from the
payload, with explicit `floor_inclusive` / `cap_inclusive` flags.

***

### Output fields

**Identity**: `ticker`, `event_ticker`, `series_ticker`, `market_title`,
`event_title`, `series_title`, `category`, `schema_version`

**Location**: `city`, `city_canonical`, `station`, `station_kind`, `city_source`,
`region`, `region_kind`

**Storms**: `storm_name`, `storm_basin`, `storm_metric`, `storm_category`,
`storm_wind_threshold_mph`, `season_start`, `season_end`

**Classification**: `weather_metric`, `unit`, `metric_source`, `frequency`, `tags`,
`market_date`, `mutually_exclusive`

**Strike**: `strike_type`, `floor_strike`, `cap_strike`, `floor_inclusive`,
`cap_inclusive`, `bracket_label`, `yes_sub_title`, `no_sub_title`

**Prices**: `yes_bid`, `yes_ask`, `no_bid`, `no_ask`, `last_price`,
`previous_price`, `previous_yes_bid`, `previous_yes_ask`, `yes_bid_size`,
`yes_ask_size`, `implied_probability_yes`

**Size**: `volume`, `volume_24h`, `open_interest`, `liquidity`, `notional_value`

**Settlement**: `status`, `result`, `settlement_value`, `settlement_ts`,
`observed_value`, `observed_value_min`, `observed_value_max`, `observed_value_source`

**Times**: `open_time`, `close_time`, `expiration_time`, `expected_expiration_time`,
`can_close_early`, `created_time`, `updated_time`, `scraped_at`

**Optional**: `orderbook_yes_bids`, `orderbook_no_bids`, `orderbook_best_yes_bid`,
`orderbook_best_no_bid`, `orderbook_yes_ask`, `orderbook_yes_ask_size`,
`orderbook_spread`, `recent_trades`

**Raw**: `rules_primary`, `rules_secondary` are passed through untouched, so you can
always check the parsing against the source.

***

### Notes on accuracy

- Prices are **dollars per contract**, 0.0000 to 1.0000. A missing quote is `null`,
  never `0`.
- Volumes and sizes can be fractional. They are numbers, not strings.
- `result` can be `yes`, `no` or `scalar`. `scalar` means a voided or partial
  settlement. Do not assume every settled market pays 0 or 1.
- Settled markets can later be `disputed` or `amended`. `updated_time` is included
  so you can upsert rather than append.
- `city_source` and `observed_value_source` tell you where each derived value came
  from, so you can filter to only what was read directly from the payload.
- Order books are bid-only on both sides. The `orderbook_yes_ask` field is derived
  as `1 − best_no_bid`, and it matches Kalshi's own quote on every market checked.

***

### Schema stability & versioning

Every row carries `schema_version`. Fields will be added, never silently renamed or
removed within a major version. Kalshi replaced its integer-cent price fields with
decimal-string dollar fields in 2026 and broke most tools in this niche; this one
reads the current format and fails loudly rather than emitting zeros.

***

### Compliance

This is an **unofficial** tool. It is not affiliated with, endorsed by, or connected
to Kalshi in any way. "Kalshi" is used only to describe what the data is about.

It reads Kalshi's public, unauthenticated market-data endpoints at a deliberately
conservative 2 requests per second with backoff and a circuit breaker. It does not
log in, place orders, or access any account data.

You are responsible for your own use of the data, including compliance with Kalshi's
terms. Nothing here is trading or investment advice.

***

### Changelog

**1.0**: First release. Live and settled modes, deep archive, storm and region
parsing, multi-city rain decomposition, observed-value recovery, order book depth,
incremental runs.

***

### Keywords

kalshi weather api, kalshi weather markets, kalshi temperature markets, KXHIGH data,
KXHIGHNY, KXLOWT, KXLOW, KXRAIN, KXHURCAT, kalshi hurricane markets, kalshi tornado
markets, kalshi rain markets, weather prediction market data, prediction market
scraper, kalshi scraper, kalshi api, temperature futures data, weather trading data,
kalshi settlement history, kalshi historical data, event contracts weather

# Actor input Schema

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

Open markets with live prices, settled markets with their final outcome and the observed temperature, or both.

## `seriesFilter` (type: `array`):

Which Kalshi series to fetch, e.g. KXHIGHNY, KXRAIN, KXHURCAT. Defaults to three representative series so a default run is fast and cheap. Clear this field entirely to fetch every weather series that currently has open markets, which takes about six minutes. Series are discovered at runtime, so new ones appear without an update.

## `metrics` (type: `array`):

Keep only these kinds of market. Leave empty for all.

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

Keep only markets for these cities, e.g. Chicago, New York City, Miami. Matching ignores case and punctuation, and works against both the city as Kalshi wrote it and its canonical name. Leave empty for every city.

## `regions` (type: `array`):

Keep only markets in these regions, e.g. Atlantic, Florida, United States. Useful for storm markets, which have a basin instead of a city.

## `stations` (type: `array`):

Keep only markets settled by these station codes, e.g. CLINYC, CLIORD.

## `stormNames` (type: `array`):

Keep only markets about these named storms, e.g. Yolanda.

## `minStrike` (type: `number`):

Lowest strike to keep, in the market's own unit: degrees Fahrenheit for temperature, inches for rain, category for hurricanes. A market counts if any part of its range falls inside the window, so open-ended markets like 'below 79' are kept.

## `maxStrike` (type: `number`):

Highest strike to keep, in the market's own unit.

## `dateFrom` (type: `string`):

Keep only markets dated on or after this day, as YYYY-MM-DD. This is the weather date, not the market close date.

## `dateTo` (type: `string`):

Keep only markets dated on or before this day, as YYYY-MM-DD.

## `minVolume` (type: `number`):

Drop markets that have traded less than this. The single most effective way to cut a run down to markets anyone is actually trading.

## `minOpenInterest` (type: `number`):

Drop markets with fewer open contracts than this.

## `minProbability` (type: `number`):

Keep markets priced at or above this implied probability, from 0 to 1. Use 0.4 with a maximum of 0.6 for near coin-flips.

## `maxProbability` (type: `number`):

Keep markets priced at or below this implied probability, from 0 to 1. Use 0.05 to find longshots.

## `results` (type: `array`):

For settled modes only: keep only markets that settled this way.

## `includeStorms` (type: `boolean`):

Hurricane, tropical storm and tornado markets are the largest bloc of open weather markets. They carry storm names and ocean basins instead of cities.

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

Adds resting bid levels on both sides, plus a derived yes-ask and spread. Batched, so it costs very few extra requests.

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

Adds recent executed trades per market. This costs one request per market and makes large runs much slower.

## `maxTradesPerMarket` (type: `integer`):

How many recent trades to attach when trades are enabled.

## `historyDays` (type: `integer`):

How far back to pull settled markets. Only applies to the settled modes.

## `includeHistoricalArchive` (type: `boolean`):

Kalshi keeps only about two months of settled markets on the live endpoint. Turn this on to also walk the archive, which reaches back years. Returns a lot of rows.

## `incremental` (type: `boolean`):

Remember the newest settlement seen and fetch only newer ones next time. Ideal for a scheduled run.

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

Safety cap on how many rows one run produces.

## `requestsPerSecond` (type: `integer`):

Politeness limit. The default is deliberately conservative; raising it risks the exchange throttling the run.

## Actor input object example

```json
{
  "mode": "live",
  "seriesFilter": [
    "KXHIGHNY",
    "KXRAIN",
    "KXHURCAT"
  ],
  "metrics": [],
  "cities": [
    "New York City",
    "Chicago"
  ],
  "regions": [],
  "stations": [],
  "stormNames": [],
  "results": [],
  "includeStorms": true,
  "includeOrderbook": false,
  "includeRecentTrades": false,
  "maxTradesPerMarket": 20,
  "historyDays": 7,
  "includeHistoricalArchive": false,
  "incremental": false,
  "maxResults": 5000,
  "requestsPerSecond": 2
}
```

# Actor output Schema

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

Every market matched by the run, with the city, weather station, storm name, strike bounds, prices and settled outcome parsed into their own columns.

# 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 = {
    "seriesFilter": [
        "KXHIGHNY",
        "KXRAIN",
        "KXHURCAT"
    ],
    "cities": [
        "New York City",
        "Chicago"
    ]
};

// Run the Actor and wait for it to finish
const run = await client.actor("utopicvision/kalshi-weather-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 = {
    "seriesFilter": [
        "KXHIGHNY",
        "KXRAIN",
        "KXHURCAT",
    ],
    "cities": [
        "New York City",
        "Chicago",
    ],
}

# Run the Actor and wait for it to finish
run = client.actor("utopicvision/kalshi-weather-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 '{
  "seriesFilter": [
    "KXHIGHNY",
    "KXRAIN",
    "KXHURCAT"
  ],
  "cities": [
    "New York City",
    "Chicago"
  ]
}' |
apify call utopicvision/kalshi-weather-markets-scraper --silent --output-dataset

```

## MCP server setup

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