---
title: "Paginated, filtered, event-grouped market list for the Discover page"
method: GET
path: "/api/markets/discover/v2"
tags: ["Discover"]
---

# Paginated, filtered, event-grouped market list for the Discover page

`GET /api/markets/discover/v2`

## Request

- Base URL: `https://data.kairos.trade`
- URL: `https://data.kairos.trade/api/markets/discover/v2`
- Auth: API key in header `X-Client-Id` and API key in header `X-Api-Key` and API key in header `X-Api-Secret`

Returns a paginated, filtered, sorted, event-grouped market list for the Discover page, with server-side filtering (provider, category incl. alias remapping, free-text search, price/volume bounds, explicit market-id list, platform-tag/subcategory intersection, expiration window) and sorting. Most filter combinations are cacheable; free-text or bounded (price/volume) requests always run fresh and are not cached. Tag/subcategory filters that resolve to zero markets short-circuit to an empty payload; tag-filtered requests that would otherwise come back empty also get a best-effort fallback lookup so populated subtopics never render empty.

## Query parameters

- `offset` integer
- `limit` integer
- `sort_by` 'volume' | 'volume_24h' | 'volume_1h' | 'price' | 'newest' | 'liquidity' | 'rewards'
- `sort_order` 'asc' | 'desc'
- `provider` string
- `category` 'all' | 'politics' | 'crypto' | 'sports' | 'esports' | 'finance' | 'tech' | 'world' | 'weather' | 'elections' | 'geopolitics' | 'culture' | 'economy' | 'climate & science'
- `search` string
- `min_price` number
- `max_price` number
- `min_volume_1h` number
- `market_ids` string
- `tag_ids` string
- `tag_slug` string
- `subcategory` string
- `expiration_after` string, date-time
- `expiration_before` string, date-time
- `zipper` boolean

## Response `200`

Paginated market list (event-grouped where applicable).

- DiscoverV2Response
  - `markets` DiscoverMarket[], required
    - `id` string, required
    - `ticker` string, required
    - `event_id` string, nullable
    - `eventTitle` string: Grouped rows only.
    - `title` string, required
    - `outcomeLabel` string, nullable: Single (isGrouped=false) rows only.
    - `yes_sub_title` string, nullable: Single rows only.
    - `no_sub_title` string, nullable: Single rows only.
    - `price` number, required: Cents/100.
    - `volume` number
    - `volume24h` number
    - `volume1h` number: Dollars traded in the last hour, from our trade tape (or the venue's own hourly figure where it publishes one). 0 where neither reports an hour — that is "not measured", not "no trades". Rank on volume1hRank instead.
    - `volume1hRank` number: Ranking score for the hourly dimension — the hour above where it is known, the 24h volume spread over its hours where it is not. Never display it.
    - `liquidity` number
    - `rewardRate` number
    - `provider` string, required
    - `status` string, required
    - `expiration` string, date-time, nullable
    - `token_id` string, nullable: Single rows only.
    - `condition_id` string, nullable: Single rows only.
    - `category` string, nullable
    - `image` string, nullable
    - `icon` string, nullable
    - `isGrouped` boolean, required
    - `outcomes` DiscoverMarketOutcome[]: Grouped rows only.
      - `id` string
      - `ticker` string
      - `title` string
      - `outcomeLabel` string
      - `price` number: Cents/100.
      - `volume` number
      - `volumeTotal` number: Same value as volume.
      - `volume24h` number
      - `volume1h` number: Dollars traded in the last hour, from our trade tape (or the venue's own hourly figure where it publishes one). 0 where neither reports an hour — that is "not measured", not "no trades". Rank on volume1hRank instead.
      - `volume1hRank` number: Ranking score for the hourly dimension — the hour above where it is known, the 24h volume spread over its hours where it is not. Never display it.
      - `token_id` string, nullable
      - `condition_id` string, nullable
      - `rewardRate` number
  - `total` integer, required
  - `offset` integer, required
  - `limit` integer, required
  - `timestamp` string, required: ISO timestamp of the underlying cache build; empty string when the response falls back to a live query, or on the empty-filter short-circuit path.

## Other responses

- `400`: Invalid sort_by/sort_order/category/provider, invalid tag_slug/subcategory slug, invalid expiration ISO date, or oversized market_ids/tag_ids list.
- `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.
- `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`: Discover assembly failed.
- `503`: Discover data store is not ready.

---

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