A desk screening for gaps between Kalshi and Polymarket needs both quotes for the same event, plus whether the two contracts pay out on the same terms. Narwhal returns the quotes, each venue's rules text, settlement source and volume, and any rules flag in one response.
Read both venues on one event
A screen starts from one event that both venues price. The request below reads the Federal Reserve decision for December 2026 and returns every venue's quote for each outcome. The response is trimmed to the 25 bps hike outcome and leaves out the venue rules text, which the screen reads in the next step.
curl -s -H "Authorization: Bearer $NARWHAL_API_KEY" \
"https://api.narwhalapi.com/v1/predictions/scalar/usa-fed-2026-12"{
"event_id": "usa-fed-2026-12",
"question": "Federal Reserve rate decision, 9 December 2026",
"status": "trading",
"closes_at": "2026-12-09T18:59:00Z",
"resolves_at": "2026-12-09T19:00:00Z",
"as_of": "2026-11-03T03:09:00Z",
"stale": false,
"venues": [
{
"venue": "kalshi",
"url": "https://kalshi.com/markets/kxfeddecision/kxfeddecision-26dec",
"rules_url": "https://assets.kalshi.com/contract_terms/FEDDECISION.pdf",
"settlement_source": "Federal Reserve",
"weight": "0.0624",
"volume_24h_usd": "32792.43",
"open_interest_usd": "426866.37"
},
{
"venue": "polymarket",
"url": "https://polymarket.com/event/fed-decision-in-december-20260729232808632",
"settlement_source": "Federal Reserve",
"weight": "0.9376",
"volume_24h_usd": "665452.98"
}
],
"outcomes": [
{
"key": "hike_25",
"label": "Hike of 25 bps",
"lower": "12.5",
"upper": "37.5",
"probability": "0.7185",
"quotes": [
{
"venue": "kalshi",
"probability": "0.7073",
"market_ids": [
"KXFEDDECISION-26DEC-H25"
]
},
{
"venue": "polymarket",
"probability": "0.7192",
"market_ids": [
"111662129652828266889360374468657435151479595863413519836161861837506140152890"
]
}
]
}
]
}The event has one as_of, one closes_at and one resolves_at, so both quotes are taken at the same moment. For the hike outcome Kalshi quotes 0.7073 and Polymarket quotes 0.7192, a gap of 0.0119. Each venue entry carries its settlement_source and volume_24h_usd, and Kalshi also carries open_interest_usd. weight is how much each venue counts in the headline probability.
When the rules differ, read the gap as two contracts
A price gap says two numbers differ. It does not say two contracts pay the same. The recession question shows a case the response flags itself: the venue entry gets excluded_reason and a match note that says what differs.
curl -s -H "Authorization: Bearer $NARWHAL_API_KEY" \
"https://api.narwhalapi.com/v1/predictions/binary/usa-recession-2026"{
"event_id": "usa-recession-2026",
"question": "US recession by end of 2026?",
"as_of": "2026-11-03T03:09:00Z",
"stale": false,
"probability": "0.0550",
"quotes": [
{
"venue": "kalshi",
"probability": "0.0250",
"market_ids": [
"KXRECSSNBER-26"
]
},
{
"venue": "polymarket",
"probability": "0.0550",
"market_ids": [
"100379208559626151022751801118534484742123694725746262280150222742563282755057"
]
}
],
"venues": [
{
"venue": "kalshi",
"url": "https://kalshi.com/markets/kxrecssnber/kxrecssnber-26",
"rules_url": "https://assets.kalshi.com/contract_terms/NBERRECSS.pdf",
"settlement_source": "Bureau of Economic Analysis",
"excluded_reason": "rules",
"match": {
"differences": [
"rules"
],
"note": "Kalshi resolves on two negative GDP quarters only; Polymarket also counts an NBER recession call"
},
"volume_24h_usd": "125.68",
"open_interest_usd": "17994.22"
},
{
"venue": "polymarket",
"url": "https://polymarket.com/event/us-recession-by-end-of-2026",
"settlement_source": "Bureau of Economic Analysis; NBER",
"weight": "1.0000",
"volume_24h_usd": "4195.50"
}
]
}Kalshi quotes 0.0250 and Polymarket quotes 0.0550, a gap of 0.0300. The headline probability is 0.0550, which is Polymarket's quote alone, because Kalshi is excluded for rules. The note says Kalshi resolves on two negative GDP quarters only, while Polymarket also counts a recession call by the research bureau. The settlement sources differ too. A screen should drop that gap from its candidate list or tag it as a different contract.
Screen every outcome and write a log row
The script below reads a list of events, computes the Polymarket minus Kalshi gap for each outcome that both venues quote, and prints one row per outcome with the flags a desk would filter on.
import csv, json, os, sys, urllib.request
from decimal import Decimal
def get(path):
req = urllib.request.Request("https://api.narwhalapi.com" + path,
headers={"Authorization": "Bearer " + os.environ["NARWHAL_API_KEY"]})
with urllib.request.urlopen(req, timeout=30) as r:
return json.load(r)
def scan(shape, event_id):
e = get(f"/v1/predictions/{shape}/{event_id}")
venues = {v["venue"]: v for v in e["venues"]}
flags = [v["venue"] + ":" + ",".join(v.get("match", {}).get("differences", [])) for v in e["venues"] if v.get("excluded_reason")]
sources = {v["settlement_source"] for v in e["venues"]}
rows = e["outcomes"] if shape == "scalar" else [{"key": "yes", "quotes": e["quotes"]}]
for row in rows:
q = {x["venue"]: Decimal(x["probability"]) for x in row["quotes"]}
if len(q) < 2:
continue
yield [e["as_of"], e["stale"], event_id, row["key"], q["kalshi"], q["polymarket"],
q["polymarket"] - q["kalshi"], len(sources), ";".join(flags) or "none",
venues["kalshi"]["volume_24h_usd"], venues["polymarket"]["volume_24h_usd"]]
out = csv.writer(sys.stdout)
out.writerow(["as_of", "stale", "event", "outcome", "kalshi", "polymarket", "gap", "sources", "flags", "k_vol_24h", "p_vol_24h"])
for shape, event_id in [("scalar", "usa-fed-2026-12"), ("binary", "usa-recession-2026")]:
out.writerows(scan(shape, event_id))
as_of,stale,event,outcome,kalshi,polymarket,gap,sources,flags,k_vol_24h,p_vol_24h
2026-11-03T03:09:00Z,False,usa-fed-2026-12,cut_50_plus,0.0049,0.0035,-0.0014,1,none,32792.43,665452.98
2026-11-03T03:09:00Z,False,usa-fed-2026-12,cut_25,0.0146,0.0055,-0.0091,1,none,32792.43,665452.98
2026-11-03T03:09:00Z,False,usa-fed-2026-12,hold,0.2586,0.2530,-0.0056,1,none,32792.43,665452.98
2026-11-03T03:09:00Z,False,usa-fed-2026-12,hike_25,0.7073,0.7192,0.0119,1,none,32792.43,665452.98
2026-11-03T03:09:00Z,False,usa-fed-2026-12,hike_50_plus,0.0146,0.0188,0.0042,1,none,32792.43,665452.98
2026-11-03T03:09:00Z,False,usa-recession-2026,yes,0.0250,0.0550,0.0300,2,kalshi:rules,125.68,4195.50The five Fed rows share a single settlement source and no flag, so they are comparable on settlement. Each venue's five quotes add up to 1.0000, so the gaps add up to zero. The recession row has two settlement sources and a kalshi:rules flag. Rules wording still needs a human or a diff: Kalshi's text says a cancelled meeting resolves the no change outcome to Yes, while Polymarket's text rounds a change to a level not on the list up to the nearest 25 bps and resolves to the no change bracket if no statement is out by the end of the next scheduled meeting.
What to log on every scan
A scan is only useful later if it can be replayed. Log the inputs next to the gap.
| Field | Why it is kept |
|---|---|
as_of and stale | The time of both quotes, and whether the event data was current |
event_id and outcome key | The exact contract pair that was compared |
| Both quotes and the gap | The numbers, kept as strings or decimals |
market_ids of each quote | The venue contract behind each number |
excluded_reason and match | Why a gap is a different contract |
settlement_source of each venue | Whether the same body decides the result |
volume_24h_usd, open_interest_usd | How much trading stands behind a quote |
A hash of each rules_text | A rules edit between two scans becomes visible |
The Fed example shows why size is logged. On this scan Kalshi's 24 hour volume is 32,792.43 dollars and Polymarket's is 665,452.98, so the same gap stands on very different depth. The recession row has 125.68 on Kalshi.
Does a gap mean an order can be filled at those prices?
The API does not say so, and this page makes no claim about returns. A quote is a probability, not an order. The response has no fee schedule, no order book depth and no price a desk could trade at. The screen narrows a list of events to read.
What do the venue weights tell a desk?
weight is the share a venue has in the headline probability, and the weights add up to 1. On this scan they are 0.0624 for Kalshi and 0.9376 for Polymarket. Applied to the hold quotes they give 0.25335, against a headline of 0.2533. A heavy weight on one venue means the headline mostly follows that venue, so a screen that compares quotes should use the venue quotes and not the headline.
What if a venue lists the outcomes differently?
The venue entry reports outcome_set in match.differences and explains the merge in the match note. The quotes in quotes are then already on the same rows. Read match before comparing two outcome lists by label. The how-to on comparing Polymarket and Kalshi shows the single-question steps.
Do the close and resolve times differ by venue?
The event gives one closes_at and one resolves_at. Venue wording about timing sits in rules_text, so a screen that cares about it compares the two texts. The Polymarket text for this event names the FOMC statement as the resolution source and a fallback to the no change bracket if none is issued.
Published .