Prediction Market Odds API — Polymarket + Kalshi avatar

Prediction Market Odds API — Polymarket + Kalshi

Pricing

from $0.01 / 1,000 results

Go to Apify Store
Prediction Market Odds API — Polymarket + Kalshi

Prediction Market Odds API — Polymarket + Kalshi

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.

Pricing

from $0.01 / 1,000 results

Rating

0.0

(0)

Developer

Daniel Posztos

Daniel Posztos

Maintained by Community

Actor stats

0

Bookmarked

1

Total users

1

Monthly active users

17 days ago

Last modified

Share

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:

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

LangChain:

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>):

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):

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=55, include_orderbook=truetrue, 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:

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:

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:

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

FieldTypeDefaultDescription
querystringFree-text search keyword, e.g. "bitcoin", "2028 election". Use this OR market_ids.
market_idsarray 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.
platformsarray of strings["polymarket", "kalshi"]Which platform(s) to query.
include_orderbookbooleanfalseAttach a top-10-level order book per market. Bills an extra event (see pricing) and roughly doubles request count.
max_marketsinteger (1–200)20Total 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

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

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

[
{
"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:

"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

FieldTypeNotes
platformstring"polymarket" or "kalshi"
market_idstringPolymarket condition ID (or slug if no condition ID) / Kalshi ticker
questionstringHuman-readable market question
outcomes[].namestringe.g. "Yes" / "No", or a named outcome for multi-way markets
outcomes[].pricefloat (0–1)Raw last/mid price, i.e. the platform's own implied probability including vig
outcomes[].implied_probfloat (0–1)Same as price — kept as its own explicit field per the output contract
outcomes[].devigged_probfloat (0–1) or nullVig-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)
volumefloat or nullPlatform-reported traded volume
liquidityfloat or nullPlatform-reported liquidity/open interest
close_timestring (ISO 8601) or nullMarket close/expiration time
urlstring or nullLink to the market on the platform's site
scraped_atstring (ISO 8601)When this actor fetched the data
orderbookobject, only if include_orderbook: true{bids: [{price, size}], asks: [{price, size}]}, top 10 levels each

Pricing (pay-per-event)

EventPriceWhen it's charged
market-result$0.003Once per market returned
orderbook-snapshot$0.02Once 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.