---
title: "Fetch candles for up to 200 contracts in one request"
method: POST
path: "/candles/batch"
tags: ["Candles"]
---

# Fetch candles for up to 200 contracts in one request

`POST /candles/batch`

## Request

- Base URL: `https://data.kairos.trade`
- URL: `https://data.kairos.trade/candles/batch`
- Auth: API key in header `X-Client-Id` and API key in header `X-Api-Key` and API key in header `X-Api-Secret`

Batched form of `GET /candles`. Accepts up to 200 per-contract requests and returns results in the same order, index-tagged.

**Auth**: same as `GET /candles` — `require_auth_or_admin_or_apikey`, no scope required.

**Cache / data source**: each item is checked against the cache in parallel first; only uncached items are fetched fresh. Items with `rebuild: true` always skip the cache regardless of a hit.

**Fail-fast, partial-failure semantics**: an empty series for an item is returned as `"candles": []` (not an error) — the client renders an empty chart and expects later bars via the live WebSocket feed; there is no server-side ingestion retry. If fetching fails for some items but others were already served from cache, those cached items are still returned; every failed item instead gets `"candles": [], "error": "<message>"` so the client can distinguish a genuine failure from a legitimately empty window. Only if **no** item could be served at all does the whole request fail with 500.

**Validation** (all 400): body must contain a JSON array under `requests` (or the legacy alias `items`); the array must be non-empty and at most 200 items; each item must be an object with `provider`, `contract_id`, `timeframe_seconds`, `start`, and `end` present; the same per-field checks as `GET /candles` apply per item (unknown provider, invalid contract_id, disallowed timeframe, unparseable/out-of-order timestamps, negative or non-integer `outcome`).

## Request body

- CandlesBatchRequest: `requests` is the primary field name; the router also accepts a legacy `items` key with the identical array shape as a fallback if `requests` is absent.
  - `requests` CandlesBatchRequestItem[], required
    - `provider` string, required
    - `contract_id` string, required
    - `timeframe_seconds` 1 | 60 | 300 | 900 | 3600 | 14400 | 86400, required
    - `start` string, date-time, required
    - `end` string, date-time, required
    - `outcome` integer: Zero-based outcome index. Omitted/null defaults to 0; negative or non-integer values return 400.
    - `rebuild` boolean: Bypass the cache for this item and force a fresh fetch.

## Response `200`

Index-ordered results, one per request item. A failed item carries an `error` string and an empty `candles` array instead of a 5xx for the whole batch.

- CandlesBatchResponse
  - `results` CandlesBatchResultItem[], required: Index-ordered, one entry per request item.
    - `index` integer, required: Position of this result, matching the index of the corresponding item in the request's `requests` array.
    - `candles` CandlesCandle[], required
      - `contract_id` string, required: Contract/market identifier this bar belongs to.
      - `timeframe_seconds` integer, required: Bucket width in seconds.
      - `bucket_start` string, date-time, required: Bucket start time, ISO 8601 UTC.
      - `open` number, required: Opening price, 0-100 cents scale.
      - `high` number, required: High price in the bucket, 0-100 cents scale.
      - `low` number, required: Low price in the bucket, 0-100 cents scale.
      - `close` number, required: Closing price, 0-100 cents scale.
      - `volume` integer, required: Traded size (contracts/shares) within the bucket.
      - `token_id` string: Outcome token identifier, present only when this series is token-scoped (e.g. multi-outcome Polymarket markets).
    - `error` string: Present only when this specific item failed while other items in the batch succeeded from cache. `candles` is `[]` in that case.

## Other responses

- `400`: Malformed batch — `requests` missing/not an array/empty/over 200 items, or a specific item is malformed (missing required field, unknown provider, invalid contract_id, disallowed timeframe_seconds, unparseable/out-of-order start/end, non-integer or negative outcome).
- `401`: No valid credential presented — missing/invalid API-key headers, or an invalid/expired session token.
- `403`: Authenticated but not permitted. Three distinct causes: the session user is not invited (`Invite required`), the API key lacks the operation's scope, or API-key access to the requested venue is switched off (`API access is disabled for <provider>`). Admin and API-key callers bypass the invite check; session and admin callers bypass scope and venue checks.
- `413`: Request body exceeded the 8 MiB service-wide cap, refused before the handler ran.
- `422`: Request failed FastAPI parameter validation.
- `429`: Rate limit exceeded for this route's sliding window, keyed on the session `sub` when authenticated and on the trusted client IP otherwise. Honor `Retry-After`. A per-API-key data ceiling (set per credential) rejects with `{"detail": "API key data rate limit exceeded"}` instead of the `error` envelope below.
- `500`: Every request in the batch failed; nothing could be served from cache.

---

[API](https://skmtc.dev/kairos/apis/kairos-data-api.md) - [All operations](https://skmtc.dev/kairos/apis/kairos-data-api/llms.txt) - [OpenAPI document](https://skmtc.dev/kairos/apis/kairos-data-api/revisions/cf6b76079e38?raw)
