Skip to main content
OHLCV candles for any contract, from 1-second to 1-day resolution. Use GET /v1/candles for one series, POST /v1/candles/batch for up to 200 at once, and the binary format when JSON parsing is your bottleneck.

Get candles

Returns one OHLCV series for one outcome of one contract. Auth: none required — works on the anonymous free tier. light bucket, cost 1 + 1 per 5,000 requested bars.

Request

Example

Add your credentials to raise the budget:

Response

Prices are on the 0–100 scale (probability × 100). volume is in contracts.

Errors

Behavior

  • Half-open windows, bucket-aligned. start floors to a bucket boundary; end mid-bucket extends to include the current bucket, and is then clamped to at most one bucket past now. A request ending “now” includes the live, still-forming bar. A window that aligns to nothing is widened to exactly one bucket rather than rejected.
  • Sparse series. Buckets with no trades are omitted. Gaps in the array reflect periods with no trading. An empty candles array is a valid 200 — the window is authoritatively empty, not an error.
  • Range caps per timeframe. start is clamped, never rejected: 1-second data older than 24 hours is not retained — a 1s window entirely older than that returns {"candles": []} without querying anything.
  • Rollups. Only 1s and 1m bars are stored; 5m, 15m, 1h, 4h and 1d are aggregated from the 1m base on the fly. If that comes back empty, 1m, 1h, 4h and 1d retry against the 1s base (which is what gives brand-new contracts a series); 5m and 15m have no such retry.
  • Multi-outcome markets. Pass outcome to select the token; each outcome has independent OHLC.
  • Caching. A window still in progress is no-store; a recent window is cached briefly; a fully historical one is cached for a day. The latter two carry a strong ETag and honor If-None-Match with a 304.
Send kalshi, not the kalshi_offchain alias. The alias takes a legacy path that derives outcome 1 as 100 − price instead of reading its own series. On a multi-outcome Kalshi market that is the wrong number.

Batch

Fetches up to 200 series per call in one round trip, each with its own provider, contract, timeframe, and window. Auth: none required. light bucket, cost 1 up front plus 1 per 5,000 bars summed over every item.

Request

Body: JSON, at most 4 MiB.

Example

Response

Results come back index-aligned with per-item errors — one bad request doesn’t fail the batch:

Errors

A single item’s failure never fails the call. It comes back inside a 200 as {"index": n, "candles": [], "error": "…"}. Check every item’s error field; do not infer success from the HTTP status alone.
Batch overage is charged late. Because the true cost is only known once the body is parsed, the excess lands against your next request, not this one. See rate limits.
Batch responses are always Cache-Control: no-store and never carry an ETag.

Binary format

For chart-heavy or high-volume consumers, request the columnar binary encoding — roughly 8× smaller than JSON before compression:
The Accept header is equivalent:

Layout

Little-endian throughout:
A single-series GET always has num_results = 1. A batch frame has one section per request index, in input order.
The high bit of count is an error flag, not part of the count. On a batch, a failed item sets 0x80000000 on its count. Mask it off (count & ~0x80000000) before using the value, and treat a set bit as “this series errored” — not as “this series has no data”. A decoder that skips this step will try to read roughly two billion candles. The error message is only available from the JSON response.

Reference decoder (Python)