# Prediction Market Odds API — Polymarket + Kalshi (`westerly_breaker/prediction-market-odds-api`) Actor

Search or look up Polymarket and Kalshi prediction markets and get one unified JSON schema back: prices, volume, liquidity, order books, and de-vigged fair probabilities (multiplicative/power method). No API key needed — built for research, arbitrage-scanning, and AI agents.

- **URL**: https://apify.com/westerly\_breaker/prediction-market-odds-api.md
- **Developed by:** [Daniel Posztos](https://apify.com/westerly_breaker) (community)
- **Categories:** AI, Agents, Automation
- **Stats:** 1 total users, 1 monthly users, 100.0% runs succeeded, 0 bookmarks
- **User rating**: No ratings yet

## Pricing

from $0.01 / 1,000 results

This Actor is paid per event and usage. You are charged both the fixed price for specific events and for Apify platform usage.

Learn more: https://docs.apify.com/platform/actors/running/actors-in-store#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 Odds API — Polymarket + Kalshi, De-vigged Fair Probabilities

**SEO title:** Prediction Market Odds API — Polymarket & Kalshi Scraper, extract de-vigged probabilities as JSON
**SEO description:** Search or look up Polymarket and Kalshi prediction markets and get one unified JSON schema back: prices, volume, liquidity, order books, and de-vigged fair probabilities (multiplicative/power method). No API key needed — built for research, arbitrage-scanning, and AI agents.

One actor, one schema, two prediction market platforms. Query by keyword or by exact market ID, get back current prices AND the de-vigged "fair" probability for every outcome — the overround-adjusted number you actually want if you're comparing markets, building a model, or looking for mispriced longshots.

### Use with AI agents (MCP / LangChain)

Built to be called as a **tool by an AI agent**, not just from the Console. The tool definition (name,
description, arguments) is generated automatically from this actor's title and input schema, so an LLM
can pick it and fill the arguments correctly.

**MCP (Claude Desktop / Cursor / VS Code)** — add to your MCP client config:

```json
{
  "mcpServers": {
    "apify": {
      "url": "https://mcp.apify.com",
      "headers": { "Authorization": "Bearer <APIFY_TOKEN>" },
      "actors": ["westerly_breaker/prediction-market-odds-api"]
    }
  }
}
```

**LangChain:**

```python
from langchain_apify import ApifyActorsTool

odds = ApifyActorsTool("westerly_breaker/prediction-market-odds-api")  # APIFY_TOKEN from env
result = odds.invoke({"query": "bitcoin", "max_markets": 5})
```

**Why agents like it:** no API key needed, one unified schema across Polymarket and Kalshi, and
de-vigged fair probabilities computed for you — exactly the shape a trading/research agent wants. Set
`include_orderbook: false` (default) to keep responses small and cheap; turn it on only when depth matters.

**Standby (low-latency HTTP API, no cold start)** — Standby mode is enabled for this actor, so you can
skip the batch run/dataset round-trip entirely and call it as a plain HTTP API. Authenticate with your
own Apify token (`Authorization: Bearer <APIFY_TOKEN>`, or `?token=<APIFY_TOKEN>`):

```bash
curl -H "Authorization: Bearer <APIFY_TOKEN>" \
  "https://westerly-breaker--prediction-market-odds-api.apify.actor/search?query=bitcoin&max_markets=5&include_orderbook=false"
```

or `POST` the same fields as a JSON body (identical shape to the Console/API input):

```bash
curl -X POST "https://westerly-breaker--prediction-market-odds-api.apify.actor/search" \
  -H "Authorization: Bearer <APIFY_TOKEN>" \
  -H "Content-Type: application/json" \
  -d '{"query": "bitcoin", "max_markets": 5, "include_orderbook": false}'
```

Both return `{"query": ..., "count": ..., "items": [...]}` directly in the HTTP response — no dataset,
no `Actor.push_data`. `GET /` is a free health check (`{"status": "ready", ...}`, never charged). Query
parameters are plain strings, coerced to the right type server-side (`max_markets=5` → `5`,
`include_orderbook=true` → `true`, `platforms=polymarket,kalshi` or repeated `platforms=` params → a
list); invalid input returns HTTP 400 with a speaking error message rather than failing a run. Each
returned market still costs exactly one `market-result` charge, same as the batch flow — a
budget-limited caller gets back fewer, fully-paid markets rather than a response it wasn't billed for
the tail of. When `include_orderbook` is true, an order book is only attached to a market once its own
`orderbook-snapshot` charge succeeds: on a tight budget you may get back a fully-paid market with no
`orderbook` field rather than an unpaid one, but a market is never dropped just because its order book
couldn't be paid for. The Standby endpoint above is live.

### Recipes

Copy-paste starting points — each is a single call that returns finished, de-vigged JSON.

**Scan for cross-platform mispricing (Polymarket vs Kalshi on the same theme).** Ask both platforms for
the same keyword, then compare `devigged_prob` for equivalent outcomes — a gap wider than both platforms'
combined fees is a candidate:

```bash
curl -H "Authorization: Bearer <APIFY_TOKEN>" \
  "https://westerly-breaker--prediction-market-odds-api.apify.actor/search?query=bitcoin&platforms=polymarket,kalshi&max_markets=40"
```

**Give a trading/research agent a fair-probability feed.** The de-vig is already done, so an LLM tool call
gets the number it should reason about (`devigged_prob`), not the vig-inflated headline price. Keep
responses small and cheap by leaving `include_orderbook` off until depth actually matters:

```python
from langchain_apify import ApifyActorsTool
odds = ApifyActorsTool("westerly_breaker/prediction-market-odds-api")
fair = odds.invoke({"query": "2028 election", "max_markets": 10})  # devigged_prob per outcome
```

**Pull one specific market by ID for monitoring.** Skip search entirely and poll exact markets (Polymarket
condition IDs / slugs, Kalshi tickers) on the low-latency Standby endpoint:

```bash
curl -X POST "https://westerly-breaker--prediction-market-odds-api.apify.actor/search" \
  -H "Authorization: Bearer <APIFY_TOKEN>" -H "Content-Type: application/json" \
  -d '{"market_ids": ["KXBTCD-26JUL0517-T52999.99"], "include_orderbook": true}'
```

### Why this exists

Polymarket (Gamma + CLOB APIs) and Kalshi (trade-api v2) both expose public, unauthenticated market data — but in two different shapes, with prices that still include the platform's own vig/overround. This actor:

1. queries both (or either) platform for markets matching a keyword, or fetches specific markets by ID,
2. normalizes them into one schema,
3. removes the vig with a standard de-vig method (multiplicative by default) so `devigged_prob` values for a market's outcomes actually sum to 1.0,
4. optionally attaches a top-of-book order book snapshot.

No scraping, no anti-bot risk, no login — both platforms' public market-data endpoints are called directly (over HTTPS, no browser).

### Input

| Field | Type | Default | Description |
|---|---|---|---|
| `query` | string | — | Free-text search keyword, e.g. `"bitcoin"`, `"2028 election"`. Use this OR `market_ids`. |
| `market_ids` | array of strings | `[]` | Exact market IDs to fetch directly: Polymarket condition IDs (`0x...`) or slugs, and/or Kalshi tickers (e.g. `"KXBTCD-26JUL0517-T52999.99"`). Mixing IDs from both platforms in one list is fine — each platform only matches its own IDs. |
| `platforms` | array of strings | `["polymarket", "kalshi"]` | Which platform(s) to query. |
| `include_orderbook` | boolean | `false` | Attach a top-10-level order book per market. Bills an extra event (see pricing) and roughly doubles request count. |
| `max_markets` | integer (1–200) | `20` | Total markets to return across all platforms combined. Split evenly between the requested platforms so one platform's abundance of matches (Polymarket tends to have far more markets matching a broad query than Kalshi) doesn't crowd out the other. |

You must provide `query` and/or `market_ids` — an actor input with neither fails immediately with a message telling you exactly that.

#### Worked example — input

```json
{
  "query": "bitcoin",
  "platforms": ["polymarket", "kalshi"],
  "include_orderbook": false,
  "max_markets": 5
}
```

#### Worked example — output (2 of 5 items, real data)

```json
[
  {
    "platform": "polymarket",
    "market_id": "0x4863841fee98ae432b657dbad973cd5562e7fa6bab5e1a725ebd833723c9493d",
    "question": "Will the price of Bitcoin be above $50,000 on July 5?",
    "outcomes": [
      { "name": "Yes", "price": 0.9995, "implied_prob": 0.9995, "devigged_prob": 0.9995 },
      { "name": "No",  "price": 0.0005, "implied_prob": 0.0005, "devigged_prob": 0.0005 }
    ],
    "volume": 132828.07,
    "liquidity": 126913.72,
    "close_time": "2026-07-05T16:00:00Z",
    "url": "https://polymarket.com/event/bitcoin-above-50k-on-july-5-2026",
    "scraped_at": "2026-07-05T14:57:29.520Z"
  },
  {
    "platform": "kalshi",
    "market_id": "KXBTCMAX150-25-26OCT31-149999.99",
    "question": "When will Bitcoin cross $100k again? — Before October 2026",
    "outcomes": [
      { "name": "Yes", "price": 0.055, "implied_prob": 0.055, "devigged_prob": 0.0524 },
      { "name": "No",  "price": 0.945, "implied_prob": 0.945, "devigged_prob": 0.9476 }
    ],
    "volume": 3678944.42,
    "liquidity": 1104224.42,
    "close_time": "2026-10-31T03:59:00Z",
    "url": "https://kalshi.com/markets/kxbtcmax150/kxbtcmax150-25-26oct31-149999.99",
    "scraped_at": "2026-07-05T14:57:29.522Z"
  }
]
```

With `include_orderbook: true`, each item additionally gets:

```json
"orderbook": {
  "bids": [{ "price": 0.98, "size": 357.0 }, "... up to 10 levels"],
  "asks": [{ "price": 0.99, "size": 45095.0 }, "... up to 10 levels"]
}
```

### Output schema

| Field | Type | Notes |
|---|---|---|
| `platform` | string | `"polymarket"` or `"kalshi"` |
| `market_id` | string | Polymarket condition ID (or slug if no condition ID) / Kalshi ticker |
| `question` | string | Human-readable market question |
| `outcomes[].name` | string | e.g. `"Yes"` / `"No"`, or a named outcome for multi-way markets |
| `outcomes[].price` | float (0–1) | Raw last/mid price, i.e. the platform's own implied probability including vig |
| `outcomes[].implied_prob` | float (0–1) | Same as `price` — kept as its own explicit field per the output contract |
| `outcomes[].devigged_prob` | float (0–1) or `null` | Vig-removed fair probability (multiplicative method); all outcomes of one market sum to 1.0. `null` only if de-vig math wasn't possible (e.g. a single-outcome market) |
| `volume` | float or `null` | Platform-reported traded volume |
| `liquidity` | float or `null` | Platform-reported liquidity/open interest |
| `close_time` | string (ISO 8601) or `null` | Market close/expiration time |
| `url` | string or `null` | Link to the market on the platform's site |
| `scraped_at` | string (ISO 8601) | When this actor fetched the data |
| `orderbook` | object, only if `include_orderbook: true` | `{bids: [{price, size}], asks: [{price, size}]}`, top 10 levels each |

### Pricing (pay-per-event)

| Event | Price | When it's charged |
|---|---|---|
| `market-result` | $0.003 | Once per market returned |
| `orderbook-snapshot` | $0.02 | Once per market, only when `include_orderbook: true` |

Plus Apify's own `apify-actor-start` synthetic event (first 5 seconds of compute free, platform-managed — never charged from this actor's code).

