Kalshi API vs Polymarket API: A Python Developer’s Comparison

Kalshi API vs Polymarket API - OddsPapi API Blog
How To Guides June 18, 2026

If you’re building anything on prediction-market data, you eventually hit the same fork in the road: Kalshi API vs Polymarket API. They look interchangeable from the outside — two venues quoting probabilities on the same events — but the moment you open an editor they could not be more different. Different auth models, different identifiers, different price formats, even a different number of APIs. Get the wrong mental model and your parser breaks on the first request.

This is a developer-to-developer breakdown: how each API is actually shaped, the gotchas that bite, and a shortcut that gives you both venues — already converted to decimal odds — from a single HTTP call.

The 30-Second Answer

Dimension Kalshi API Polymarket API
Venue type CFTC-regulated US exchange Onchain (Polygon), crypto-settled
Number of APIs One REST API Two: Gamma (catalog) + CLOB (order book)
Read auth None (public market data) None (public reads)
Market identifier ticker (human-readable) token_id (77-digit ERC-1155)
Price format USD strings, 0–1 (_dollars fields) Share-price strings, 0–1
Decimal odds? No — compute 1 / price No — compute 1 / price
Trading auth API key + RSA signature EIP-712 wallet sig + L2 headers

Neither API hands you decimal odds, and neither lets you pull both venues in one request. If all you want is to read prices side by side, skip to the OddsPapi section — both venues come back pre-converted from one endpoint. If you’re going deeper (trading, raw depth, settlement), read on.

Kalshi API: One REST API, Ticker-Based

Kalshi is a CFTC-regulated exchange, so the API feels like a traditional financial data feed. There’s a single base URL, everything is keyed off a human-readable ticker, and market data reads need no authentication.

Base URL: https://api.elections.kalshi.com/trade-api/v2

The ID hierarchy

Kalshi nests three levels, all string tickers:

  • Series — a recurring template (e.g. a weekly question type)
  • Event (event_ticker) — a single real-world question
  • Market (ticker) — one yes/no contract inside an event

Fetching markets and prices

import requests

KALSHI = "https://api.elections.kalshi.com/trade-api/v2"

# List open markets (no auth required for reads)
r = requests.get(f"{KALSHI}/markets", params={"status": "open", "limit": 100})
markets = r.json()["markets"]

m = markets[0]
print(m["ticker"])             # e.g. KXBTCD-25JUN06-T100000
print(m["title"])
print(m["yes_bid_dollars"])    # "0.6550"  -> implied probability of YES
print(m["yes_ask_dollars"])    # "0.6600"
print(m["no_bid_dollars"], m["no_ask_dollars"])
print(m["last_price_dollars"]) # last trade

Current-API gotcha: Kalshi moved its price fields to a _dollars suffix. The values are strings in the 0–1 range (so "0.6550" = a 65.5% implied probability). Older tutorials reference integer-cent fields like yes_bid; on the live v2 API you want yes_bid_dollars, yes_ask_dollars, no_bid_dollars, no_ask_dollars, and last_price_dollars. Cast before doing math.

The order book

ticker = m["ticker"]
ob = requests.get(f"{KALSHI}/markets/{ticker}/orderbook", params={"depth": 5}).json()

# Shape: {"orderbook_fp": {"yes_dollars": [[price, size], ...],
#                          "no_dollars":  [[price, size], ...]}}
book = ob["orderbook_fp"]
for price, size in book["yes_dollars"]:
    print(float(price), float(size))   # both are strings -> cast

Each level is a [price, size] pair, both strings. To turn a Kalshi share price into decimal odds: decimal = 1 / float(price). A YES contract at 0.655 is decimal 1.527.

Polymarket API: Two Services, Token-Based

Polymarket splits its read API in two — and confusing them is the #1 reason people’s first script fails. We covered this in depth in the Polymarket API deep dive; here’s the comparison-relevant version.

Service Base URL Purpose
Gamma https://gamma-api.polymarket.com Catalog: /events, /markets, metadata, discovery
CLOB https://clob.polymarket.com Order book: /book, /price, /midpoint

