Appearance
Kalshi Search & Baskets
Discover, cluster, correlate, and assemble baskets across the live Kalshi prediction-market universe. These endpoints turn the open market set into a queryable database with three lenses — keyword search, embedding similarity, and time-series math — joinable in a single request, plus a composite layer that pushes portfolio construction (correlation-aware basket building, Kelly sizing, backtests) entirely server-side so a client can satisfy each high-level use case with one HTTP call.
Pair this surface with the Prediction Markets Agent for narrative analysis on individual events, and with Events History for per-event time series.
Like the Chat Completions and Responses endpoints, these are direct REST endpoints called relative to the Octagon API base URL:
text
https://api.octagonai.co/v1Authentication
Every request requires a valid Octagon API key passed as a Bearer token. Requests without one return 401.
Authorization: Bearer your-octagon-api-keyConventions
| Convention | Details |
|---|---|
| Path style | Kebab-case (e.g. /behavioral-clusters, /markets-with-edge, /cluster-peers). |
| Field style | snake_case in query strings, request bodies, and response payloads. |
| Timestamps | RFC 3339 UTC (2026-08-19T00:00:00Z). |
| Prices | 0–1 fraction units (Kalshi cents are normalized server-side). |
| Pagination | base64(JSON) cursor tokens. Pass next_cursor from a previous page back as cursor=.... |
| Unknown params | Return 400 with a list of unknown query parameter names. |
| Bad bodies | Pydantic validation errors return 422 for missing or wrongly-typed fields. |
Data model
The endpoints read from a set of tables maintained by the nightly Kalshi sync + clustering pipeline.
| Table | Purpose |
|---|---|
kalshi_markets_active | Live market universe. Includes a 256-d pgvector embedding and a GIN-indexed search_tsv TSVECTOR for full-text search. |
kalshi_events_active | Event metadata, including category / subcategory. |
kalshi_market_candles / kalshi_market_candles_daily | Hourly and daily OHLC bars. |
kalshi_clusters (+ assignments + runs) | Thematic K-means clusters over market embeddings, LLM-labeled, atomically swapped per nightly run. |
kalshi_behavioral_clusters (+ assignments + runs) | Behavioral K-means clusters over 30-day daily return vectors. Covers only markets with ≥14 days of daily candles. |
events_history | Per-run event snapshots — the same table that powers /prediction-markets/events. Drives /kalshi/markets-with-edge. |
Primitives
Single-purpose endpoints that a client can compose freely.
GET /kalshi/markets
Structured + full-text search over the open Kalshi universe.
Query parameters
| Parameter | Type | Required | Description |
|---|---|---|---|
q | string | No | Full-text query against search_tsv (title + subtitle + ticker). Results are ranked by ts_rank when supplied. Minimum 3 characters. |
category | string | No | Matches event category or subcategory (exact). |
series_ticker | string | No | Filter by Kalshi series. |
event_ticker | string | No | Filter to markets in a single event. |
close_before | datetime | No | Only markets closing on or before this RFC 3339 timestamp. |
min_volume_24h | float | No | Floor on volume_24h. |
limit | integer | No | Page size. Default 50; min 1; max 200. |
cursor | string | No | Pagination cursor returned from a previous response. |
Example
Python
import requests
url = "https://api.octagonai.co/v1/prediction-markets/kalshi/markets"
headers = {"Authorization": "Bearer your-octagon-api-key"}
params = {
"q": "bitcoin",
"category": "crypto",
"min_volume_24h": 10000,
"limit": 20,
}
response = requests.get(url, headers=headers, params=params)
response.raise_for_status()
print(response.json())JavaScript
const params = new URLSearchParams({
q: "bitcoin",
category: "crypto",
min_volume_24h: "10000",
limit: "20",
});
const response = await fetch(
`https://api.octagonai.co/v1/prediction-markets/kalshi/markets?${params}`,
{ headers: { Authorization: "Bearer your-octagon-api-key" } }
);
if (!response.ok) throw new Error(`Request failed: ${response.status}`);
console.log(await response.json());sh
curl -G "https://api.octagonai.co/v1/prediction-markets/kalshi/markets" \
-H "Authorization: Bearer your-octagon-api-key" \
--data-urlencode "q=bitcoin" \
--data-urlencode "category=crypto" \
--data-urlencode "min_volume_24h=10000" \
--data-urlencode "limit=20"Example response
json
{
"data": [
{
"market_ticker": "KXBTCD-26DEC31-T100000",
"event_ticker": "KXBTCD-26DEC31",
"series_ticker": "KXBTCD",
"title": "Bitcoin above $100k by end of Dec 2026",
"subtitle": null,
"status": "open",
"close_time": "2026-12-31T23:59:59Z",
"last_price": 0.58,
"yes_bid": 0.57,
"yes_ask": 0.59,
"no_bid": 0.41,
"no_ask": 0.43,
"volume": 1234,
"volume_24h": 1234,
"liquidity": 100,
"open_interest": 5,
"category": "crypto",
"event_name": "Bitcoin price ladders"
}
],
"next_cursor": null,
"has_more": false
}GET /kalshi/markets/similar
Cosine-distance similarity over kalshi_markets_active.embedding. Catches matches keyword search misses (e.g. "Will Bitcoin pierce six figures" ↔ "BTC above $100k") and is freely combinable with structured filters.
Query parameters — exactly one of anchor_ticker or q is required.
| Parameter | Type | Required | Description |
|---|---|---|---|
anchor_ticker | string | Cond. | Use the stored embedding of this market as the anchor. Zero added latency. Mutually exclusive with q. |
q | string | Cond. | Anchor by free-text query. Embedded at request time via text-embedding-3-small at dim=256. Adds one OpenAI roundtrip. Mutually exclusive with anchor_ticker. |
top_k | integer | No | Number of nearest neighbors. Default 25; min 1; max 100. |
category | string | No | Restrict to a category. |
min_volume_24h | float | No | Floor on volume_24h. |
close_before | datetime | No | Only markets closing before this RFC 3339 timestamp. |
Supplying neither or both of anchor_ticker / q returns 400.
Example — anchor by ticker (no OpenAI call)
Python
import requests
response = requests.get(
"https://api.octagonai.co/v1/prediction-markets/kalshi/markets/similar",
headers={"Authorization": "Bearer your-octagon-api-key"},
params={"anchor_ticker": "KXBTCD-26DEC31-T100000", "top_k": 10},
)
response.raise_for_status()
print(response.json())JavaScript
const params = new URLSearchParams({
anchor_ticker: "KXBTCD-26DEC31-T100000",
top_k: "10",
});
const response = await fetch(
`https://api.octagonai.co/v1/prediction-markets/kalshi/markets/similar?${params}`,
{ headers: { Authorization: "Bearer your-octagon-api-key" } }
);
console.log(await response.json());sh
curl -G "https://api.octagonai.co/v1/prediction-markets/kalshi/markets/similar" \
-H "Authorization: Bearer your-octagon-api-key" \
--data-urlencode "anchor_ticker=KXBTCD-26DEC31-T100000" \
--data-urlencode "top_k=10"Example — anchor by free-text query (embedded server-side)
sh
curl -G "https://api.octagonai.co/v1/prediction-markets/kalshi/markets/similar" \
-H "Authorization: Bearer your-octagon-api-key" \
--data-urlencode "q=Will Bitcoin pierce six figures" \
--data-urlencode "category=crypto" \
--data-urlencode "min_volume_24h=10000"Example response
json
{
"anchor_ticker": "KXBTCD-26DEC31-T100000",
"anchor_query": null,
"data": [
{
"market_ticker": "KXETHU-26DEC31-T10000",
"event_ticker": "KXETHU-26DEC31",
"title": "ETH above $10k by Dec 2026",
"category": "crypto",
"distance": 0.18
}
]
}Lower distance = closer cosine similarity.
GET /kalshi/clusters
Browse the current run of thematic (embedding-based) clusters. Each cluster has an LLM-generated label and description, the cluster size, and a few representative sample_titles.
Query parameters
| Parameter | Type | Required | Description |
|---|---|---|---|
limit | integer | No | Default 200; min 1; max 500. |
sample_titles | integer | No | Number of sample titles per cluster. Default 4; min 0; max 20. |
label_contains | string | No | Case-insensitive substring filter on the cluster label. |
Example
sh
curl -G "https://api.octagonai.co/v1/prediction-markets/kalshi/clusters" \
-H "Authorization: Bearer your-octagon-api-key" \
--data-urlencode "label_contains=fed" \
--data-urlencode "sample_titles=4"Example response
json
{
"data": [
{
"cluster_id": 42,
"label": "Fed decisions",
"description": "Markets resolving on FOMC actions and Fed policy outcomes",
"size": 14,
"sample_titles": [
"Will the Fed hike in March?",
"FOMC March: 25bp cut?"
],
"created_at": "2026-05-21T03:00:00Z"
}
]
}GET /kalshi/clusters/{cluster_id}/markets
Markets in one thematic cluster, ranked ascending by distance to the cluster centroid. Same response shape and cursor pagination as GET /kalshi/markets.
Example
sh
curl "https://api.octagonai.co/v1/prediction-markets/kalshi/clusters/42/markets?limit=20" \
-H "Authorization: Bearer your-octagon-api-key"GET /kalshi/behavioral-clusters
Same shape as /kalshi/clusters but over the behavioral cluster run — K-means on 30-day daily return vectors. Each row additionally exposes mean_daily_return and daily_volatility. Covers only the subset of markets with ≥14 days of daily candles (the threshold for behavioral eligibility).
Query parameters — identical to GET /kalshi/clusters.
Example
sh
curl "https://api.octagonai.co/v1/prediction-markets/kalshi/behavioral-clusters?limit=40" \
-H "Authorization: Bearer your-octagon-api-key"GET /kalshi/behavioral-clusters/{cluster_id}/markets
Members of one behavioral cluster, same response shape as the thematic version.
sh
curl "https://api.octagonai.co/v1/prediction-markets/kalshi/behavioral-clusters/4/markets?limit=20" \
-H "Authorization: Bearer your-octagon-api-key"GET /kalshi/markets/{market_ticker}/clusters
Return the thematic and behavioral cluster IDs a single market belongs to under the current runs. Useful for driving "no more than N legs from the same cluster" basket constraints client-side when not using /kalshi/baskets/build.
Example
sh
curl "https://api.octagonai.co/v1/prediction-markets/kalshi/markets/KXBTCD-26DEC31-T100000/clusters" \
-H "Authorization: Bearer your-octagon-api-key"Example response
json
{
"market_ticker": "KXBTCD-26DEC31-T100000",
"thematic": {
"cluster_id": 17,
"label": "Crypto price ladders",
"description": "BTC/ETH/SOL strike-ladder markets",
"size": 122
},
"behavioral": {
"cluster_id": 4,
"label": "Mean-reverting low-vol",
"size": 87,
"mean_daily_return": -0.002,
"daily_volatility": 0.018
}
}Either field can be null if the market isn't in a current-run assignment for that clustering kind.
POST /kalshi/markets/correlations
Pairwise Pearson correlation matrix over close-price series for N markets.
Request body
| Field | Type | Required | Description |
|---|---|---|---|
market_tickers | array of strings | Yes | 2–100 distinct market tickers. |
window_days | integer | Yes | Lookback window. Min 1, max 730. |
interval | string | No | "1h" or "1d". Auto-picked when omitted: 1d when window_days ≥ 90, else 1h. 1h reads hourly candles, 1d reads daily. |
Example
sh
curl -X POST "https://api.octagonai.co/v1/prediction-markets/kalshi/markets/correlations" \
-H "Authorization: Bearer your-octagon-api-key" \
-H "Content-Type: application/json" \
-d '{
"market_tickers": ["KXBTCD-26DEC31-T100000", "KXETHU-26DEC31-T10000", "KXSOL-26DEC31-T200"],
"window_days": 90,
"interval": "1d"
}'Example response
json
{
"tickers": ["KXBTCD-26DEC31-T100000", "KXETHU-26DEC31-T10000", "KXSOL-26DEC31-T200"],
"matrix": [
[1.0, 0.92, 0.81],
[0.92, 1.0, 0.74],
[0.81, 0.74, 1.0]
],
"ranked_pairs": [
{ "ticker_a": "KXBTCD-26DEC31-T100000", "ticker_b": "KXSOL-26DEC31-T200", "correlation": 0.81 },
{ "ticker_a": "KXETHU-26DEC31-T10000", "ticker_b": "KXSOL-26DEC31-T200", "correlation": 0.74 },
{ "ticker_a": "KXBTCD-26DEC31-T100000", "ticker_b": "KXETHU-26DEC31-T10000", "correlation": 0.92 }
],
"window_days": 90,
"interval": "1d",
"missing": []
}ranked_pairsis the upper-triangle of the matrix sorted ascending by correlation — the most-uncorrelated pairs come first.- Markets without candle data in the window appear in
missingand are dropped from the matrix. - Cells with fewer than 3 paired observations or constant series come back as
null.
POST /kalshi/baskets/candles
OHLC bars for a weighted basket NAV.
Request body
| Field | Type | Required | Description |
|---|---|---|---|
market_tickers | array of strings | Yes | Tickers to combine into the basket. |
weights | array of numbers | No | Per-ticker weights. Defaults to equal weight. Must match market_tickers length and sum to a positive value (renormalized server-side). |
timeframe | string | Yes | One of 1w, 1m, 3m, 6m, 1y. Drives both the lookback window and the candle bin size (see below). |
| Timeframe | Lookback | Bin |
|---|---|---|
1w | 7 days | 3h |
1m | 30 days | 12h |
3m | 90 days | 1d |
6m | 180 days | 3d |
1y | 365 days | 7d |
Example
sh
curl -X POST "https://api.octagonai.co/v1/prediction-markets/kalshi/baskets/candles" \
-H "Authorization: Bearer your-octagon-api-key" \
-H "Content-Type: application/json" \
-d '{
"market_tickers": ["KXBTCD-26DEC31-T100000", "KXETHU-26DEC31-T10000"],
"weights": [0.6, 0.4],
"timeframe": "1y"
}'Example response
json
{
"timeframe": "1y",
"interval_source": "1d",
"candles": [
{ "time": 1735776000, "open": 0.41, "high": 0.45, "low": 0.40, "close": 0.44 }
],
"tickers": ["KXBTCD-26DEC31-T100000", "KXETHU-26DEC31-T10000"],
"missing": []
}time values are Unix-seconds boundaries for each bin. Markets without data in the window appear in missing and are excluded from the basket NAV.
Composite endpoints
Higher-level endpoints that combine the primitives plus portfolio math server-side so a client can satisfy a use case in a single request.
GET /kalshi/markets/{market_ticker}/cluster-peers
One-call "show me others in the same theme" — replaces the two-step GET /markets/{ticker}/clusters → GET /clusters/{id}/markets dance.
Query parameters
| Parameter | Type | Required | Description |
|---|---|---|---|
kind | string | No | thematic (default) or behavioral. Selects the clustering to use. |
limit | integer | No | Number of peers, excluding the anchor. Default 50; min 1; max 200. |
Example
sh
curl "https://api.octagonai.co/v1/prediction-markets/kalshi/markets/KXBTCD-26DEC31-T100000/cluster-peers?kind=thematic&limit=50" \
-H "Authorization: Bearer your-octagon-api-key"Example response
json
{
"market_ticker": "KXBTCD-26DEC31-T100000",
"kind": "thematic",
"cluster": {
"cluster_id": 17,
"label": "Crypto price ladders",
"description": "BTC/ETH/SOL strike-ladder markets",
"size": 122
},
"data": [
{ "market_ticker": "KXETHU-26DEC31-T10000", "title": "ETH above $10k by Dec 2026", "distance": 0.04 }
]
}POST /kalshi/baskets/size
Apply fractional Kelly to a set of legs the caller has already picked. The server looks up each leg's live yes_bid / no_bid from kalshi_markets_active to determine entry price.
Kelly formula (binary outcome)
For a leg with entry price p (the bid for the chosen side) and model probability q:
edge_pp = (q - p) * 100
payoff b = (1 - p) / p
fraction = (b * q - (1 - q)) / b # classic Kelly
f_capped = min(max(fraction, 0), kelly_multiplier)Sums of f_capped are scaled down so total allocation stays within the multiplier cap. Legs with no edge (q < p) get kelly_fraction = 0.
Request body
| Field | Type | Required | Description |
|---|---|---|---|
bankroll_usd | number | Yes | Must be > 0. |
kelly_multiplier | number | Yes | 0 ≤ x ≤ 1. Cap on total bankroll fraction (e.g. 0.25 for "quarter Kelly"). |
legs | array of objects | Yes | 1–50 entries, each { "market_ticker": string, "side": "yes" | "no", "model_probability": number }. |
Example
sh
curl -X POST "https://api.octagonai.co/v1/prediction-markets/kalshi/baskets/size" \
-H "Authorization: Bearer your-octagon-api-key" \
-H "Content-Type: application/json" \
-d '{
"bankroll_usd": 1000.0,
"kelly_multiplier": 0.25,
"legs": [
{ "market_ticker": "KX-A", "side": "yes", "model_probability": 0.62 },
{ "market_ticker": "KX-B", "side": "no", "model_probability": 0.55 }
]
}'Example response
json
{
"bankroll_usd": 1000.0,
"kelly_multiplier": 0.25,
"total_notional": 168.50,
"legs": [
{
"market_ticker": "KX-A",
"side": "yes",
"model_probability": 0.62,
"price": 0.55,
"edge_pp": 7.0,
"kelly_fraction": 0.127,
"weight": 0.62,
"notional_usd": 104.45
}
]
}POST /kalshi/baskets/build
One-shot diversified basket builder. Pulls a candidate universe, annotates each candidate with its thematic cluster, computes correlations across the pool, greedily selects legs that respect both a per-cluster cap and a pairwise correlation cap, then sizes the legs by the chosen strategy.
Request body
| Field | Type | Required | Description |
|---|---|---|---|
universe | object | Yes | Filters that define the candidate pool. See below. |
n | integer | Yes | Number of legs requested. Min 1; max 20. |
max_per_cluster | integer | Yes | Caps how many legs may share a single thematic cluster. Min 1; max 20. |
max_pairwise_correlation | number | Yes | Range -1.0 to 1.0. Rejects any candidate whose worst pairwise correlation with already-accepted legs exceeds this value. |
candidate_pool_size | integer | Yes | How many markets to consider before greedy selection. Min 2; max 200. |
correlation_window_days | integer | Yes | Lookback for the correlation matrix used during selection. Min 7; max 365. Interval auto-picks 1d for ≥90 days, else 1h. |
sizing | object | Yes | { "strategy": "equal" } or { "strategy": "kelly", "bankroll_usd": ..., "kelly_multiplier": ..., "leg_probabilities": { "<market_ticker>": <probability> } }. Missing probabilities default to 0.5 (no edge → zero allocation). |
universe fields
| Field | Type | Description |
|---|---|---|
q | string | Free-text query — when set, the pool comes from semantic similarity via find_similar_markets. |
anchor_ticker | string | Semantic anchor — same effect as q, but no embedding roundtrip. |
category | string | Restrict to a category. |
series_ticker | string | Restrict to a series. |
min_volume_24h | number | Floor on volume_24h. |
close_before | datetime | Only markets closing before this RFC 3339 timestamp. |
label_contains_any | array | Restrict to markets whose thematic cluster label matches any of these substrings (e.g. ["fed", "cpi"]). |
When q / anchor_ticker is unset, the pool is structured-only via search_active_markets.
Example
Python
import requests
response = requests.post(
"https://api.octagonai.co/v1/prediction-markets/kalshi/baskets/build",
headers={
"Authorization": "Bearer your-octagon-api-key",
"Content-Type": "application/json",
},
json={
"universe": {"category": "crypto", "min_volume_24h": 10000},
"n": 8,
"max_per_cluster": 2,
"max_pairwise_correlation": 0.6,
"candidate_pool_size": 50,
"correlation_window_days": 60,
"sizing": {
"strategy": "kelly",
"bankroll_usd": 1000,
"kelly_multiplier": 0.25,
"leg_probabilities": {
"KXBTCD-26DEC31-T100000": 0.62,
"KXETHU-26DEC31-T10000": 0.58,
},
},
},
)
response.raise_for_status()
print(response.json())JavaScript
const response = await fetch(
"https://api.octagonai.co/v1/prediction-markets/kalshi/baskets/build",
{
method: "POST",
headers: {
Authorization: "Bearer your-octagon-api-key",
"Content-Type": "application/json",
},
body: JSON.stringify({
universe: { category: "crypto", min_volume_24h: 10000 },
n: 8,
max_per_cluster: 2,
max_pairwise_correlation: 0.6,
candidate_pool_size: 50,
correlation_window_days: 60,
sizing: {
strategy: "kelly",
bankroll_usd: 1000,
kelly_multiplier: 0.25,
leg_probabilities: {
"KXBTCD-26DEC31-T100000": 0.62,
"KXETHU-26DEC31-T10000": 0.58,
},
},
}),
}
);
if (!response.ok) throw new Error(`Request failed: ${response.status}`);
console.log(await response.json());sh
curl -X POST "https://api.octagonai.co/v1/prediction-markets/kalshi/baskets/build" \
-H "Authorization: Bearer your-octagon-api-key" \
-H "Content-Type: application/json" \
-d '{
"universe": {"category": "crypto", "min_volume_24h": 10000},
"n": 8,
"max_per_cluster": 2,
"max_pairwise_correlation": 0.6,
"candidate_pool_size": 50,
"correlation_window_days": 60,
"sizing": {
"strategy": "kelly",
"bankroll_usd": 1000,
"kelly_multiplier": 0.25,
"leg_probabilities": {
"KXBTCD-26DEC31-T100000": 0.62,
"KXETHU-26DEC31-T10000": 0.58
}
}
}'Example response
json
{
"legs": [
{
"market_ticker": "KXBTCD-26DEC31-T100000",
"title": "Bitcoin above $100k by end of Dec 2026",
"category": "crypto",
"cluster_id": 17,
"cluster_label": "Crypto price ladders",
"volume_24h": 50000,
"price": 0.58,
"side": "yes",
"model_probability": 0.62,
"kelly_fraction": 0.08,
"weight": 0.18,
"notional_usd": 76.40
}
],
"realized_max_pairwise_correlation": 0.54,
"cluster_breakdown": { "17": 2, "22": 2, "31": 1, "44": 1, "58": 2 },
"dropped": [
{ "market_ticker": "KX-X", "reason": "cluster_cap_reached" },
{ "market_ticker": "KX-Y", "reason": "correlation_above_threshold" }
],
"universe_size": 50
}realized_max_pairwise_correlation lets the caller verify the constraint was satisfied; dropped shows which candidates were rejected and why.
POST /kalshi/baskets/backtest
Superset of /kalshi/baskets/candles. Returns the same OHLC bars plus a summary block with backtest statistics computed over the basket NAV series. Request body is identical to /kalshi/baskets/candles.
Annualization details
Kalshi markets trade 24/7, so we annualize by calendar seconds rather than 252 trading days. The basket-candles bin size determines periods_per_year:
| Timeframe | Bin | periods_per_year |
|---|---|---|
1w | 3h | ~2922 |
1m | 12h | ~731 |
3m | 1d | ~365 |
6m | 3d | ~122 |
1y | 7d | ~52 |
sharpe = (mean_period_return / std_period_return) * sqrt(periods_per_year)whenstd > 0, elsenull.annualized_return = (1 + total_return) ** (periods_per_year / n_returns) - 1.max_drawdownis reported as a negative fraction (e.g.-0.092= 9.2% drawdown from peak).win_rateis the share of bin-to-bin returns that were positive.
Example
sh
curl -X POST "https://api.octagonai.co/v1/prediction-markets/kalshi/baskets/backtest" \
-H "Authorization: Bearer your-octagon-api-key" \
-H "Content-Type: application/json" \
-d '{
"market_tickers": ["KX-A","KX-B","KX-C"],
"weights": [0.4, 0.4, 0.2],
"timeframe": "1y"
}'Example response
json
{
"timeframe": "1y",
"interval_source": "1d",
"candles": [],
"tickers": ["KX-A", "KX-B", "KX-C"],
"missing": [],
"summary": {
"total_return": 0.247,
"annualized_return": 0.247,
"sharpe": 1.21,
"max_drawdown": -0.092,
"win_rate": 0.55,
"first_nav": 0.40,
"final_nav": 0.50,
"observation_count": 52
}
}GET /kalshi/clusters/ranked-by-return
"Show me thematic baskets that historically returned ≥ X%" in one call. For each current cluster, picks the top-N markets by volume_24h, builds an equal-weight basket, runs the backtest, and returns clusters whose total_return ≥ min_return, sorted descending by return.
Query parameters
| Parameter | Type | Required | Description |
|---|---|---|---|
timeframe | string | No | One of 1w, 1m, 3m, 6m, 1y (default 1y). |
min_return | number | No | Minimum total_return to include. Default 0.0. |
top_n_per_cluster | integer | No | Basket size per cluster. Default 5; min 1; max 20. |
kind | string | No | thematic (default) or behavioral. |
max_clusters | integer | No | Cap on how many clusters to evaluate (cost control). Default 50; min 1; max 200. |
Example
sh
curl -G "https://api.octagonai.co/v1/prediction-markets/kalshi/clusters/ranked-by-return" \
-H "Authorization: Bearer your-octagon-api-key" \
--data-urlencode "timeframe=1y" \
--data-urlencode "min_return=0.20" \
--data-urlencode "top_n_per_cluster=5"Example response
json
{
"timeframe": "1y",
"kind": "thematic",
"top_n_per_cluster": 5,
"min_return": 0.20,
"data": [
{
"cluster_id": 22,
"label": "AI infrastructure",
"description": "Markets resolving on AI infra capex, datacenter buildouts, and GPU shipments",
"size": 18,
"basket_tickers": ["KX-AI1", "KX-AI2", "KX-AI3", "KX-AI4", "KX-AI5"],
"summary": {
"total_return": 0.41,
"sharpe": 1.6,
"max_drawdown": -0.11
}
}
]
}GET /kalshi/markets-with-edge
Edge-vs-consensus ranking joined to the latest completed run from events_history (the same table that powers GET /prediction-markets/events). One-call recipe for "10 most-traded politics markets ranked by edge vs consensus".
Query parameters
| Parameter | Type | Required | Description |
|---|---|---|---|
run_id | string (UUID) | No | Defaults to the most recent events_history.run_id. |
category | string | No | Filter on series_category (case-insensitive). |
edge_pp_min | number | No | Lower bound on edge_pp (model probability minus market probability, in percentage points). |
edge_pp_max | number | No | Upper bound on edge_pp. |
expected_return_min | number | No | Floor on expected_return. |
total_volume_min | number | No | Floor on total_volume. |
model_probability_min | number | No | Floor on model_probability. |
sort_by | string | No | One of edge_pp (default), expected_return, total_volume, model_probability. Descending. |
limit | integer | No | Default 50; min 1; max 200. |
cursor | string | No | Pagination cursor. |
Example
Python
import requests
response = requests.get(
"https://api.octagonai.co/v1/prediction-markets/kalshi/markets-with-edge",
headers={"Authorization": "Bearer your-octagon-api-key"},
params={"category": "politics", "sort_by": "edge_pp", "limit": 10},
)
response.raise_for_status()
print(response.json())JavaScript
const params = new URLSearchParams({
category: "politics",
sort_by: "edge_pp",
limit: "10",
});
const response = await fetch(
`https://api.octagonai.co/v1/prediction-markets/kalshi/markets-with-edge?${params}`,
{ headers: { Authorization: "Bearer your-octagon-api-key" } }
);
console.log(await response.json());sh
curl -G "https://api.octagonai.co/v1/prediction-markets/kalshi/markets-with-edge" \
-H "Authorization: Bearer your-octagon-api-key" \
--data-urlencode "category=politics" \
--data-urlencode "sort_by=edge_pp" \
--data-urlencode "limit=10"Example response
json
{
"run_id": "8c0e1b9a-1f44-4d2a-9b7e-2d6e5f50a912",
"captured_at": "2026-05-21T03:00:00Z",
"sort_by": "edge_pp",
"data": [
{
"event_ticker": "KXFEDCUT-26JUN",
"title": "Will the Fed cut rates in June?",
"series_category": "politics",
"model_probability": 0.62,
"market_probability": 0.55,
"edge_pp": 7.0,
"expected_return": 0.12,
"confidence_score": 0.8,
"total_volume": 124300,
"total_open_interest": 5400
}
],
"next_cursor": null,
"has_more": false
}If filters reject every row but the run exists, captured_at still reflects the run's snapshot age.
Use-case recipes
Each recipe is satisfied by a single HTTP call unless explicitly noted. All commands assume BASE=https://api.octagonai.co and a valid OCTAGON_API_KEY.
1. Keyword search — markets matching "BTC 100k"
sh
curl -G "$BASE/v1/prediction-markets/kalshi/markets" \
-H "Authorization: Bearer $OCTAGON_API_KEY" \
--data-urlencode "q=BTC 100k"2. Semantic by ticker — markets similar to a known ticker
sh
curl "$BASE/v1/prediction-markets/kalshi/markets/similar?anchor_ticker=KXBTCD-26DEC31-T100000&top_k=25" \
-H "Authorization: Bearer $OCTAGON_API_KEY"3. Semantic by free text — "Will Bitcoin pierce six figures" finds "BTC over $100k"
sh
curl -G "$BASE/v1/prediction-markets/kalshi/markets/similar" \
-H "Authorization: Bearer $OCTAGON_API_KEY" \
--data-urlencode "q=Will Bitcoin pierce six figures" \
--data-urlencode "top_k=25"4. Filtered semantic — crypto markets thematically similar to ETH 2.0 staking, closing within 90 days, volume_24h > $10k
sh
curl -G "$BASE/v1/prediction-markets/kalshi/markets/similar" \
-H "Authorization: Bearer $OCTAGON_API_KEY" \
--data-urlencode "q=ETH 2.0 staking" \
--data-urlencode "category=crypto" \
--data-urlencode "close_before=2026-08-19T00:00:00Z" \
--data-urlencode "min_volume_24h=10000"5. Browse themes — clustered map of the universe
sh
curl "$BASE/v1/prediction-markets/kalshi/clusters?limit=40&sample_titles=4" \
-H "Authorization: Bearer $OCTAGON_API_KEY"Render label, description, size, sample_titles as a card grid. Drill in with /kalshi/clusters/{id}/markets.
6. Cluster browsing — "all markets in the Fed-decisions theme" (two calls)
sh
# Step 1: find the cluster ID by label
curl -G "$BASE/v1/prediction-markets/kalshi/clusters" \
-H "Authorization: Bearer $OCTAGON_API_KEY" \
--data-urlencode "label_contains=fed"
# Step 2: list the markets in it
curl "$BASE/v1/prediction-markets/kalshi/clusters/42/markets?limit=50" \
-H "Authorization: Bearer $OCTAGON_API_KEY"7. Fast first-pass dedup — "show me others in the same theme"
sh
curl "$BASE/v1/prediction-markets/kalshi/markets/KXBTCD-26DEC31-T100000/cluster-peers?kind=thematic&limit=50" \
-H "Authorization: Bearer $OCTAGON_API_KEY"8. Pairwise correlation — find uncorrelated bets
sh
curl -X POST "$BASE/v1/prediction-markets/kalshi/markets/correlations" \
-H "Authorization: Bearer $OCTAGON_API_KEY" \
-H "Content-Type: application/json" \
-d '{"market_tickers":["KXBTCD-26DEC31-T100000","KXETHU-26DEC31-T10000","KXSOL-26DEC31-T200"],"window_days":90,"interval":"1d"}'Read off ranked_pairs[0..k] for the most-uncorrelated pairs — no client-side sorting needed.
9. "Find me 5 uncorrelated bets on macro themes"
sh
curl -X POST "$BASE/v1/prediction-markets/kalshi/baskets/build" \
-H "Authorization: Bearer $OCTAGON_API_KEY" \
-H "Content-Type: application/json" \
-d '{
"universe": {"label_contains_any": ["fed", "cpi", "fomc", "gdp", "jobs"]},
"n": 5,
"max_per_cluster": 1,
"max_pairwise_correlation": 0.4,
"candidate_pool_size": 50,
"correlation_window_days": 90,
"sizing": {"strategy": "equal"}
}'10. "Build a $1000 basket on crypto"
sh
curl -X POST "$BASE/v1/prediction-markets/kalshi/baskets/build" \
-H "Authorization: Bearer $OCTAGON_API_KEY" \
-H "Content-Type: application/json" \
-d '{
"universe": {"category": "crypto", "min_volume_24h": 10000},
"n": 8,
"max_per_cluster": 2,
"max_pairwise_correlation": 0.6,
"candidate_pool_size": 50,
"correlation_window_days": 60,
"sizing": {
"strategy": "kelly",
"bankroll_usd": 1000,
"kelly_multiplier": 0.25,
"leg_probabilities": {"KXBTCD-26DEC31-T100000": 0.62, "KXETHU-26DEC31-T10000": 0.58}
}
}'leg_probabilities come from your edge engine. If you want Octagon-model probabilities, pull them from /kalshi/markets-with-edge first and feed them in.
11. "10 most-traded politics markets ranked by edge vs consensus"
sh
curl -G "$BASE/v1/prediction-markets/kalshi/markets-with-edge" \
-H "Authorization: Bearer $OCTAGON_API_KEY" \
--data-urlencode "category=politics" \
--data-urlencode "sort_by=edge_pp" \
--data-urlencode "limit=10"Swap sort_by=total_volume to literally rank by trading activity first.
12. "Thematic baskets that historically returned 20%+"
sh
curl -G "$BASE/v1/prediction-markets/kalshi/clusters/ranked-by-return" \
-H "Authorization: Bearer $OCTAGON_API_KEY" \
--data-urlencode "timeframe=1y" \
--data-urlencode "min_return=0.20" \
--data-urlencode "top_n_per_cluster=5"13. "Did this basket return 20%+?"
sh
curl -X POST "$BASE/v1/prediction-markets/kalshi/baskets/backtest" \
-H "Authorization: Bearer $OCTAGON_API_KEY" \
-H "Content-Type: application/json" \
-d '{"market_tickers":["KX-A","KX-B","KX-C"],"weights":[0.4,0.4,0.2],"timeframe":"1y"}'Read summary.total_return directly.
What the client owns
The API is intentionally permissive — these are the bits we don't (and can't) absorb server-side:
- Model probabilities for Kelly sizing. The client supplies them (typically pulled from
/kalshi/markets-with-edgeor from its own model). The Kelly endpoint never invents probabilities. - Order placement. Octagon doesn't trade your basket; that's a Kalshi-API concern.
- Presentation. Chart rendering, table layout, and natural-language framing of results.
Errors
| Status | Cause |
|---|---|
400 | Bad query parameters — unknown name, invalid kind / timeframe / sort_by, weights/tickers length mismatch, anchor_ticker + q both supplied, negative kelly_multiplier, and similar. |
401 | Missing or invalid Authorization header. |
422 | Pydantic validation failure on a request body (missing or wrongly-typed fields). |
502 | Upstream failure — typically an OpenAI embedding hiccup on GET /kalshi/markets/similar?q=..., or a candle-pipeline query error. The detail carries only the exception class name; full diagnostics are in server logs. |
503 | Kalshi search is unavailable — either kalshi_markets_active isn't populated yet (nightly sync job hasn't run) or q is shorter than 3 characters. The endpoint deliberately does not fall back to the legacy in-memory Kalshi-API scan, which was both slow and stale; resolving silently hid real problems. Confirm the sync schedule is enabled and the table has rows. |
All errors return a JSON body with a detail field describing the cause.
Related docs
- Prediction Markets Agent — the agent that produces narrative reports on individual events.
- Events History — per-event time-series snapshots from
events_history. - Prediction Markets Events — latest snapshot of every analyzed event. Powers the same underlying table as
/kalshi/markets-with-edge.