---
title: "Paginate active markets for one provider"
method: GET
path: "/v1/markets"
tags: ["Markets"]
---

# Paginate active markets for one provider

`GET /v1/markets`

## Request

- Base URL: `https://md.kairos.trade`
- URL: `https://md.kairos.trade/v1/markets`
- Auth: one of:
  - none
  - API key in header `X-Client-Id` and API key in header `X-Api-Key` and API key in header `X-Api-Secret`

Enumerates active markets for a single provider, cursor-paginated. Rate-limit bucket: LIGHT.

The response is ETagged (strong ETag) with a `Cache-Control` header; a matching `If-None-Match` returns 304.

Returns 503 (`cache_cold`) until the active-market listing has been populated for the requested provider at least once.

## Query parameters

- `provider` 'kalshi' | 'polymarket' | 'opinion' | 'predictfun' | 'hyperliquid' | 'kalshi_offchain' | 'dome', required
- `limit` integer
- `cursor` string

## Response `200`

Page of active markets.

- MarketListResponse: Response body for GET /v1/markets.
  - `exchange_id` string, required
  - `markets` Market[], required
    - `exchange_id` string, required: Canonical provider name.
    - `market_id` string, required
    - `condition_id` string, required: On-chain condition id for CTF venues; empty string for kalshi.
    - `event_id` string, required: Grouping event id (multi-market events); may be empty.
    - `title` string, required
    - `neg_risk` boolean, required: Whether this market is part of a Polymarket negative-risk group.
    - `tick_size` number, nullable: Minimum price increment (0, 1) exclusive.
    - `taker_base_fee_bps` integer, nullable
    - `fees_enabled` boolean, nullable
    - `category` string, nullable
    - `group_slug` string, nullable
    - `fee_type` string, nullable
    - `status` string, nullable: Venue-reported market status, e.g. active/closed. Populated inconsistently across providers.
    - `image` string, nullable: Display image URL.
    - `icon` string, nullable: Display icon URL.
    - `end_date` string, nullable: ISO8601 UTC market end/close time.
    - `open_time` string, nullable: ISO8601 UTC market open/listing time.
    - `resolution_status` string: Present only when the market has resolution state.
    - `payout_numerators` integer[]: Present only when non-empty (i.e. the market has resolved with a payout vector).
    - `resolved_ts` string: ISO8601 UTC. Present only when set.
    - `proposed_price` number: Present only when set (a resolution price has been proposed).
    - `challenge_window_ends_at` string: ISO8601 UTC. Present only when set.
    - `match_delay_ms` integer, nullable: Polymarket only (absent for other venues). How long Polymarket holds an order before matching it, in milliseconds. Sports markets hold every order for whole seconds (e.g. 1000 or 3000), and the value can change while a market trades, typically at game start; some crypto "Up or Down" markets hold taker orders for 250. When both apply, this is the longer one. 0 means no delay; null means Kairos has not observed this market's delay yet.
    - `outcomes` MarketOutcome[], required
      - `outcome` string, required: Display-layer outcome label as provided by the venue. For kalshi, this can legitimately be duplicated across YES/NO (it's a team/threshold subtitle, not a binary identity) — use `outcome_index`/`side` for execution logic on those markets.
      - `normalized_outcome` string, required: Lowercased/normalized form of `outcome`.
      - `token_id` string, required: Venue-specific outcome/token identifier.
      - `outcome_index` integer: Stable binary position (0 or 1). Only present for providers keyed directly by market id (e.g. kalshi), and only for the first two outcome entries.
      - `side` 'yes' | 'no': Stable binary side matching `outcome_index`. Same provider scope as `outcome_index`.
    - `raw` object, required: Full, unmodified provider-specific market document. Shape varies by provider. Treat as opaque/pass-through.
  - `count` integer, required: Number of markets in this page (`markets.length`).
  - `next_cursor` string, nullable, required: Opaque cursor for the next page, or null when this is the last page.
  - `has_more` boolean, required

## Other responses

- `304`: Not Modified — `If-None-Match` matched the current ETag.
- `400`: Missing/unknown `provider`, or `limit` out of range.
- `401`: No valid credential presented. Every path returns `code: unauthorized`; the triggers differ: - An `Authorization` header on a deployment where bearer auth is not enabled (`bearer authentication is not enabled`). - A bearer token that fails verification (`invalid or expired session`), or whose token/session has been revoked (`session has been revoked`). - An incomplete or wrong API-key header set. Sending *any* of `X-Client-Id`, `X-Api-Key`, `X-Api-Secret` commits the request to the API-key path, so a partial set is rejected rather than silently degrading to anonymous limits (`valid X-Client-Id, X-Api-Key, and X-Api-Secret headers are required`). - No credential material at all while the anonymous free tier is unavailable — switched off for the deployment, forced off through the runtime kill switch, or not yet resolvable on a replica that has never read the flag (it fails closed).
- `403`: API key presented from a source IP not in that credential's whitelist.
- `404`: The upstream metadata cache answered the listing request with 404.
- `429`: Two different gates return this status, both with `code: rate_limited`: - **Per-credential (or per-IP, when anonymous) weighted budget exhausted.** The message is `rate limit exceeded`, or, for anonymous callers, `anonymous rate limit exceeded — request an API key for higher limits (…)`. `Retry-After` is the whole number of seconds until the current one-minute window rolls over, floored at 1. All four `X-RateLimit-*` headers are present. - **Per-IP admission gate.** An in-process, pre-authentication guard that keeps a flood off the shared credential cache and database. The message is `too many requests from this address` and `Retry-After` is a flat `60`. Because this fires *before* authentication, the response carries **no** `X-RateLimit-*` headers at all.
- `500`: `internal` / `authentication backend unavailable` — the API-key path could not reach the credential store.
- `502`: The metadata cache answered with an unexpected status (`upstream` / `metadata cache request failed`).
- `503`: `cache_cold` — the provider's active-market listing has not been populated yet (`Retry-After: 5`); `unavailable` — not configured for this deployment; or `rate_limiter_unavailable`.

---

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