Step 1 — discover via Gamma

import requests, json

GAMMA = "https://gamma-api.polymarket.com"

r = requests.get(f"{GAMMA}/markets",
                 params={"active": "true", "closed": "false", "limit": 5})
m = r.json()[0]
print(m["question"])
print(m["conditionId"])          # 0x... on-chain market hash

# GOTCHA: these arrive as STRINGIFIED JSON, not native arrays
token_ids = json.loads(m["clobTokenIds"])   # double-parse required
outcomes  = json.loads(m["outcomes"])       # ["Yes", "No"]
prices    = json.loads(m["outcomePrices"])  # ["0.505", "0.495"]

The classic Polymarket trap: clobTokenIds, outcomes, and outcomePrices are strings containing JSON. If you iterate them directly you’ll loop over individual characters. Always json.loads() a second time.

Step 2 — get the book via CLOB

CLOB = "https://clob.polymarket.com"
token_id = token_ids[0]   # the 77-digit ERC-1155 position id

book = requests.get(f"{CLOB}/book", params={"token_id": token_id}).json()
# {"bids": [{"price": "0.50", "size": "..."}], "asks": [...], ...}

mid   = requests.get(f"{CLOB}/midpoint", params={"token_id": token_id}).json()  # {"mid": "0.505"}
price = requests.get(f"{CLOB}/price", params={"token_id": token_id, "side": "buy"}).json()

Every CLOB endpoint wants the token_id (a.k.a. asset_id), not the human-readable slug or the conditionId. Prices and sizes are strings in the 0–1 range; decimal odds = 1 / float(price).

Head-to-Head: The Differences That Actually Bite

You want to… Kalshi Polymarket
Find a market GET /marketsticker GET /markets (Gamma) → clobTokenIds
Get best bid/ask Read yes_bid_dollars / yes_ask_dollars off the market GET /price (CLOB) per token
Get full depth GET /markets/{ticker}/orderbook GET /book (CLOB) per token
Parse prices String 0–1 in _dollars fields String 0–1, double-json.loads the arrays
Both outcomes One market = one YES/NO pair One token per outcome (separate ids)
Decimal odds Compute 1/price Compute 1/price

The summary: Kalshi is one tidy REST API with readable tickers and prices attached to the market object. Polymarket is two APIs, opaque token IDs, and stringified JSON you have to parse twice. Neither gives you decimal odds, and — critically — neither lets you compare the two venues in a single call. You’re maintaining two clients, two ID schemes, two price-parsing paths, and writing your own odds converter, just to read a number both venues already know.

The Shortcut: Both Venues, Decimal Odds, One Call

This is where OddsPapi earns its place in the stack. It aggregates 350+ bookmakers and exchanges — including both Polymarket and Kalshi — behind one schema, and pre-converts every price into decimal, American, and fractional. One request returns both prediction markets plus the sharp sportsbook line (Pinnacle) for the same event, so you can compare apples to apples instead of stitching three feeds together.

Authentication

import requests

API_KEY = "YOUR_API_KEY"          # grab a free one at oddspapi.io
BASE = "https://api.oddspapi.io/v4"

# apiKey is a QUERY PARAMETER, never a header
params = {"apiKey": API_KEY}
print(requests.get(f"{BASE}/sports", params=params).status_code)  # 200

Pull both prediction markets for one fixture

Here’s a real, live example: the 2026 FIFA World Cup match Brazil vs Morocco. One call returns Kalshi, Polymarket, and Pinnacle on the Full Time Result market.

fixture_id = "id1000001666456928"   # Brazil vs Morocco, World Cup 2026

r = requests.get(f"{BASE}/odds", params={
    "apiKey": API_KEY,
    "fixtureId": fixture_id,
    "bookmakers": "kalshi,polymarket,pinnacle",
})
books = r.json()["bookmakerOdds"]

# 1X2 = market 101; outcomes 101=Home, 102=Draw, 103=Away
LABELS = {"101": "Brazil", "102": "Draw", "103": "Morocco"}

