Fetch OHLCV candles for a single contract
Returns OHLCV candle bars for one (provider, contract_id, outcome) series over [start, end), bucketed at timeframe_seconds.
Auth: accepts an admin secret (X-Admin-Secret), an API key (X-Client-Id + X-Api-Key + X-Api-Secret), or a first-party JWT (Authorization: Bearer or turnkey_session_jwt cookie). No API-key scope is required for this endpoint — any valid credential can read candles.
Cache / data source: responses may be served from cache unless rebuild=true, which always bypasses the cache and reconstructs candles from the underlying trade/candle data, then repopulates the cache. rebuild=true is for callers that explicitly want fresh data regardless of what’s cached.
Validation (all return 400): unknown provider (must exist in the live provider registry — currently includes kalshi, polymarket, dome, opinion); empty or >128-char contract_id; timeframe_seconds not one of the fixed allowed values; unparseable start/end (ISO 8601); or end <= start. An outcome index invalid for the given provider/contract (e.g. an out-of-range index on a binary market) also returns 400.
curl --request GET \
--url https://data.kairos.trade/candles \
--header 'X-Api-Key: <api-key>' \
--header 'X-Api-Secret: <api-key>' \
--header 'X-Client-Id: <api-key>'import requests
url = "https://data.kairos.trade/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://data.kairos.trade/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://data.kairos.trade/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://data.kairos.trade/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://data.kairos.trade/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://data.kairos.trade/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{
"candles": [
{
"contract_id": "KXPRESPOLAND-24-DT",
"timeframe_seconds": 60,
"bucket_start": "2026-07-21T14:32:00+00:00",
"open": 63.5,
"high": 64,
"low": 63,
"close": 63.8,
"volume": 1250,
"token_id": "71321045679252212594626385532706912750332728571942532289631379312455583992563"
}
]
}{
"detail": "Invalid timeframe. Must be one of: [1, 60, 300, 900, 3600, 14400, 86400]"
}{
"detail": "Not authenticated"
}{
"detail": "Invite required"
}{
"detail": [
{}
]
}{
"error": "Rate limit exceeded: 100 per 1 minute"
}{
"detail": "Invalid provider"
}Authorizations
Credential client id (kairos_ck_...). Must be sent together with X-Api-Key and X-Api-Secret.
64-char hex API key.
64-char hex API secret.
Query Parameters
Venue identifier, matched case-insensitively against the live provider registry (e.g. kalshi, polymarket). Unknown providers return 400.
Provider-specific contract/market/token identifier. Whitespace is trimmed; empty or >128 chars returns 400.
1 - 128Candle bucket width in seconds. FastAPI first enforces >= 1; the handler then rejects any value not exactly one of [1, 60, 300, 900, 3600, 14400, 86400] (1s, 1m, 5m, 15m, 1h, 4h, 1d) with 400.
1, 60, 300, 900, 3600, 14400, 86400 Window start, ISO 8601 (any offset; normalized to UTC internally). Unparseable values return 400.
Window end, ISO 8601. Must be strictly after start (400 otherwise).
Zero-based outcome index for multi-outcome markets. Omitted/null is treated as 0 (the first/primary outcome). An index invalid for the contract's outcome count returns 400.
x >= 0When true, bypasses the cache and forces a fresh fetch/rebuild.
Response
OHLCV series for the requested window.
Show child attributes
Show child attributes
Was this page helpful?
curl --request GET \
--url https://data.kairos.trade/candles \
--header 'X-Api-Key: <api-key>' \
--header 'X-Api-Secret: <api-key>' \
--header 'X-Client-Id: <api-key>'import requests
url = "https://data.kairos.trade/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://data.kairos.trade/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://data.kairos.trade/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://data.kairos.trade/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://data.kairos.trade/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://data.kairos.trade/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{
"candles": [
{
"contract_id": "KXPRESPOLAND-24-DT",
"timeframe_seconds": 60,
"bucket_start": "2026-07-21T14:32:00+00:00",
"open": 63.5,
"high": 64,
"low": 63,
"close": 63.8,
"volume": 1250,
"token_id": "71321045679252212594626385532706912750332728571942532289631379312455583992563"
}
]
}{
"detail": "Invalid timeframe. Must be one of: [1, 60, 300, 900, 3600, 14400, 86400]"
}{
"detail": "Not authenticated"
}{
"detail": "Invite required"
}{
"detail": [
{}
]
}{
"error": "Rate limit exceeded: 100 per 1 minute"
}{
"detail": "Invalid provider"
}
