Get a single OHLCV candle series
Returns OHLCV candles for one (provider, contract_id, timeframe_seconds, outcome)
series over [start, end).
Alignment. start is floored and end is ceiled to the nearest
timeframe_seconds bucket boundary; end is additionally clamped to
now + 1 bucket. Degenerate windows (end <= start after alignment)
are extended by exactly one bucket. The requested window is silently
clamped (not rejected) to the per-timeframe retention ceiling: 1s=1
day, 1m=30 days, 5m=90 days, 15m=180 days, 1h=365 days, 4h=365 days,
1d=730 days. For timeframe_seconds=1, if the aligned window falls
entirely before now - 24h (the retention window for 1-second
candles) the endpoint short-circuits to {"candles":[]} without
querying upstream.
Timeframes. Only 1s and 1m candles are stored directly; every
other timeframe (5m, 15m, 1h, 4h, 1d) is rolled up server-side from
the 1m base. When the first read comes back empty, 60, 3600,
14400 and 86400 retry as a rollup of the 1s base (brand-new
contracts whose only history is 1-second candles); 300 and 900
have no such fallback and return an empty series instead. Prices are
on a 0-100 scale. For providers that are not natively per-token
(i.e. not kalshi, polymarket, dome, opinion, predictfun,
hyperliquid), requesting outcome > 0 inverts prices
(price = 100 - price, high/low swapped) rather than resolving a
distinct token. The check is on the provider string exactly as
submitted, so the kalshi_offchain alias takes the inverting path
even though kalshi does not — send kalshi for Kalshi candles.
Rate limiting / cost. This route is in the light rate-limit
bucket. Larger requests — wider windows, finer timeframes — consume
more of your quota; a typical chart paint (a few hundred bars) costs
1 unit.
Caching. Cache-Control depends on how the aligned window
relates to now: a window still in progress (end >= now) is
no-store; a window entirely in the recent past is cached briefly;
anything older is cached for longer. Cacheable responses carry a
strong ETag and honor If-None-Match, returning 304 on a match.
Responses ≥1KB are gzip-encoded when the client sends
Accept-Encoding: gzip.
Binary format. Pass ?fmt=binary or send
Accept: application/x-kairos-candles to receive a compact
columnar binary frame instead of JSON. Layout: u8 magic=0xCA, u8 version=1, u16 num_results, then per result u32 index, u32 count, followed by the columnar arrays u32[count] t (epoch
seconds), u16[count] o,h,l,c (price ×100), i64[count] vol
(×100). A single-series GET response always has num_results=1.
curl --request GET \
--url https://md.kairos.trade/v1/candles \
--header 'X-Api-Key: <api-key>' \
--header 'X-Api-Secret: <api-key>' \
--header 'X-Client-Id: <api-key>'import requests
url = "https://md.kairos.trade/v1/candles"
headers = {
"X-Client-Id": "<api-key>",
"X-Api-Key": "<api-key>",
"X-Api-Secret": "<api-key>"
}
response = requests.get(url, headers=headers)
print(response.text)const options = {
method: 'GET',
headers: {
'X-Client-Id': '<api-key>',
'X-Api-Key': '<api-key>',
'X-Api-Secret': '<api-key>'
}
};
fetch('https://md.kairos.trade/v1/candles', options)
.then(res => res.json())
.then(res => console.log(res))
.catch(err => console.error(err));<?php
$curl = curl_init();
curl_setopt_array($curl, [
CURLOPT_URL => "https://md.kairos.trade/v1/candles",
CURLOPT_RETURNTRANSFER => true,
CURLOPT_ENCODING => "",
CURLOPT_MAXREDIRS => 10,
CURLOPT_TIMEOUT => 30,
CURLOPT_HTTP_VERSION => CURL_HTTP_VERSION_1_1,
CURLOPT_CUSTOMREQUEST => "GET",
CURLOPT_HTTPHEADER => [
"X-Api-Key: <api-key>",
"X-Api-Secret: <api-key>",
"X-Client-Id: <api-key>"
],
]);
$response = curl_exec($curl);
$err = curl_error($curl);
curl_close($curl);
if ($err) {
echo "cURL Error #:" . $err;
} else {
echo $response;
}package main
import (
"fmt"
"net/http"
"io"
)
func main() {
url := "https://md.kairos.trade/v1/candles"
req, _ := http.NewRequest("GET", url, nil)
req.Header.Add("X-Client-Id", "<api-key>")
req.Header.Add("X-Api-Key", "<api-key>")
req.Header.Add("X-Api-Secret", "<api-key>")
res, _ := http.DefaultClient.Do(req)
defer res.Body.Close()
body, _ := io.ReadAll(res.Body)
fmt.Println(string(body))
}HttpResponse<String> response = Unirest.get("https://md.kairos.trade/v1/candles")
.header("X-Client-Id", "<api-key>")
.header("X-Api-Key", "<api-key>")
.header("X-Api-Secret", "<api-key>")
.asString();require 'uri'
require 'net/http'
url = URI("https://md.kairos.trade/v1/candles")
http = Net::HTTP.new(url.host, url.port)
http.use_ssl = true
request = Net::HTTP::Get.new(url)
request["X-Client-Id"] = '<api-key>'
request["X-Api-Key"] = '<api-key>'
request["X-Api-Secret"] = '<api-key>'
response = http.request(request)
puts response.read_bodyAuthorizations
Credential client id (kairos_ck_...). Must be sent together with X-Api-Key and X-Api-Secret.
64-char hex API key. Must be sent together with X-Client-Id and X-Api-Secret.
64-char hex API secret. Must be sent together with X-Client-Id and X-Api-Key.
Query Parameters
Market-data provider. Case-insensitive. dome and kalshi_offchain are aliases resolved to polymarket and kalshi respectively. opinion is a disabled provider kept resolvable for historic reads only. Unknown values return 400 invalid_request.
kalshi, polymarket, opinion, predictfun, hyperliquid, dome, kalshi_offchain Provider-native contract/market/ticker id. For polymarket this may be either a numeric market id or a 0x-prefixed condition_id (both resolve to the same series). Empty or >128 chars is rejected with 400 invalid_request.
128Candle bucket width in seconds. Any value outside this set is rejected with 400 invalid_request. Only 1 and 60 are stored directly; 300/900/3600/14400/86400 are always served as rollups.
1, 60, 300, 900, 3600, 14400, 86400 Window start, inclusive (before alignment). Accepts RFC 3339 (2024-01-15T10:00:00Z or with a numeric offset), a bare datetime (2024-01-15T10:00:00, treated as UTC), or a bare date (2024-01-15, treated as UTC midnight). Unparseable values return 400 invalid_request.
Window end, exclusive (before alignment). Same accepted formats as start. Must be strictly after start or the request is rejected with 400 invalid_request: end must be after start.
Zero-based outcome index (e.g. 0=Yes, 1=No for a binary market, or an index into a multi-outcome market's token list). Defaults to 0. Negative or non-integer values return 400 invalid_request.
x >= 0Set to binary to receive the columnar binary frame instead of JSON. Equivalent to sending Accept: application/x-kairos-candles.
binary Response
Candle series for the requested window. {"candles":[]} (empty array, no-store) is a valid 200 response, not an error — it means the window is authoritatively empty.
Response body of GET /v1/candles in JSON mode.
Candles in ascending bucket_start order. Empty when the aligned window is authoritatively empty.
Show child attributes
Show child attributes
Was this page helpful?
curl --request GET \
--url https://md.kairos.trade/v1/candles \
--header 'X-Api-Key: <api-key>' \
--header 'X-Api-Secret: <api-key>' \
--header 'X-Client-Id: <api-key>'import requests
url = "https://md.kairos.trade/v1/candles"
headers = {
"X-Client-Id": "<api-key>",
"X-Api-Key": "<api-key>",
"X-Api-Secret": "<api-key>"
}
response = requests.get(url, headers=headers)
print(response.text)const options = {
method: 'GET',
headers: {
'X-Client-Id': '<api-key>',
'X-Api-Key': '<api-key>',
'X-Api-Secret': '<api-key>'
}
};
fetch('https://md.kairos.trade/v1/candles', options)
.then(res => res.json())
.then(res => console.log(res))
.catch(err => console.error(err));<?php
$curl = curl_init();
curl_setopt_array($curl, [
CURLOPT_URL => "https://md.kairos.trade/v1/candles",
CURLOPT_RETURNTRANSFER => true,
CURLOPT_ENCODING => "",
CURLOPT_MAXREDIRS => 10,
CURLOPT_TIMEOUT => 30,
CURLOPT_HTTP_VERSION => CURL_HTTP_VERSION_1_1,
CURLOPT_CUSTOMREQUEST => "GET",
CURLOPT_HTTPHEADER => [
"X-Api-Key: <api-key>",
"X-Api-Secret: <api-key>",
"X-Client-Id: <api-key>"
],
]);
$response = curl_exec($curl);
$err = curl_error($curl);
curl_close($curl);
if ($err) {
echo "cURL Error #:" . $err;
} else {
echo $response;
}package main
import (
"fmt"
"net/http"
"io"
)
func main() {
url := "https://md.kairos.trade/v1/candles"
req, _ := http.NewRequest("GET", url, nil)
req.Header.Add("X-Client-Id", "<api-key>")
req.Header.Add("X-Api-Key", "<api-key>")
req.Header.Add("X-Api-Secret", "<api-key>")
res, _ := http.DefaultClient.Do(req)
defer res.Body.Close()
body, _ := io.ReadAll(res.Body)
fmt.Println(string(body))
}HttpResponse<String> response = Unirest.get("https://md.kairos.trade/v1/candles")
.header("X-Client-Id", "<api-key>")
.header("X-Api-Key", "<api-key>")
.header("X-Api-Secret", "<api-key>")
.asString();require 'uri'
require 'net/http'
url = URI("https://md.kairos.trade/v1/candles")
http = Net::HTTP.new(url.host, url.port)
http.use_ssl = true
request = Net::HTTP::Get.new(url)
request["X-Client-Id"] = '<api-key>'
request["X-Api-Key"] = '<api-key>'
request["X-Api-Secret"] = '<api-key>'
response = http.request(request)
puts response.read_body
