---
title: "Get one market's metadata"
method: GET
path: "/v1/markets/{provider}/{market_id}"
tags: ["Markets"]
---

# Get one market's metadata

`GET /v1/markets/{provider}/{market_id}`

## Request

- Base URL: `https://md.kairos.trade`
- URL: `https://md.kairos.trade/v1/markets/{provider}/{market_id}`
- 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`

Returns the full metadata document for a single market. Rate-limit bucket: LIGHT.

Provider is resolved case-insensitively against the registry (aliases such as `kalshi_offchain` → `kalshi` and `dome` → `polymarket` are accepted and normalized before the lookup).

Response is ETagged (strong ETag) with a `Cache-Control` header; a matching `If-None-Match` returns 304. Responses ≥1KB are gzip-compressed when the client sends `Accept-Encoding: gzip`.

## Path parameters

- `provider` 'kalshi' | 'polymarket' | 'opinion' | 'predictfun' | 'hyperliquid' | 'kalshi_offchain' | 'dome', required
- `market_id` string, required

## Response `200`

Market metadata document.

- Market: Full market metadata document. Resolution-state fields (`resolution_status`, `payout_numerators`, `resolved_ts`, `proposed_price`, `challenge_window_ends_at`) are present in the payload only when set — the overwhelming majority of markets are unresolved and omit all five.
  - `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.

## Other responses

- `304`: Not Modified — `If-None-Match` matched the current ETag.
- `400`: Unknown provider.
- `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`: Market not found.
- `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`: Market lookup failed upstream for a reason other than not-found/cold/disabled.
- `503`: Either the market data source is still warming up (`cache_cold`, with a `Retry-After: 5` header — retry shortly), not configured for this deployment (`unavailable`), or the rate limiter is unreachable (`rate_limiter_unavailable`, fails closed).

---

[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)
