Start with one public market-data request
Kalshi's market-data quick start documents public REST reads at https://external-api.kalshi.com/trade-api/v2. Reading the markets used here does not require generating credentials or connecting a funded account. Authentication and orders belong to a later, separate implementation.
Run this request in a terminal:
curl --fail --silent --show-error 'https://external-api.kalshi.com/trade-api/v2/markets?series_ticker=KXHIGHNY&status=open&limit=5'
KXHIGHNY is a series identifier used in Kalshi's official tutorial. A series groups recurring events; an event contains markets for a particular occurrence. The returned market ticker is the identifier you need for a specific contract. Do not hard-code a dated market ticker from someone else's screenshot.
We checked this request on October 7, 2026 and received five open markets. Availability changes, so your response can contain different markets or an empty array. An empty result should produce a clear report, not an invented price or an automatic fallback to a different event.
Know which fields you are reading
| Field | Meaning in this monitor | Handling |
|---|---|---|
| ticker | Specific market identifier | Keep with every observation |
| yes_bid_dollars / yes_ask_dollars | Quoted YES prices in dollars | Parse decimal strings; do not divide by 100 again |
| rules_primary | Primary settlement criteria | Save and read alongside the question |
| cursor | Continuation for additional results | Record it; this example intentionally reads one page |
The Get Markets reference documents filters and response fields. A displayed quote is not evidence that your full proposed size can execute at that price. A market's short title is also not a substitute for its detailed rules.
Run the Python monitor
Save the following as kalshi-market-monitor.py, or download the same example, then run python3 kalshi-market-monitor.py. It performs a bounded GET request and prints a JSON report. It does not poll indefinitely or submit an order.
"""Read one bounded page of public Kalshi markets. No credentials or orders."""
import json
from datetime import datetime, timezone
from decimal import Decimal, InvalidOperation
from urllib.parse import urlencode
from urllib.request import Request, urlopen
BASE = "https://external-api.kalshi.com/trade-api/v2"
params = {"series_ticker": "KXHIGHNY", "status": "open", "limit": 5}
url = BASE + "/markets?" + urlencode(params)
request = Request(url, headers={"User-Agent": "NickAI-market-monitor-example/1.0"})
with urlopen(request, timeout=20) as response:
data = json.load(response)
observed_at = datetime.now(timezone.utc).isoformat()
def price(value):
if value is None or value == "":
return None
try:
result = Decimal(str(value))
return result if result.is_finite() and 0 <= result <= 1 else None
except InvalidOperation:
return None
rows = []
for market in data.get("markets", []):
bid = price(market.get("yes_bid_dollars"))
ask = price(market.get("yes_ask_dollars"))
# Zero is preserved as data; it is not evidence of executable depth.
spread = ask - bid if bid is not None and ask is not None and ask >= bid else None
rows.append({
"ticker": market["ticker"], "title": market.get("title"),
"yes_bid_dollars": str(bid) if bid is not None else None,
"yes_ask_dollars": str(ask) if ask is not None else None,
"spread_dollars": str(spread) if spread is not None else None,
"rules_primary": market.get("rules_primary"),
})
print(json.dumps({"observed_at": observed_at, "source": url,
"markets": rows, "next_cursor": data.get("cursor")}, indent=2))
The report includes the exact source URL, a UTC observation time, and one entry per market. In the October 7 check, one market's YES bid and ask were $0.3600 and $0.3700, a quoted spread of $0.0100. That is a historical observation from this test, not a current quote or a trading recommendation.
The code uses Decimal so dollar strings are handled directly. Missing or invalid values stay null. Zero remains zero: replacing missing values with zero would make a broken feed look like a market observation. A negative spread is left unavailable for review rather than silently treated as an opportunity.
Make the monitor reliable before scheduling it
- Define the universe. Keep a specific series or event filter. A broad first page is not a representative sample of the exchange.
- Handle pagination deliberately. For a complete collection, pass the cursor into the next request and stop when it is empty. Also set a page budget so a changed response cannot create an unbounded job.
- Treat errors as errors. Timeouts and HTTP failures should produce an error record. Do not reuse an old successful quote without labeling it stale.
- Bound retries. Add backoff for transient failures and respect the current API limits. Keep credentials out of logs if you later add authenticated endpoints.
- Separate observation and interpretation. Save the source values before asking a model to summarize them. Ask it to distinguish the contract's rules from any outside explanation.
If you expand beyond a named series, the API exposes an mve_filter option for including or excluding multivariate markets. Select the intended market type explicitly. Do not assume a large unfiltered response contains the kinds of contracts you meant to monitor.
Build the same reporting task in NickAI
NickAI's Kalshi Data node provides market-data operations within a workflow. Start with a request such as: find open markets in a chosen series, show the tickers and quotes, include the settlement rules, and prepare a short report. Ask Nick to keep the workflow inactive so you can inspect the configuration.
Review the data-node output before connecting the report step. NickAI's normalized node fields can differ from the raw API fields used in this script. Use the actual output in your workspace, including the empty-results branch, rather than copying a path from this tutorial without checking it.
The Kalshi integration page and vibe-trading examples provide starting points. For a workflow that compares venues, first build each data path separately using this guide and the Polymarket API guide. Similar market titles do not establish identical settlement conditions.
What changes when you add an order
An order path needs account authentication, market and side selection, explicit sizing, and a way to reconcile the response with the actual order state. Read the current authenticated-request guide before implementing that path. NickAI exposes the Kalshi Order node separately from data reads.
Keep the initial monitor free of that action. Once the data and report are correct, define the behavior for rejected orders, partial fills and lost responses as a separate project. An API connection working successfully says nothing about whether a trading rule is useful.