Polymarket and Kalshi Odds API: Prediction Markets Data
Get Polymarket and Kalshi sports prices through one API: odds, last price, bid/ask, volume and open interest in the same schema as sportsbook odds.
PulseScore serves Polymarket and Kalshi sports markets through the same REST and WebSocket API as its sportsbooks. Every selection carries decimal odds plus the exchange's own lastPrice, bestBid and bestAsk, so a prediction-market probability lines up with a bookmaker's price for the same match without a second integration.
This guide covers what the two exchanges return, how their prices turn into odds and probabilities, which fields tell you how much to trust a price, and a Python script that compares an exchange's view of a match with a sportsbook's no-vig line.
How do prediction markets differ from sportsbooks?
A sportsbook sets its own prices and builds a margin into them. On a prediction market, traders buy and sell contracts that pay 1 if an outcome happens and 0 if it does not, so the price of a contract is the market's probability for that outcome. Kalshi is a CFTC-regulated US exchange, and both it and Polymarket run order books that match buyers with sellers. For a data consumer that means three numbers per outcome instead of one: the last traded price, the best bid and the best ask.
What do the Polymarket and Kalshi endpoints return?
Both exchanges use the same paths as every other bookmaker in the API, under https://api.pulsescore.net/api/polymarket and https://api.pulsescore.net/api/kalshi:
| Path | Returns |
|---|---|
/{sport}/events | Upcoming pre-match events, paginated |
/{sport}/leagues | Leagues with their event counts |
/{sport}/leagues/:league/events | Every event in one league |
/{sport}/events/:eventId | One event with every market |
/live-events | In-play events, filter with ?sport= |
/live-events/sports | Sports with in-play events now |
/live-events/events/:eventId | One in-play event |
Neither exchange publishes a separate league feed, so /leagues is built from the events listed at that moment. Authentication is the same X-Secret header as everywhere else, list endpoints take page and limit (up to 30), and sport keys are the usual kebab-case ones such as soccer, tennis, table-tennis and american-football. What each exchange lists right now is on the sport availability page.
Kalshi also keeps finished events for 30 days at /api/kalshi/results, most recent first and up to 20 per page, with optional sport, league and team filters (exact names, case-insensitive). /results/sports lists the sports with stored results, /results/leagues?sport=soccer lists the exact league names the filter expects, and /results/events/:eventId returns one event. Kalshi settles outcomes on the exchange, so a result carries the event metadata and final open interest rather than a score.
What does a prediction-market selection look like?
Here is a trimmed Polymarket soccer event from /api/polymarket/soccer/events:
{
"eventId": "688669",
"league": "Russian Premier League",
"home": "PFK CSKA Moskva",
"away": "FK Baltika Kaliningrad",
"startTime": "2026-07-24T17:00:00.000Z",
"volume": 3173.35,
"volume24hr": 3000.36,
"liquidity": 105933.49,
"markets": [
{
"canonicalMarket": "MATCH_RESULT",
"period": "FULL_TIME",
"marketId": "ml_rus-csk-bal-2026-07-24",
"isActive": true,
"volume": 2138.68,
"liquidity": 21425.63,
"selections": [
{ "canonicalOutcome": "HOME", "odds": 2.1277, "lastPrice": 0.455, "bestBid": 0.44, "bestAsk": 0.47, "isActive": true },
{ "canonicalOutcome": "DRAW", "odds": 3.3333, "lastPrice": 0.295, "bestBid": 0.29, "bestAsk": 0.3, "isActive": true },
{ "canonicalOutcome": "AWAY", "odds": 3.8462, "lastPrice": 0.245, "bestBid": 0.23, "bestAsk": 0.26, "isActive": true }
]
}
]
}| Field | Where | Meaning |
|---|---|---|
odds | Both | Decimal price to buy now |
lastPrice | Both | Last traded price, 0 to 1 |
bestBid, bestAsk | Both, when quoted | Top of the order book |
volume, volume24hr | Polymarket | Traded volume, total and 24 hours |
liquidity | Both | Depth resting in the book |
openInterest | Kalshi | Open interest per market and event |
marketTicker | Kalshi | The exchange's market ticker |
Polymarket markets carry a marketId and Kalshi markets a marketTicker. The Polymarket API and Kalshi API pages show complete sample responses.
How do prices become odds and probabilities?
A price of 0.47 means paying 0.47 for a contract that returns 1, so the outcome is priced at a 47% chance and the decimal odds are 1 / 0.47 = 2.13. PulseScore sets odds to 1 / bestAsk, the price of buying that outcome right now, and falls back to 1 / lastPrice when no ask is quoted. That keeps odds comparable with a bookmaker's price, which is also the price you can take now.
For an estimate of the probability itself, the mid-point of bid and ask is usually better than either side. In the example above the asks add up to 1.03, the bids to 0.96 and the mids to 0.995, close to the 1.00 a complete market should sum to. The odds math guide explains booksums, overround and no-vig odds in detail.
How much should you trust a price?
- Spread:
bestAsk - bestBid. A spread of 0.01 means buyers and sellers agree closely; a wide spread means the mid is a rough guess and the last trade may sit anywhere inside it. - Liquidity and volume: on Polymarket,
liquidityis the depth resting in the book andvolumeandvolume24hrare the amounts traded, on the event and per market. A price nobody trades is a weak signal. - Open interest: on Kalshi,
openIntereston each market and summed on the event shows how much is still held in its contracts, andliquidityon the event shows the depth behind the quotes. - Staleness:
lastPriceis the most recent trade, which can be hours old on a quiet market. Prefer the mid when both sides are quoted.
How do you compare an exchange with a sportsbook?
Both exchanges use the same canonical markets as every sportsbook in the API, so a Polymarket MATCH_RESULT lines up with MATCH_RESULT on Betfair Sportsbook, Bet365 or any other bookmaker, and one API key covers all 60+ of them. The script below pulls one league from each source, matches fixtures by kick-off time and team names, removes the bookmaker's margin and prints the gap per outcome:
import difflib
from datetime import datetime, timedelta
from urllib.parse import quote
import requests
API = "https://api.pulsescore.net/api"
HEADERS = {"X-Secret": "YOUR_API_KEY", "Accept-Encoding": "gzip"}
def league_events(source, sport, league):
"""Every pre-match event in one league, markets included."""
url = f"{API}/{source}/{sport}/leagues/{quote(league, safe='')}/events"
response = requests.get(url, headers=HEADERS, timeout=15)
response.raise_for_status()
return response.json()["events"]
def kickoff(event):
return datetime.fromisoformat(event["startTime"].replace("Z", "+00:00"))
def normalize(name):
return " ".join(name.lower().replace("-", " ").replace(".", " ").split())
def similar(a, b):
return difflib.SequenceMatcher(None, normalize(a), normalize(b)).ratio()
def same_fixture(a, b, window=timedelta(minutes=30), threshold=0.6):
return (abs(kickoff(a) - kickoff(b)) <= window
and similar(a["home"], b["home"]) >= threshold
and similar(a["away"], b["away"]) >= threshold)
def match_result(event):
"""{outcome: selection} of the active full-time 1X2 market, or None."""
for market in event.get("markets", []):
if (market.get("canonicalMarket") == "MATCH_RESULT"
and market.get("period") == "FULL_TIME" and market.get("isActive")):
sels = {s.get("canonicalOutcome"): s for s in market.get("selections", [])
if s.get("isActive") and s.get("odds", 0) > 1}
if set(sels) == {"HOME", "DRAW", "AWAY"}:
return sels
return None
def exchange_probs(sels):
"""Bid/ask mid (or last trade) per outcome, scaled to sum to 1."""
raw = {}
for outcome, s in sels.items():
bid, ask = s.get("bestBid"), s.get("bestAsk")
if bid is not None and ask is not None and 0 < bid <= ask < 1:
raw[outcome] = (bid + ask) / 2
else:
raw[outcome] = s.get("lastPrice") or 1 / s["odds"]
total = sum(raw.values())
return {k: v / total for k, v in raw.items()}
def no_vig_probs(sels):
"""Sportsbook implied probabilities with the margin removed."""
raw = {k: 1 / s["odds"] for k, s in sels.items()}
total = sum(raw.values())
return {k: v / total for k, v in raw.items()}
# Copy each league name exactly as that source's /soccer/leagues lists it.
exchange = league_events("polymarket", "soccer", "English Premier League")
book = league_events("betfair-sb", "soccer", "English Premier League")
for event in exchange:
twin = next((b for b in book if same_fixture(event, b)), None)
ex = match_result(event)
bk = match_result(twin) if twin else None
if not ex or not bk:
continue
p, f = exchange_probs(ex), no_vig_probs(bk)
print(f"{event['home']} v {event['away']}")
for outcome in ("HOME", "DRAW", "AWAY"):
gap = (p[outcome] - f[outcome]) * 100
print(f" {outcome:<5} exchange {p[outcome]:6.1%} book {f[outcome]:6.1%} {gap:+.1f} pts")With an exchange quoting 0.40/0.42, 0.26/0.28 and 0.31/0.33 and a bookmaker at 2.30, 3.50 and 3.10, the output looks like this:
Fulham v Chelsea
HOME exchange 41.0% book 41.7% -0.7 pts
DRAW exchange 27.0% book 27.4% -0.4 pts
AWAY exchange 32.0% book 30.9% +1.1 ptsTwo requests cover a whole league, one per source, and rate limits apply per bookmaker, so this fits every plan including the free one at 500 requests a month. Swap polymarket for kalshi, or betfair-sb for any other bookmaker id, and nothing else changes. Event ids differ per source, which is why the script matches on names and kick-off: the 0.6 similarity threshold handles Fulham against Fulham FC, while pairs like Man Utd and Manchester United need an alias table.
Live prices: REST, WebSocket or Stream API
In-play markets come from /api/polymarket/live-events and /api/kalshi/live-events, or as a push feed on the PRO, MAX and ULTRA plans: wss://api.pulsescore.net/api/polymarket/ws/live?key=YOUR_API_KEY&sport=soccer (and the same path under kalshi) sends every in-play event of that sport about once a second, in the same schema as the REST responses. Both exchanges are also on the Stream API, which sends each board once and then only what changed.
Caveats before you call it an edge
- Thin markets: a small order can move a shallow book, so weigh comparisons by
liquidity,volume24hroropenInterest. - Stale trades: a last price can be old; use the mid when both sides are quoted and check the spread.
- Fees: the prices are order-book quotes and trades, with any exchange fees charged separately, so subtract them before comparing with a bookmaker's price.
- Resolution rules: check what a contract settles on, for example regular time or including extra time, before treating it as the same outcome as a bookmaker's market.
- Timing: compare prices fetched close together, and live prices only with live prices.
Frequently asked questions
Does PulseScore have a Polymarket API?
Yes. PulseScore serves Polymarket sports markets under /api/polymarket with the same paths and schema as its sportsbooks: pre-match events, leagues, single events, live events and a WebSocket feed on the PRO, MAX and ULTRA plans. Each selection carries decimal odds, lastPrice, bestBid and bestAsk, and events include volume, 24-hour volume and liquidity.
Does PulseScore have a Kalshi API?
Yes. Kalshi markets are served under /api/kalshi with decimal odds, lastPrice, bestBid and bestAsk per selection, open interest per market and event, live events, a WebSocket feed on the PRO, MAX and ULTRA plans, and a 30-day results archive at /api/kalshi/results.
How are prediction-market prices converted to decimal odds?
A contract price between 0 and 1 is the market's implied probability. PulseScore sets odds to 1 divided by the best ask, the price of buying the outcome now, and falls back to 1 divided by the last traded price when no ask is quoted. A best ask of 0.47 gives odds of about 2.13.
Can I compare Polymarket or Kalshi with sportsbook odds?
Yes. Both use the same canonical markets as every sportsbook in the API, so a MATCH_RESULT market on Polymarket lines up with MATCH_RESULT on Bet365 or Betfair Sportsbook. Remove the bookmaker's margin first, then compare its fair probabilities with the exchange's bid/ask mid. Event ids differ per source, so match fixtures by kick-off time and team names.
Do Polymarket and Kalshi prices include fees?
No. The prices are the exchanges' order-book quotes and last trades, and any trading fees are charged separately, so account for them before treating a price difference as an edge.
Keep reading
Implied Probability, Overround and No-Vig Odds Explained
Convert odds to implied probability, measure a bookmaker's overround and margin, remove the vig to get fair odds, and run the math in Python on live API data.
How to Build an Odds Comparison Tool with One API
Build an odds comparison tool in Node.js: pull one league from several bookmakers with one API key, match fixtures, show the best price and each book's margin.