**Example:** 100 markets, no order books: `100 × $0.003 = $0.30`. 100 markets + order books on 10 of them: `$0.30 + 10 × $0.02 = $0.50`.

If a run's cost would exceed the `Max total charge USD` you set for it, the actor stops producing further results at exactly that point — it never crashes and never produces unbilled/"free" results past the limit.

### Error messages

- **No `query` and no `market_ids`:** *"Missing search criteria: provide either 'query' ... or 'market\_ids' ..."* — add one of the two fields.
- **Unsupported `platforms` value:** *"'platforms' contains unsupported value(s) \[...]. Supported values are: \['kalshi', 'polymarket']."* — fix the typo/remove the entry.
- **`max_markets` out of range:** *"'max\_markets' must be between 1 and 200, got N."* — pick a value in range.
- **Wrong type for any field** (e.g. `query` as a number, `market_ids` as a bare string instead of an array): the message states the expected type and shows a corrected example.
- **0 results with otherwise valid input:** not an error — the run succeeds, but the log has an explicit `WARNING: 0 results produced despite valid input (...)` line and the run's status message says so, so this is never a silent/invisible "nothing happened."

### Known limitations (documented, not hidden)

- **Kalshi has no public full-text market search endpoint.** `query` search against Kalshi resolves by substring-matching the query against the ~11k-entry `/series` catalog's titles/tickers (fetched once per run, cached in-memory for that run), then lists open markets for matching series. This is adequate for the actor's per-run cost/latency budget; a future improvement could cache the catalog in the key-value store across runs.
- **Order books are attached per-market, not per-outcome.** For binary Yes/No markets this is the whole picture (No is fully determined by Yes); for a multi-outcome market, only the first outcome's book is attached.
- **`market_ids` mixing platforms:** the input is a single flat list rather than per-platform lists, so an ID that doesn't belong to a requested platform simply yields nothing for that platform — this is intentional (see Input table), not an error.

