# Prediction Market Data (Polymarket + Kalshi) (`inn_corp/prediction-market-data`) Actor

Fetch live prediction markets from Polymarket's public Gamma API and Kalshi's public markets API. One clean record per market: question, outcomes, prices, volume, liquidity, status, and URL. No account or API key required for either source.

- **URL**: https://apify.com/inn\_corp/prediction-market-data.md
- **Developed by:** [Inn Corp](https://apify.com/inn_corp) (community)
- **Categories:** AI, Automation, Developer tools
- **Stats:** 2 total users, 1 monthly users, 100.0% runs succeeded, 0 bookmarks
- **User rating**: No ratings yet

## Pricing

from $1.50 / 1,000 market retrieveds

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

## Prediction Market Data (Polymarket + Kalshi)

Pull live prediction markets from Polymarket's public Gamma API and Kalshi's
public markets API in one call. One clean record per market: question,
outcomes with current prices, volume, liquidity, end date, status, and a
working URL. Both sources are fetched with public endpoints; no account or
API key required for either.

### What it does

- Give it one or both sources (`polymarket`, `kalshi`, both by default).
- Filter by category (`politics`, `sports`, `crypto`, ...), by keyword
  against the question text, and by active-only vs. include closed/resolved.
- Get back up to `maxMarkets` records, combined across sources, newest
  matches first within each source's own ordering.
- Optionally pull a live top-of-book snapshot per market.

### Output example

Real records from test runs.

Polymarket:

```json
{
  "source": "polymarket",
  "marketId": "559651",
  "question": "Xi Jinping out before 2027?",
  "category": null,
  "outcomes": [
    { "name": "Yes", "price": 0.0525 },
    { "name": "No", "price": 0.9475 }
  ],
  "volume": 12639436.846033994,
  "liquidity": 165893.7477,
  "endDate": "2026-12-31",
  "status": "active",
  "url": "https://polymarket.com/event/xi-jinping-out-before-2027",
  "orderBook": null,
  "fetchedAt": "2026-08-25T03:42:41+00:00"
}
```

Kalshi, with `categories: ["politics"]`:

```json
{
  "source": "kalshi",
  "marketId": "KXNEXTDNCCHAIR-45-FSHA",
  "question": "Will Faiz Shakir be the next Chair of the Democratic National Committee?",
  "category": "Politics",
  "outcomes": [
    { "name": "Yes", "price": 0.0955 },
    { "name": "No", "price": 0.9045 }
  ],
  "volume": 248.38,
  "liquidity": 0.0,
  "endDate": "2045-01-08T15:00:00Z",
  "status": "active",
  "url": "https://kalshi.com/markets/kxnextdncchair-45-fsha",
  "orderBook": null,
  "fetchedAt": "2026-08-25T03:35:23+00:00"
}
```

A settled Kalshi market, showing the definite 0/1 outcome once resolved:

```json
{
  "source": "kalshi",
  "marketId": "KXG7LEADEROUT-45JAN01-GMEL",
  "question": "Will Prime Minister of Italy be the first to leave office?",
  "category": "World",
  "outcomes": [
    { "name": "Yes", "price": 0.0 },
    { "name": "No", "price": 1.0 }
  ],
  "status": "resolved"
}
```

`category` is `null` on Polymarket records unless you filter by `categories`
(Polymarket's market objects carry no native category field; see Honest
limits). It is populated on Kalshi records whenever the run walked events at
all, which happens whenever a category or keyword filter is set, or falls
back to whatever the unfiltered event walk turned up.

### Input

| Field | Meaning |
| --- | --- |
| `sources` | `polymarket`, `kalshi`, or both (default). |
| `categories` | Optional category filter, one per line. Polymarket: checked against its live tag list. Kalshi: matched against categories seen live on events (no fixed list exists; see Honest limits). |
| `keywords` | Optional keyword filter, one per line. Case-insensitive substring match against the question text, applied on this Actor's side. |
| `activeOnly` | Default `true`: only currently tradeable markets. Off also includes closed and resolved/settled markets. |
| `maxMarkets` | Cap on total records, combined across sources. Default 200, max 2000. |
| `includeOrderBook` | Off by default. Fetches a live top-of-book snapshot per market: 2 extra calls per Polymarket market (Yes and No are separate order-book tokens), 1 per Kalshi market. Meaningfully slows a run; leave off unless you need depth, not just the current price. |

### Data sources and how they were verified

- **Polymarket**: `gamma-api.polymarket.com` (markets, events, tags) and
  `clob.polymarket.com` (order book), both confirmed live to serve full
  market data with zero auth headers.
- **Kalshi**: the documented `trading-api.kalshi.com` host now returns HTTP
  401 with a message pointing to a new host. That new host,
  `api.elections.kalshi.com`, was confirmed live to serve markets, events,
  and order books with zero auth headers -- genuinely public read access,
  not a gap this Actor is papering over. If Kalshi ever locks that host
  down, this Actor is meant to fail loudly (an HTTP error in the log), not
  fall back to guessing.

### Honest limits

- **Neither source's server-side filters are trustworthy, verified live.**
  Polymarket's `search` query param and Kalshi's `category` query param are
  both silently ignored for values they don't recognize -- they quietly
  return the default unfiltered page instead of erroring or returning zero
  results. So keyword filtering always happens on this Actor's side, and
  category filtering is validated against live data before it's trusted:
  Polymarket tags one at a time against `/tags/slug/{slug}` (a real 200 vs.
  a 404), Kalshi categories against whatever actually turns up while
  walking events during the run.
- **Kalshi has no fixed category list.** An early version of this Actor
  shipped one hand-sampled from live data; it was already missing "Crypto"
  and "Transportation" within the same test session. Rather than ship
  another guess, a Kalshi category filter is matched against categories
  seen live during the run, and a filter that matches nothing logs the
  categories it did see so you can adjust.
- **Kalshi's `/markets` endpoint is mostly synthetic combo markets.** A live
  scan of 15,000 open Kalshi markets found only 64 that were a real,
  single-question market; the rest were auto-generated "multivariate"
  parlays across unrelated events (a market on "Bitcoin above $85k AND the
  Dodgers win" is not what this Actor sells). Those are filtered out
  unconditionally. Real markets are instead reached by walking Kalshi's
  events (which carry no such pollution) and pulling each event's own
  markets, which costs one extra call per event but is the only reliable
  path to clean data.
- **Kalshi keyword filtering checks the event's shared title AND each
  market's own title, and both directions can miss a real match.**
  Fetching every event's markets just to check their titles is what made an
  unscoped keyword search against thousands of events time out during
  testing, so a keyword is checked against the event's own title (free,
  already on the page) before its markets are fetched at all -- a keyword
  that appears only in one specific market's title, never in the shared
  event title, can be missed. The reverse also happens: a keyword can match
  the event's shared title but appear in none of that event's individual
  market titles, which also returns zero results for that event. Both
  directions are a real limitation of doing keyword matching without
  fetching every event's markets unconditionally. This does not apply to
  Polymarket, where keyword filtering already runs against every market's
  own question text with no prefilter.
- **Polymarket's classic offset pagination has a real ceiling around
  2,000-3,000 records, not just this Actor's own page-count cap.** A rare
  keyword or category combined with a high `maxMarkets` can hit Polymarket's
  own API limit (it starts rejecting deep offsets) before this Actor's
  internal safety cap ever engages. When that happens, the run keeps
  whatever it already found, logs a warning, and finishes cleanly rather
  than erroring -- so a very rare query may return fewer markets than
  `maxMarkets` asks for, not because nothing else exists, but because
  Polymarket's own pagination cannot reach it with this method.
- **Kalshi market URLs are constructed, not fetched.** Kalshi's website
  blocks scripted requests with bot protection, so `https://kalshi.com/markets/{ticker}`
  is built from Kalshi's documented routing pattern rather than confirmed
  by an actual page load. It was not possible to verify live.
- **Polymarket has no native category field.** Its market objects carry no
  category or tag list; the only way to attach one is to query by a tag
  you already know, which is what `categories` does. Run without a category
  filter and `category` is `null` on every Polymarket record -- not
  omitted, genuinely unknown.
- **Prices are a live snapshot, not a guarantee.** Polymarket outcome
  prices come straight from `outcomePrices`. Kalshi's Yes price is the
  midpoint of the live bid/ask when both are quoted, falling back to the
  last traded price when the book is empty, except once a market is
  settled, where Kalshi's own `result` field (a clean "yes" or "no") is
  used directly instead of stale quotes that would otherwise average out to
  a meaningless 0.5. Either way, a market can move the instant after you
  read it. This is public market data, not financial advice, and comes
  with no guarantee of real-time accuracy.

### Fair pricing

Pay per market record returned (`market-retrieved`) and per completed
fetch-and-filter operation (`query-processed`, charged once per source that
finishes a run, success or an empty match, not on a request failure), once
pay-per-event pricing is enabled. No subscription.

# Actor input Schema

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

Which prediction markets to pull from. Both are fetched with public, keyless endpoints.

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

Optional category filter, one per line (e.g. "politics", "sports", "crypto"). For Polymarket this is checked against its live tag list one tag at a time; an unrecognized tag is skipped with a warning. Kalshi publishes no fixed category list, so a Kalshi category is matched case-insensitively against whatever categories actually turn up on live events during the run; if none match, the log reports the categories it did see so you can adjust. Leave empty for no category filter.

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

Optional keyword filter, one per line. Keeps markets whose question text contains any of these (case-insensitive). Applied on this Actor's side: neither source's search parameter reliably filters server-side, so matching pages are scanned until enough results are found, the source is exhausted, or (rare, Polymarket only) its own pagination limit is reached first -- see the README's Honest limits section.

## `activeOnly` (type: `boolean`):

When on (default), only currently tradeable markets are returned. Turn off to also include closed and resolved/settled markets.

## `maxMarkets` (type: `integer`):

Upper bound on total market records returned, across all selected sources combined.

## `includeOrderBook` (type: `boolean`):

Fetch a live top-of-book snapshot (bids/asks) for every returned market. This costs one extra API call per market (two for Polymarket, which prices Yes and No as separate tokens), so it meaningfully slows a run and is off by default. Leave off unless you specifically need depth, not just the current price.

## Actor input object example

```json
{
  "sources": [
    "polymarket",
    "kalshi"
  ],
  "categories": [
    "politics",
    "sports"
  ],
  "keywords": [
    "bitcoin",
    "election"
  ],
  "activeOnly": true,
  "maxMarkets": 200,
  "includeOrderBook": false
}
```

# Actor output Schema

## `markets` (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": [
        "polymarket",
        "kalshi"
    ]
};

// Run the Actor and wait for it to finish
const run = await client.actor("inn_corp/prediction-market-data").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": [
        "polymarket",
        "kalshi",
    ] }

# Run the Actor and wait for it to finish
run = client.actor("inn_corp/prediction-market-data").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": [
    "polymarket",
    "kalshi"
  ]
}' |
apify call inn_corp/prediction-market-data --silent --output-dataset

```

## MCP server setup

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

```

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/yzdHZKqOloWjH5guc/builds/apYIH4hy104Qhfsc5/openapi.json
