The thing I keep rebuilding is a probability tracker. Some market catches my eye on Polymarket or Kalshi and I want a clean time series of the odds, polled every few minutes, stored somewhere I can chart, running without babysitting. It sounds like an afternoon project. It mostly is, except both venues hide their sharp edges in different places, and you tend to find them only after your cron job has been writing nulls for a week.
Quick lay of the land first. Polymarket runs an off-chain order book that settles on Polygon, and its data surface is split across three separate services you stitch together yourself. Kalshi is a regulated US exchange with one conventional REST API. Market data is free on both, and for read-only work you barely need authentication on either, which is the good news.
Polymarket, three APIs pretending to be one
Polymarket splits its data across the Gamma API for metadata, the CLOB API for prices and order books, and a separate data API for trade history. Gamma is where you discover markets. You can filter by slug or active status, or pull whole events, and the response includes the field the rest of the pipeline depends on, the CLOB token IDs. Each binary market has two outcome tokens, one for yes and one for no, and they are long numeric ERC-1155 identifiers that look nothing like the market slug or the condition ID.
This is the first gotcha and it costs everyone an hour. The CLOB price and book endpoints are keyed by token ID, not by market. Pass a condition ID or a slug and you get nothing useful back. So the discovery step is really an identifier-mapping step. Resolve the human question to a market, then store the slug, the condition ID, and both token IDs, because different endpoints want different ones.
Once you have a token ID the CLOB API is pleasant. There is a midpoint endpoint, a price endpoint that takes a side, a book endpoint for bid and ask depth, and a price history endpoint that backfills a time series so you are not polling for weeks before you have anything to chart. Prices come back as decimal strings between 0 and 1, which means they are already probabilities. The yes and no tokens have separate books, and because of spread their prices rarely sum to exactly 1, so pick the yes token and stay consistent. None of this needs a key. Polymarket authentication, which derives credentials from a wallet signature, only matters when you place orders or read your own account.
Kalshi, one API with stricter shapes
Kalshi hangs everything off a versioned trade API and organizes markets in a hierarchy. A series is the recurring concept, an event is one instance of it, and a market is one strike or outcome within that event. Tickers encode this, so a market ticker embeds its series, its expiration, and its strike. You can list markets with filters, pull one by ticker, fetch its order book, and page through the public trade tape.
Two differences matter for a tracker. Prices are integer cents from 1 to 99, so divide by 100 before storing anything next to Polymarket data or your charts will be nonsense. And while the market data endpoints are public reads, account and order endpoints need an API key with request signing, an RSA key pair used to sign a timestamp plus the request path. For pure monitoring you can skip auth entirely, though authenticated requests typically get roomier rate limits, so it is worth setting up once your polling grows.
The Kalshi gotcha that bites hardest is recurring markets. A weekly market resolves and a brand new market with a brand new ticker takes its place. If your tracker stores one ticker it works perfectly until the period rolls over, then silently polls a settled market forever. Track the series ticker as the stable identity and resolve it to the current market ticker at poll time.
The tracker itself
The pipeline I run is deliberately boring, four steps on a timer.
- Discover. Resolve the question to identifiers. On Polymarket that means slug, condition ID, and both token IDs from Gamma. On Kalshi it means the series ticker plus the current market ticker.
- Poll. Every few minutes, fetch the Polymarket midpoint for the yes token and the Kalshi yes bid and ask, compute the mid, and normalize both to a probability between 0 and 1.
- Store. One row per venue per poll: timestamp, market identifier, mid, best bid, best ask, and the depth near the touch. A market at 60 cents with a couple hundred dollars behind it and one at 60 cents with deep size on both sides are different observations, and you cannot recover that distinction later.
- Check status on every poll. Polymarket exposes closed and resolved flags, Kalshi has a status field and a result field. When a market resolves, write a final row with the outcome and stop polling it.
On rate limits, both venues tolerate polite read traffic. Polling a few dozen markets once a minute has never gotten me throttled on either. Use the historical endpoints for backfill instead of hammering the live ones, add jitter so your requests do not land in lockstep, and treat any 429 as a signal to back off exponentially rather than retry immediately.
The parts that actually cost me time
Pagination is different on each side and both versions can hurt you. Gamma uses limit and offset, and offset pagination over a list that reorders as markets close will skip or duplicate entries mid-scan, so filter to active markets and keep pages large. Kalshi uses cursors, which are safer, but treat a paginated scan as something you finish promptly rather than resume tomorrow.
Resolution has lag on both venues. Polymarket resolutions go through UMA, an optimistic oracle with a dispute window, so a market can trade at 99 cents for days while the flags still say open. Kalshi settlement also trails the real-world outcome. My rule of thumb is to treat the resolution flag as truth for accounting, treat a price pinned near 0 or 1 as a strong hint for everything else, and never assume a market near the boundary is finished.
Also, keep Polymarket prices as decimals rather than floats when you parse them. They arrive as strings for a reason, and float rounding on probability data produces small ugly artifacts in spreads and sums that will make you distrust your own pipeline.
We run a much larger version of this at Blockcircle to feed prediction-market signals, and scale did not change the lesson. The polling loop is trivial. The identifier bookkeeping, the ticker rollovers, and the resolution handling are the real work, so get those right on one market before you point the thing at a thousand.