### Data sources (public, no API key, no scraping)

- Polymarket Gamma API: `https://gamma-api.polymarket.com/public-search`, `/markets`
- Polymarket CLOB API: `https://clob.polymarket.com/book`
- Kalshi trade-api v2: `https://api.elections.kalshi.com/trade-api/v2/{series,events,markets}`

All are the same public, unauthenticated endpoints the platforms' own web UIs call — no login, no key, no ToS-risk scraping.

# Actor input Schema

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

Keyword(s) to search for across market questions/titles, e.g. "bitcoin", "2028 election", "Fed rate cut". Use this OR 'market\_ids' below — you don't need both. Leave empty if you already know the exact market ID(s) you want (use 'market\_ids' instead).

## `market_ids` (type: `array`):

Specific market identifiers to fetch directly instead of searching. Accepts Polymarket condition IDs (start with "0x...") or slugs (e.g. "bitcoin-above-50k-on-july-5-2026"), and/or Kalshi market tickers (e.g. "KXBTCD-26JUL0517-T52999.99"). Every requested platform tries every ID in this list; an ID that doesn't belong to a given platform simply returns nothing for that platform (not an error) — so it's fine to mix Polymarket and Kalshi IDs in one list when 'platforms' includes both. Leave empty and use 'query' to search instead.

