# Polymarket & Kalshi Scraper - Prediction Market Odds API (`michael_b/prediction-market-odds`) Actor

Polymarket and Kalshi prediction market odds in one table, no API key: chance in percent, volume, close date, result, link and 24h change per market. Search both sites, compare Polymarket vs Kalshi (gap in points), browse Fed, election, sports and crypto markets, paste URLs, add price history.

- **URL**: https://apify.com/michael\_b/prediction-market-odds.md
- **Developed by:** [Michal Búci](https://apify.com/michael_b) (community)
- **Categories:** AI, Agents
- **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 returneds

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

## Polymarket & Kalshi Scraper: Prediction Market Odds in One Table

Scrape live prediction market odds from Polymarket and Kalshi, the two largest prediction markets, into one table: the question, the chance in percent of the side named next to it (usually Yes; a team name on match markets), volume, close date, the final result once known, a link, and the 24h change where the site publishes it. Same columns for both sites, so you can plug the output into a script, a spreadsheet or an AI agent without reconciling two formats. Search both sites at once, browse the most-traded markets by topic, or paste market URLs to fetch exactly those. Read-only public data, no account or key needed.

One row per yes/no market. A question with several possible answers (who wins the election, what the Fed does, a game's side bets) becomes several rows, one per answer, all sharing the same `eventTitle`. The chance always refers to the side named in `outcomeYes`, which sits right next to it.

### Polymarket API and Kalshi API without a key

Data comes from the same public market-data feeds the two sites' own pages use, read at run time, so the numbers are as fresh as the page and the chance is the one the page shows (Polymarket's displayed price, Kalshi's last trade; settled markets 100 or 0). No accounts, no keys, no trading and no rate limits for you to manage: read-only market data. Every row links to the market's page for verification.

### What data you get from Polymarket and Kalshi

| Column | Meaning |
|--------|---------|
| `question`, `outcomeYes`, `probability`, `change24h` | the question, the side the chance refers to, the chance in percent, and how many points it moved in the last 24 hours (blank on markets listed less than a day ago; Polymarket's page arrow covers a longer window) |
| `venue`, `eventTitle`, `category`, `endDate`, `url` | which site, the question group, topic, when trading closes, and the link |
| `outcome`, `outcomeNo`, `eventMarketCount`, `status` | answer label within its group, the opposite side, how many answers the group has on the site (more than the rows you got = the per-question cap cut some), open / closed / resolved |
| `volume`, `volume24h`, `openInterest`, `liquidity`, `yesBid`, `yesAsk` | size and order book. `liquidity` fills on Polymarket, `openInterest` on Kalshi, so each is blank half the time; for one "how big is this" number use `volume` |
| `resolvedOutcome`, `resolvedAt` | the winning side and when the result was posted (settled markets only) |
| `marketId`, `eventId`, `description`, `createdAt` | permanent ids (paste back into `marketIds`), the rules text (shortened), listing date |
| `matched*`, `probabilityGap`, `matchScore` | only with "Compare Polymarket vs Kalshi" on |
| `priceHistory` | only with "Add daily price history" on |

Example rows from a search for "fed decision" with comparison on:

| question | outcomeYes | probability | change24h | venue | matchedProbability | probabilityGap |
|---|---|---|---|---|---|---|
| Will the Fed increase interest rates by 25 bps after the October 2026 meeting? | Yes | 68.5 | 4.0 | polymarket | 70.0 | 1.5 |
| Will the Federal Reserve Hike rates by 25bps at their October 2026 meeting? | Yes | 70.0 | 4.0 | kalshi | 68.5 | 1.5 |
| Will there be no change in Fed interest rates after the October 2026 meeting? | Yes | 30.5 | -3.0 | polymarket | 31.0 | 0.5 |

### Search, browse or look up Polymarket and Kalshi markets

**Browse** (empty input): the 25 markets with the most trading in the last 24 hours on each site, interleaved so both sites are on the first screen. On a match day that is mostly games; set `sortBy: "volume"` for the big standing questions (Fed, elections, Bitcoin). Add a topic (`category`: politics, elections, sports, crypto, economics, finance, science\_tech, entertainment, weather, world, health) to narrow it down.

**Search** (`query`): e.g. `"fed decision"`, `"bitcoin 100k"`, `"super bowl"`. Results are ordered by trading volume, not by best match, so add a word like "october" to narrow down. Every word must appear somewhere in the question, its group or its answer, in any order. Common aliases count: BTC = Bitcoin, 100k = 100,000, fed = Federal Reserve, Super Bowl = Pro Football Champion (both sites renamed it).

**Look up** (`marketIds`): paste the page URLs from polymarket.com or kalshi.com, one per line, to fetch exactly those markets. A page with several answers returns all of them (up to 100, most traded first). Kalshi tickers (`KXFED-26SEP-T3.75`, or the event ticker `KXFED-26SEP` for the whole group) and the Polymarket `0x…` ids from the `marketId` column also work, including settled markets. Ids that can't be found come back as free `lookup_failed` notice rows.

### Resolved Polymarket and Kalshi markets with outcomes

Add `status: "resolved"` to browse or search and you get settled markets with the winning side in `resolvedOutcome`, `probability` at 100 for the winner and 0 for the rest, and the time the result was posted, most recent first. Pair it with `includePriceHistory` to see what the market said before the result. Handy for checking how often the market was right.

### Compare Polymarket vs Kalshi odds on the same question

Only works on the rows a run returns, so search for a phrase that appears on both sites (e.g. `"fed decision"` or `"2028 democratic nominee"`), then turn on `matchAcrossVenues`: the actor pairs markets that ask the same question on both sites and writes the other site's id, question and chance plus the gap in points onto both rows. For multi-answer questions raise `maxMarketsPerEvent` first, since only returned rows can pair. Matching is strict on purpose: the numbers (cutoffs, basis points, years) and the direction (cut / hike / hold) must agree, topics must agree, close dates must be within a week, and the cleaned-up wording must still be nearly the same. Sibling markets that differ by one number never pair. Unmatched rows are left blank rather than guessed. The gap is how far the two sites disagree, in points.

### Polymarket and Kalshi historical odds (daily price history)

Turn on `includePriceHistory` for a `priceHistory` column holding one chance per day for the last `historyDays` days (default 30), so you see the trend and not just today's move. On open markets the last point is today so far and equals `probability`. It is a nested list, so in a CSV export it is one cell of text per row; use the JSON export or a script to chart it. Blank (and not charged) on markets listed less than a day ago.

### Polymarket and Kalshi scraper input parameters

All optional. `{}` returns the 25 most-traded open markets per site.

| Field | Default | Notes |
|-------|---------|-------|
| `query` | `""` | search phrase; empty = browse |
| `marketIds` | `[]` | URLs, tickers or ids to fetch exactly (ignores everything below except the extras and the rules-text length) |
| `venues` | both | `["polymarket"]`, `["kalshi"]` or both |
| `category` | `all` | topic list above |
| `status` | `open` | `open` or `resolved` |
| `sortBy` | `volume24h` | `volume24h` / `volume` / `liquidity` / `endingSoon` / `newest` |
| `limit` | `25` | rows per site, up to 500 |
| `endingWithinDays` | `0` | only markets closing within N days |
| `matchAcrossVenues` | `false` | compare Polymarket vs Kalshi |
| `includePriceHistory` / `historyDays` | `false` / `30` | daily price history |
| `maxMarketsPerEvent` | `3` | answers shown per question, most traded first; `eventMarketCount` shows how many exist (advanced) |
| `minVolume` | `0` | skip thin markets (advanced) |
| `maxDescriptionChars` | `150` | 0 drops the rules text (advanced) |

### Why some Kalshi parlays and sports side bets are missing

Kalshi combo bets (parlays) are always left out, and so are Polymarket's reserved placeholder answers ("Team H", "Other" with no trading), which its pages hide too. In browse and search, a game with 100 side bets or an election with 50 candidates shows only its 3 most-traded answers by default (raise "Answers shown per question" under Advanced to see all; a pasted URL always returns the whole group). In browse mode, games that already finished but whose result isn't posted yet (price at 0 or 100, close time passed) are skipped; in search and lookup you get whatever you asked for.

### Error rows (free)

If something goes wrong you get one free row with a `type` and a `message` instead of data: `no_results` (nothing matched, or one site matched nothing while the other did), `invalid_input` (the run continued with defaults), `lookup_failed` (one per id not found), `venue_error` (one site failed; the other site's rows are still returned) or `charge_limit_reached` (your spending cap cut the list). Error rows are never charged.

### How much does it cost to scrape Polymarket and Kalshi

You pay per row (Apify pay-per-event): **$0.002 per market row** and **$0.003 extra per row that gets price history**. The default run (50 rows) is about $0.10 and finishes in about 5 seconds; 500 rows per site with 30-day history is about $5 and takes a few minutes. Error rows are free.

### Prediction market data for AI agents (MCP server)

Call it from Claude, ChatGPT or any MCP-compatible agent through the [Apify MCP server](https://apify.com/apify/actors-mcp-server):

```bash
npx @apify/actors-mcp-server --tools michael_b/prediction-market-odds
```

Questions this answers in one call:

- "What does the market expect from the next Fed meetings, and do Polymarket and Kalshi agree?" → `query: "fed decision"`, `matchAcrossVenues: true`
- "What's the market pricing for the 2028 nominees?" → `query: "2028 nominee"`, `category: "elections"`
- "What gets decided this week in politics?" → `category: "politics"`, `endingWithinDays: 7`
- "How has the Bitcoin $100k market moved this month?" → `query: "bitcoin 100k"`, `includePriceHistory: true`
- "What were the results of last month's economics markets?" → `category: "economics"`, `status: "resolved"`

Pairs well with [economic-calendar-fed-watch](https://apify.com/michael_b/economic-calendar-fed-watch), [stock-earnings-calendar](https://apify.com/michael_b/stock-earnings-calendar) and [finviz-ticker-news](https://apify.com/michael_b/finviz-ticker-news) for the "why" behind a move in the odds.

# Actor input Schema

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

Words to search for on both sites, e.g. "fed decision", "bitcoin 100k", "super bowl". Results are ordered by trading volume (see Sort by), not by best match, so add a word like "october" to narrow down. Not case-sensitive; every word must appear in the question, its group title or its answer label, in any order. Common aliases count: BTC = Bitcoin, 100k = 100,000, fed = Federal Reserve, Super Bowl = Pro Football Champion. Leave empty to browse the most-traded markets instead.

## `marketIds` (type: `array`):

Paste the page URL from polymarket.com or kalshi.com, one per line, to fetch exactly those markets. A page with several answers returns all of them (up to 100, most traded first). Kalshi's short codes also work (KXFED-26SEP-T3.75 for one market, the shorter KXFED-26SEP for its whole group), as do the long 0x codes from the marketId column. This replaces search: query, sites, category, status, sort, limit, the filters and 'Answers shown per question' are ignored, and settled markets come back too. Ids that can't be found come back as free lookup\_failed rows. Up to 200 per run.

## `venues` (type: `array`):

Polymarket, Kalshi, or both (default). Not used when looking up specific markets.

## `category` (type: `string`):

One topic list for both sites (their own category names are translated to it). 'politics' and 'elections' are separate topics; use 'all' plus a search phrase to span both.

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

Open = still trading (default). A few rows may say 'closed' in the status column: trading just ended, result not posted yet. Resolved = finished markets with their result, most recently settled first, useful for checking past predictions.

## `sortBy` (type: `string`):

Order within each site (the two sites are then interleaved by rank). 'volume24h' = most traded in the last 24 hours (default, shows what's hot right now, on a match day that is the games). 'volume' = most traded ever (the big standing questions). 'liquidity' = most money waiting to trade (Polymarket; Kalshi uses open interest instead). 'endingSoon' = closest close date first. 'newest' = most recently listed. With status = resolved, volume24h and endingSoon both mean most recently settled first; the other options still apply.

## `limit` (type: `integer`):

Up to this many rows from each site, 1 to 500, after the filters are applied. 25 per site (50 rows) is a readable list. If you get exactly this many rows there were probably more; raise the number to see them. Rows from the two sites are interleaved by rank so both appear on the first screen.

## `endingWithinDays` (type: `integer`):

Only markets closing within this many days from now. 0 = no limit. Try 7 for "what gets decided this week". Ignored for resolved markets.

## `matchAcrossVenues` (type: `boolean`):

Pair up markets that ask the same question on both sites and add the other site's id, question and chance plus the gap in points to both rows. Pairs are only looked for among the rows this run returns, so use a search phrase that finds the question on both sites and raise 'Answers shown per question' for multi-answer questions. Matching is strict (numbers and direction must agree); unmatched rows are left blank. Needs both sites selected, otherwise you get a free invalid\_input row and unmatched rows.

## `includePriceHistory` (type: `boolean`):

Add `priceHistory`: one {date, probability} point per day for the last `historyDays` days, so you see the trend, not just today's move. Blank on markets younger than a day.

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

1 to 365 days of daily points when 'Add daily price history' is on. Longer than the market's life is clipped, not rejected.

## `maxMarketsPerEvent` (type: `integer`):

A question with many answers (election winner, Fed decision, a game's side bets) becomes many rows, one per answer, all sharing the same eventTitle. Only the most-traded answers are shown, up to this many per question; eventMarketCount on each row tells you how many the question has in total. 3 keeps "Fed decision" readable; set 100 to see every answer (do this before comparing sites on multi-answer questions), 1 for just the top answer. Not used when looking up specific markets.

## `minVolume` (type: `integer`):

Skip markets with less than this much traded ever. 10000 skips thin markets where the price means little.

## `maxDescriptionChars` (type: `integer`):

How many characters of the market's rules to keep in `description`. 0 drops the column (shorter rows if you only need the odds); 400 or more reads as a full sentence.

## Actor input object example

```json
{
  "query": "fed decision october",
  "marketIds": [
    "https://polymarket.com/event/democratic-presidential-nominee-2028",
    "KXPRESNOMD-28"
  ],
  "venues": [
    "polymarket",
    "kalshi"
  ],
  "category": "all",
  "status": "open",
  "sortBy": "volume24h",
  "limit": 25,
  "endingWithinDays": 0,
  "matchAcrossVenues": false,
  "includePriceHistory": false,
  "historyDays": 30,
  "maxMarketsPerEvent": 3,
  "minVolume": 0,
  "maxDescriptionChars": 150
}
```

# Actor output Schema

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

Dataset of market rows ranked per venue by sortBy. Free meta rows (type = no\_results / invalid\_input / lookup\_failed / venue\_error / charge\_limit\_reached) explain anything that could not be delivered.

# 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 = {
    "query": ""
};

// Run the Actor and wait for it to finish
const run = await client.actor("michael_b/prediction-market-odds").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 = { "query": "" }

# Run the Actor and wait for it to finish
run = client.actor("michael_b/prediction-market-odds").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 '{
  "query": ""
}' |
apify call michael_b/prediction-market-odds --silent --output-dataset

```

## MCP server setup

```json
{
    "mcpServers": {
        "apify": {
            "type": "http",
            "url": "https://mcp.apify.com/?tools=fetch-actor-details,michael_b/prediction-market-odds"
        }
    }
}
```

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/s3f3dBtBCYTLofuMo/builds/HGS46CwMhNkyycJdy/openapi.json
