Kalshi API and Polymarket: Matching Prediction Markets by the Call
Two Kalshi routes returned 15 and 25 fields for one venue, with no tickers in common. The cross-venue match endpoint returned 20 pairs at 100 percent.

Copy this line to your agent to pull matched markets across both venues.
set up https://monid.ai/SKILL.md and use surf /prediction-market/matching/pairs
Two venues list the same question. Polymarket calls it "Will Volodymyr Zelenskyy rank in Google's Top 5 Most Searched People of 2025?" and Kalshi calls it "Will Volodymyr Zelenskyy be in the Top 5 on Google's Year in Search 2025 Global, People?". There is no shared identifier, the wording differs, and any comparison you want to make depends on knowing those are the same event. That join is the whole problem, and on 2026-09-07 an endpoint solved it for us. This guide runs through Monid, the OpenRouter for agent tools.
Which Kalshi API should you call?
There are two on the shelf, they return different field sets for the same venue, and on 2026-09-16 their first pages had no tickers in common.
The two routes
surf/prediction-market/kalshi/markets returned twenty markets, all active, fifteen fields each, the same first ticker it returned on 2026-09-07. blockrun.ai/api/v1/pm/kalshi/markets returned twenty markets, all closed, twenty-five fields each, led by the settled World Cup and Super Bowl contracts.
surf/…/kalshi/markets | blockrun.ai/…/kalshi/markets | |
|---|---|---|
| Fields per market | 15 | 25 |
| Default first page | Active markets | Highest-volume markets, settled |
| Identifier | market_ticker, event_ticker | ticker, event_ticker, plus market_id UUID |
open_interest | 167513.25, a float | integer |
close_time | 1803913200, a Unix epoch | "2026-07-19T22:04:28Z", ISO 8601 |
| Extra on one side | category, subcategory, notional_volume_usd, last_day_open_interest | yes_subtitle, no_subtitle, result, settlement_time, dollar_volume, dollar_open_interest, outcomes |
| Tickers shared on page one | 0 of 20 | 0 of 20 |
What the differences mean
The two default pages are different questions. One route answers "what is trading now", the other "what traded most". Neither is the Kalshi catalogue; both are a page of it, sorted by something you did not choose. Pass filters or you are reading the provider's default view.
Open interest with a decimal. 167513.25 contracts is not a count of whole contracts, and it is worth knowing before you cast the field to an integer.
Two timestamp formats for one field name. close_time is a Unix integer on one route and an ISO string on the other. A pipeline that reads both without normalising will compare 1803913200 to a string and get nothing.
The dollar fields live on one side. dollar_volume and dollar_open_interest are the unit conversion this article warns about two sections down, already done, on the blockrun route only. If you are comparing against Polymarket's volume_usd, that is the route that hands you comparable units.
The match endpoint, nine days later
surf/prediction-market/matching/pairs returned twenty pairs on 2026-09-16, every one confidence: 100, match_type: "exact". Identical distribution to 2026-09-07. The section below written on the seventh said to treat that uniformity with suspicion and test it; observed twice, nine days apart, it is a property of the endpoint's default filter rather than a lucky sample. The endpoint returns its exact matches by default and the hard cases are behind a parameter, which is the right design and worth knowing when you evaluate it.
What does a prediction market feed contain?
More structure than a price, and the structure is what makes it usable.
The market record
surf/prediction-market/kalshi/markets returned twenty markets on 2026-09-07. One of them:
{
"market_ticker": "KXLAYOFFSYINFO-26-494000",
"event_ticker": "KXLAYOFFSYINFO-26",
"title": "More tech layoffs in 2026 than in 2025?",
"category": "Economics",
"subcategory": "Employment",
"status": "active",
"payout_type": "Binary Option",
"total_volume": ...,
"notional_volume_usd": ...,
"open_interest": ...,
"last_day_open_interest": ...,
"start_time": ..., "end_time": ..., "close_time": ...
}
The two-level identifier
event_ticker and market_ticker are not the same thing. An event is the real-world question; a market is one tradeable outcome within it. A question with several thresholds produces several markets under one event, so aggregating at the wrong level double counts.
The fields that describe liquidity
open_interest is how many contracts are currently held, and last_day_open_interest lets you see whether interest is building or draining. A market with a confident-looking price and almost no open interest is a price two people agreed on, which is not the same as a forecast. The same distinction between a number and the confidence behind it runs through the fundamentals guide.
The fields that describe time
start_time, end_time and close_time are three different moments, and close_time is the one that matters for whether a price is still meaningful. A market approaching resolution behaves differently from one with months to run. Tracking the shape of a series rather than a single reading is the same argument as in SERP history.
📖 See also Simply Wall St GraphQL API: What Returns Fundamentals Instead
Why can you not just match the questions by text?
Because the venues write their own wording, and the differences are exactly the kind that defeat string matching.
The pair we measured
Polymarket Will Volodymyr Zelenskyy rank in Google's Top 5 Most Searched
People of 2025?
Kalshi Will Volodymyr Zelenskyy be in the Top 5 on Google's Year in
Search 2025 Global, People?
Same event. "rank in" against "be in", "Most Searched People" against "Year in Search Global, People", one carries the year in a different position. A normalised token overlap would score these as related and would score plenty of genuinely different questions the same way.
And no shared key exists
Polymarket identifies markets by condition_id and token_id, both long hex strings from its own contract layer. Kalshi uses market_ticker and event_ticker, short human-readable codes. Neither means anything to the other, and there is no registry mapping between them.
What the matching endpoint returns
surf/prediction-market/matching/pairs returned twenty pairs, each carrying confidence, match_type, category, and a nested object per venue. On 2026-09-07 all twenty came back match_type: "exact" with confidence: 100.
That uniformity is worth reading carefully rather than celebrating. Either the endpoint returns only high-confidence pairs by default, or the sample was easy. Twenty rows all at 100 does not tell you how it behaves on hard cases, and you should find out on your own categories before trusting it on the ambiguous ones.
Why this belongs in a service rather than your code
Because the join is the hard part and it changes constantly as both venues list and delist. Rebuilding it yourself means maintaining a fuzzy matcher against two moving catalogues, and the failure mode is silent: a wrong pair produces a comparison between two different questions and a number that looks like an opportunity. Silent wrongness is the recurring hazard in this whole category, from an endpoint returning 26 empty fields to a price stored without its list price.
How do you compare a market across two venues?
Three steps. Discovery is free.
For agents
Grab an API key at app.monid.ai, then paste this to your agent and hand it the key:
set up https://monid.ai/SKILL.md
It learns the whole discover, inspect, run workflow itself. More in the agent quickstart.
For humans
npm install -g @monid-ai/cli
monid keys add -k <your-api-key> -l main
Step 1. Get the matched pairs
What it does. Solves the join, which is the part you cannot do reliably yourself.
The endpoints. surf/prediction-market/matching/pairs, billed per call.
The call.
monid run -p surf -e /prediction-market/matching/pairs --query '{}'
What comes back. Pairs with confidence, match_type and both venues' identifiers, so you can go and price each side.
What it costs. A couple of cents per call. Current figures at monid.ai/tools.
Step 2. Price each side and keep the units straight
What it does. Turns a pair into a comparison.
The endpoints. surf/prediction-market/kalshi/prices for a Kalshi time series, and the Polymarket side by condition_id.
The trap. The pair record reports Polymarket volume as volume_usd and Kalshi volume as volume_contracts. Those are different units. Comparing them directly is not a small inaccuracy, it is a category error, and it is easy to make because both fields are called volume in conversation.
# WRONG
liquidity_gap = pair["polymarket"]["volume_usd"] - pair["kalshi"]["volume_contracts"]
# right: convert, or compare each venue against its own history
ks_usd = pair["kalshi"]["volume_contracts"] * contract_price_usd
Step 3. Check both sides resolve the same way
What it does. The check that decides whether the comparison means anything.
The call. No endpoint. Read both venues' resolution criteria before treating a price difference as a discrepancy. Two markets on "the same" event can settle on different sources, different dates or different definitions of the threshold, in which case the prices are allowed to differ and there is nothing to exploit.
Assert on it the way you would on any other field you depend on:
if pair["confidence"] < 95 or pair["match_type"] != "exact":
review_manually(pair) # do not auto-act on a fuzzy pair
Give this to your agent![]()
Set up https://monid.ai/SKILL.md, and then use Monid to pull matched prediction market pairs, and show me the ones where the two venues disagree most, with both resolution dates side by side.📖 See also How to Stop Depending on One Scraping Vendor
What does the volume field actually mean?
Three different things, depending on which field you read, and conflating them is the most common error in this data.
Contracts against dollars
Kalshi's volume_contracts counts contracts traded. Polymarket's volume_usd counts dollars. A contract is not a dollar, and the conversion depends on the price at which it traded, so there is no fixed multiplier. Unit mismatches like this are the quiet version of the buy-versus-build arithmetic in the real cost of scraping YouTube yourself, where the number people compare is not the number that matters.
Volume against open interest
Volume is how much changed hands; open interest is how much is currently held. A market can have large volume and near-zero open interest, meaning people traded in and out without anyone holding a position, which is a very different signal from a market where money is committed.
Today against cumulative
last_day_open_interest next to open_interest is the pair that shows direction. A rising number means positions are being built; a falling one means the market is unwinding, often ahead of resolution.
Why this matters for the comparison
Almost every naive cross-venue analysis starts by ranking markets by "volume" to find the liquid ones, and if the two sides are in different units the ranking is meaningless before any pricing happens. Normalise first, then compare, the same discipline as never comparing an engagement rate computed by one tool against one computed by another, measured in the Instagram accuracy post.
Which endpoint should I use for which job?
| Endpoint | What it does | Input | Output | Best for | Billing |
|---|---|---|---|---|---|
surf/prediction-market/matching/pairs | Cross-venue join | none | Pairs with confidence and both ids | The hard part | Per call |
surf/prediction-market/kalshi/markets | Kalshi catalogue, active first | optional filters | 15 fields: tickers, category, volume, open interest, epoch times | Browsing what trades now | Per call |
blockrun.ai/api/v1/pm/kalshi/markets | Kalshi catalogue, by volume | limit, filters | 25 fields: UUID, subtitles, result, dollar volume, ISO times | Settled markets and dollar units | Per call |
surf/prediction-market/kalshi/prices | Price time series | Market | Series | Tracking a market over time | Per call |
surf/prediction-market/kalshi/trades | Individual trades | Market | Trade records | Microstructure and real liquidity | Per call |
surf/prediction-market/polymarket/markets | Polymarket catalogue | filters required | Markets | The other side of a pair | Per call |
Every row was verified with monid inspect on 2026-09-07. The table gives billing shape rather than figures; shape drives design and current numbers live on monid.ai/tools.
One honest note on the last row: called with no arguments it returned HTTP 400 on 2026-09-07, so it wants filters rather than accepting an empty request. That is a documented-shape problem rather than an outage, and the matching endpoint is the better entry point anyway because it hands you the Polymarket identifiers directly.
When is this data misleading?
Four cases, and the first is the one that costs money.
The two markets do not resolve identically. A price gap between venues looks like an opportunity and is often a difference in settlement source or date. Check the criteria before concluding anything.
Open interest is thin. A price is only a forecast if someone was willing to take the other side at size. On a market with negligible open interest the price is closer to an opinion than a market view.
The market is near close. Prices compress towards zero or one as resolution approaches, and a series that mixes far-dated and near-dated observations will show movement that is just time passing.
You are treating a probability as a prediction. A 70% market is not saying the thing will happen; it is saying it happens roughly seven times in ten across many such situations. Judging the market by whether the single event occurred is the most common misreading there is.
And the disclosure: this is Monid's blog and we sell per-call access to these endpoints, including one that returned a 400 on the day of writing and is listed with that caveat. Nothing here is trading or investment advice, and the cross-venue gaps that look like free money are usually a resolution difference you have not read yet.
Conclusion
The data is richer than a price: two levels of identifier, open interest against volume, and three separate timestamps that mean different things. What makes it hard to use is not the pricing, it is the join. The same question is worded differently on each venue, the identifiers share nothing, and text matching is not a reliable bridge.
A matching endpoint solved that on 2026-09-07, returning twenty pairs, all exact, all at confidence 100. That uniformity is a reason to test it on your own categories rather than a reason to trust it, because a sample where everything is easy tells you nothing about the hard cases.
And keep the units straight. Polymarket reports dollars, Kalshi reports contracts, and the comparison most people make first is between those two numbers.
Free next step: run monid run -p surf -e /prediction-market/matching/pairs --query '{}' and read a few pairs side by side. The wording differences alone will tell you why the join is worth paying for. Start at monid.ai.
FAQ
Are prediction markets actually accurate?
They are generally well calibrated in aggregate, meaning events priced at 70% tend to occur about 70% of the time across many markets, and they have often outperformed pundits and polls on election and policy questions. Calibration is not the same as being right about any single event, which is where most criticism goes wrong. Accuracy also degrades sharply on thin markets, on long-dated questions, and where the resolution criteria are ambiguous enough that traders are pricing the wording rather than the world.
Why do two venues price the same event differently?
Sometimes it is a genuine difference of opinion between two pools of traders, and that is the interesting case. More often it is mechanical: different resolution sources, different settlement dates, different fee structures, different access for non-US participants, or simply thin liquidity on one side. Fees matter more than people expect, because a gap that looks like several points of edge can vanish once both legs are costed. Always read both sets of criteria before calling a difference a discrepancy.
What is the regulatory status of these venues?
It differs by venue and by jurisdiction and it has been changing. Kalshi operates as a CFTC-regulated designated contract market in the US; Polymarket has a different history with US regulators and its access rules have varied over time. Anything involving real money here is subject to rules about who may participate and on what, and those rules are not the same for everyone reading this. Reading market data is a different activity from trading on it, and this article is about the first.
How often should you poll these endpoints?
Match it to the horizon of the market. A question resolving in months moves slowly enough that hourly sampling is plenty, and daily is defensible. A market resolving this week, or one reacting to a news event, moves on a timescale where minutes matter, and there polling is the wrong shape entirely if a stream is available. The general rule is the one in rate limiting a third party API: decide your cadence from what you will do with the answer, cap concurrency, and stop when the spend rather than the pace becomes the binding constraint.
Last updated September 2026.