## `platforms` (type: `array`):

Which prediction market platform(s) to query. Allowed values: "polymarket", "kalshi". Defaults to both. Set to just one value to skip the other platform entirely (faster, cheaper).

## `include_orderbook` (type: `boolean`):

If true, additionally fetches the top-of-book order book (up to 10 price levels per side) for every returned market. This bills one extra 'orderbook-snapshot' event per market (see README pricing) and roughly doubles the number of upstream HTTP requests — leave this false unless you specifically need bid/ask depth rather than just the last traded price.

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

Maximum number of markets to return in total, across all requested platforms combined. Must be between 1 and 200. Each market returned bills one 'market-result' event, so lowering this caps cost; raise it (up to 200) for broad research scans.

## Actor input object example

```json
{
  "query": "bitcoin",
  "market_ids": [],
  "platforms": [
    "polymarket",
    "kalshi"
  ],
  "include_orderbook": false,
  "max_markets": 20
}
```

# Actor output Schema

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

Unified prediction-market items (one per market) with de-vigged fair probabilities, stored in the run's default dataset.

# 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": "bitcoin"
};

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

# Run the Actor and wait for it to finish
run = client.actor("westerly_breaker/prediction-market-odds-api").call(run_input=run_input)

# Fetch and print Actor results from the run's dataset (if there are any)
print("💾 Check your data here: https://console.apify.com/storage/datasets/" + run["defaultDatasetId"])
for item in client.dataset(run["defaultDatasetId"]).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": "bitcoin"
}' |
apify call westerly_breaker/prediction-market-odds-api --silent --output-dataset

```

## MCP server setup

```json
{
    "mcpServers": {
        "apify": {
            "command": "npx",
            "args": [
                "mcp-remote",
                "https://mcp.apify.com/?tools=westerly_breaker/prediction-market-odds-api",
                "--header",
                "Authorization: Bearer <YOUR_API_TOKEN>"
            ]
        }
    }
}

```

## OpenAPI specification

Download the OpenAPI definition: https://api.apify.com/v2/actors/DylrnhIt6qqpLvshG/builds/fw8REOgF4YQc88Jzp/openapi.json
