How to pull prediction-market prices into Google Sheets
Five ways to get Kalshi and Polymarket prices into a spreadsheet — a short Apps Script, a CSV feed, an add-in, a file — and why a cell sits stale for an hour.
IMPORTDATA cannot do it on its own, because Kalshi and Polymarket answer in JSON and IMPORTDATA reads only CSV or TSV. The working route is a few lines of Apps Script calling the venues' public price endpoints, refreshed by a time-driven trigger rather than left as a formula. If you would rather not write code, a scraper that publishes CSV or a file export gets the numbers in, at the cost of freshness.
The short way
Write one small Apps Script function that fetches the price and writes it into a cell, and run it
on a time-driven trigger. Do not reach for a formula first: IMPORTDATA reads only CSV or TSV,
according to Google's own reference, and both venues' price endpoints answer in JSON.
Kalshi's market reads are public — the OpenAPI description of GET /markets/{ticker} carries an
empty security list — and Polymarket's order-book prices need no key either. In the sheet, open
Extensions → Apps Script and paste something like this:
// Writes the latest prices into the "Prices" tab. Column A holds the identifier.
function refreshPrices() {
var sheet = SpreadsheetApp.getActive().getSheetByName('Prices');
var rows = sheet.getRange('A2:A').getValues().filter(function (r) { return r[0]; });
var out = rows.map(function (r) {
var id = String(r[0]);
if (id.length > 40) {
// A long numeric id is a Polymarket outcome token: the midpoint of its book.
var mid = UrlFetchApp.fetch('https://clob.polymarket.com/midpoint?token_id=' + id);
return [Number(JSON.parse(mid.getContentText()).mid), new Date()];
}
// Otherwise treat it as a Kalshi market ticker: the last traded YES price.
var res = UrlFetchApp.fetch('https://external-api.kalshi.com/trade-api/v2/markets/' + encodeURIComponent(id));
return [Number(JSON.parse(res.getContentText()).market.last_price_dollars), new Date()];
});
sheet.getRange(2, 2, out.length, 2).setValues(out);
}
Then add a time-driven trigger for refreshPrices under Triggers in the editor. Write the
timestamp beside every price, as above. It is the only way anyone reading the sheet can tell a
live number from one that stopped updating on Tuesday.
What the options are
The Kalshi API is the cleanest source for this. GET /markets/{ticker} returns
one market object with yes_bid_dollars, yes_ask_dollars, no_bid_dollars, no_ask_dollars
and last_price_dollars, each a fixed-point dollar string with up to four decimal places, plus
quantities as _fp strings. GET /markets?series_ticker= gives a whole series in one call, which
matters once the sheet has more than a handful of rows.
The Polymarket CLOB API prices outcome tokens, not markets. Each
market carries its token ids as a JSON-encoded array in a field called clobTokenIds, which you
read once from Gamma and keep in the sheet. After that,
/midpoint returns {"mid": "0.085"}, /price?side=BUY the lowest ask, /last-trade-price the
last fill, and batch forms of all three take several tokens in one POST.
Apify's prediction-market actors are the no-code route
that ends in an actual formula. An actor run on Apify's scheduler writes Kalshi and Polymarket rows
— question, YES and NO price, bid, ask, volume — into a dataset, and the dataset items endpoint
takes format=csv, which IMPORTDATA can read. Each actor is a different author's code with its
own field names and its own per-row price, so pin one and expect to fix the sheet when it changes.
Artemis is the one card here with a real Sheets add-in, and it answers a different question. What it serves is venue-level daily metrics — volume, open interest, fees, users — across thirteen venues, not the price of a market. The add-in comes with the 100 USD Investor tier at 50,000 sheet calls a month, or alone as the 500 USD Plugin tier.
Lychee is for when the sheet is an analysis, not a monitor. Pull Kalshi or Polymarket history in the browser, export CSV or XLSX, and open the file in Sheets. There is no refresh; the plans start at 19.99 USD a month with 12,500 rows per historical pull.
Where this breaks
A cell is not live, and Sheets decides when it updates. Google's help page states the intervals:
IMPORTDATA, IMPORTHTML, IMPORTFEED and IMPORTXML recalculate every hour, IMPORTRANGE every
30 minutes. A custom function — =KALSHI("TICKER") written as a script — is worse: Google's guide
says it does not recalculate until you edit it or a cell passed to it changes, and passing NOW()
to force it is not allowed and leaves the cell on "Loading..." for good. A custom function must
also return within 30 seconds or show #ERROR!. That is why the short way above is a triggered
script that writes values, not a formula.
The quota is per Google account, not per sheet. A consumer account gets 20,000 UrlFetchApp
calls a day and 90 minutes of trigger runtime, a Workspace account 100,000 calls and six hours. A
script that fetches 20 markets one at a time every minute makes 28,800 calls a day and stops
part-way through the day. Batch: Kalshi's list endpoint by series, Polymarket's POST forms for many
tokens.
The venue's rate limit sees Google, not you. Requests leave from Google's servers. Polymarket
enforces its limits per IP address through Cloudflare and delays requests over them rather than
rejecting them; Kalshi answers an over-limit request with a 429 and no Retry-After header. A
script that hammers either one gets slow or empty answers, and the error lands in a cell you may not
be looking at. Check the trigger's execution log.
Cents, dollars and probabilities are three different numbers. Kalshi now returns prices as
fixed-point dollar strings — "0.1200" — and its own documentation says integer-cent fields cannot
represent sub-cent ticks, which some markets now use. A tutorial dividing yes_bid by 100 was
written against a field this endpoint no longer returns. Polymarket's prices are decimal strings
between 0 and 1. Both are prices of a contract paying one unit, not probabilities: a wide spread or
a stale last trade makes the "probability" in your sheet a quote of one side of a thin book. Both
arrive as strings, so convert with Number() in the script rather than writing the string: a
sheet whose locale writes decimals with a comma can keep "0.42" as text, and a sum over text
cells does not fail — it leaves them out.
Which price is in the cell is a choice. Bid, ask, midpoint and last trade differ, sometimes by a
lot, and the sheet does not say which unless you label the column. On a market that has not traded
today, last_price_dollars is an old number that looks exactly like a current one.
Your own account is a different problem. Everything above is public data. Reading your Kalshi
positions or balance needs signed headers — RSA-PSS over SHA-256, or an Ed25519 key — and Apps
Script's built-in Utilities service offers RSA-SHA256 and HMAC signing but no PSS or Ed25519.
Pasting a private key into a script bound to a shared sheet also gives it to every editor of that
sheet. Treat it as a separate project; where your key lives is the
page for that decision. The same goes for an Apify URL carrying token=: anyone who can read the
formula can read the token.
If you outgrow this
A sheet polling once a minute is a dashboard with an hour's worth of failure modes. If what you want is a number that moves the moment the book does, that is a stream, not a spreadsheet, and streaming an order book is where that starts. If what you want is history to chart, pull it once from the price-history endpoints or an archive instead of keeping a sheet open — getting resolved market history covers the sources. And if the sheet is becoming a dashboard other people read, building a dashboard on Polymarket data covers the tools built for that job.
The approaches, in order
Cards in the catalogue that do this, ordered editorially. Paid placement does not affect this order.
1.Kalshi API
Public market reads with no key: GET /markets/{ticker} from Apps Script, with bid, ask and last trade as dollar strings such as 0.4200.
REST, WebSocket and FIX access to a CFTC-regulated event exchange.
Per-trade feeFree tier
2.Polymarket CLOB API
Keyless midpoint, best price and last-trade endpoints per outcome token, each returning one decimal string a script can drop straight into a cell.
Order books, prices and order placement on Polymarket's matching engine.
Per-trade feeFree tier
3.Apify prediction-market scrapers
Scheduled actors write Kalshi and Polymarket rows to a dataset that the Apify API serves as CSV — the one shape IMPORTDATA reads natively.
A store of third-party actors that normalize Kalshi and Polymarket into one row shape.
$19/moFree tier
4.Artemis prediction-market metrics
A paid Google Sheets add-in, but for venue-level daily volume, open interest and fees across thirteen venues, not a single market's price.
Daily volume, open interest and fees across thirteen event venues, with methodology.
$100/moFree tier
5.Lychee
Pull trade, market or book history in a browser and export CSV or XLSX to open in Sheets — a snapshot with no refresh at all.
No-code queries, charts and backtests over Kalshi and Polymarket history.
$19.99/mo
FAQ
Is there an IMPORTJSON function in Google Sheets?
Not a built-in one. IMPORTJSON is a name for community-written Apps Script that people paste into their own sheets. The built-in import functions are IMPORTDATA for CSV and TSV, IMPORTXML, IMPORTHTML, IMPORTFEED and IMPORTRANGE. Any JSON reader in a sheet is a script, and you own it the moment you paste it.
Can I pull my own Kalshi positions into the sheet the same way?
Not with the same few lines. Market prices are public; your positions sit behind Kalshi's signed requests, which use RSA-PSS over SHA-256 or an Ed25519 key. Apps Script's Utilities service lists RSA-SHA256 and HMAC signing methods and no PSS or Ed25519 variant, so the signature has to come from code you write or from somewhere outside Sheets.
How often can the prices refresh?
A time-driven trigger can run a script as often as once a minute, and Google says the time may be slightly randomised. IMPORTDATA refreshes about once an hour. A consumer Google account gets 20,000 URL fetches a day across all its scripts, which one market polled every minute uses 1,440 of.
Sources
- IMPORTDATA — Google, read
- Change a spreadsheet's locale, time zone, settings and language (recalculation of external data functions) — Google, read
- Custom functions in Google Sheets — Google, read
- Quotas for Google services — Google, read
- Installable triggers — Google, read
- Get Market (GET /markets/{ticker}) — Kalshi, read
- Fixed-Point Representation — Kalshi,
- Quick Start, Authenticated Requests — Kalshi, read
- Prices and Order Books — Polymarket, read
- Run Actor and retrieve data via API — Apify, read
The catalogue next door
This page names a handful of cards. The rest of them are in Prediction Market Analytics & Dashboards, each filled in against the same schema, with the fields to narrow it yourself.
Last updated . Corrected in place: this is a reference page, not a dated post.