for slug in ("kalshi", "polymarket", "pinnacle"):
    outcomes = books[slug]["markets"]["101"]["outcomes"]
    quote = {LABELS[oid]: o["players"]["0"]["price"] for oid, o in outcomes.items()}
    print(f"{slug:11} {quote}")

Live output (captured June 2026):

Venue Brazil Draw Morocco Overround
Kalshi 1.639 4.167 5.882 2.01%
Polymarket 1.639 4.000 5.882 3.01%
Pinnacle (sharp benchmark) 1.641 3.840 5.950 3.79%

Already decimal, no 1/price needed. The interesting read: on this early World Cup line, both prediction markets quoted a tighter margin than Pinnacle — Kalshi’s 2.01% overround was the lowest of the three. (Early lines tighten by matchday; this is a snapshot of available prices, not a value claim.) The point is you got all three, normalized, from one request — no token IDs, no double-parsing, no converter.

Depth-of-book is there too

Because Kalshi and Polymarket are exchanges, OddsPapi exposes their ladder under exchangeMeta — the same depth you’d otherwise hit two separate APIs for:

brazil = books["polymarket"]["markets"]["101"]["outcomes"]["101"]["players"]["0"]
ladder = brazil["exchangeMeta"]   # null on sportsbooks; a ladder on exchanges

for level in ladder["back"][:3]:
    print(level["price"], "odds |", level["size"], "liquidity")
# 1.639 odds | 35456.27 liquidity
# 1.613 odds | 12432.34 liquidity
# 1.587 odds | 21985.0  liquidity

Parse defensively: exchangeMeta is null on sportsbooks and a {back: [...], lay: [...]} ladder on exchanges. Each level is {cents, price, size, limit}, best price first. Polymarket also ships a bookmakerLayOutcomeId (the opposite-side token, which drops straight into Polymarket’s CLOB); Kalshi omits it. Always check the field is truthy before indexing into it.

When to Use Which

If you’re… Use
Reading/comparing prices across both venues OddsPapi (one call, decimal odds, + Pinnacle anchor)
Backtesting prediction-market accuracy OddsPapi free historical odds
Placing trades on Kalshi Kalshi API directly (RSA-signed key)
Placing trades on Polymarket Polymarket CLOB directly (EIP-712 + L2 auth)
Raw onchain settlement / token mechanics Polymarket CLOB + conditionId

For trading you’ll always go direct — each venue’s order-placement layer is venue-specific and wallet/key bound. But for the 90% of work that’s reading — comparison, alerts, dashboards, modeling, arbitrage scanning — one OddsPapi call beats maintaining two clients and a converter.

FAQ

Is the Kalshi API free to use?

Yes for market data. Reads against /markets, /events, and /markets/{ticker}/orderbook need no authentication. Placing trades requires an account and an RSA-signed API key.

Is the Polymarket API free to use?

Yes for reads. Both Gamma (gamma-api.polymarket.com) and CLOB (clob.polymarket.com) serve catalog and order-book data unauthenticated. Trading requires an EIP-712 wallet signature plus L2 auth headers.

Do Kalshi and Polymarket return decimal odds?

No. Both return share/contract prices in the 0–1 range as strings (a 0.65 price = 65% implied probability). Convert with decimal = 1 / float(price). OddsPapi pre-converts to decimal, American, and fractional so you skip the math.

What’s the biggest difference between the two APIs?

Structure. Kalshi is one REST API keyed off readable ticker strings with prices on the market object. Polymarket is two services (Gamma for catalog, CLOB for books), keyed off 77-digit token IDs, and ships its arrays as stringified JSON you must parse twice.

Can I get both Kalshi and Polymarket from one API?

Yes — OddsPapi aggregates both (plus 350+ sportsbooks and exchanges) behind one schema. A single /v4/odds call returns both venues for the same event, already converted to decimal odds, with full depth-of-book under exchangeMeta.

Stop Maintaining Two Clients

Kalshi and Polymarket are great venues with awkward, mismatched APIs. If you’re trading, go direct. If you’re reading, comparing, or modeling, get your free OddsPapi key and pull both — plus the sharp line — from one endpoint.