> ## Documentation Index
> Fetch the complete documentation index at: https://docs.kairos.trade/llms.txt
> Use this file to discover all available pages before exploring further.

# Find markets by what their book and tape are doing

> Screens the whole live catalogue by spread, resting depth, rolling volume, open interest and price, rather than by text. Numbers are live from the market-vitals cache (sub-second book and tape), joined to the discover cache for titles; a market the discover cache has never seen is still returned, under its id, with `hasMetadata: false`.

Prices and spread are in cents, money in dollars. **At least one metric bound is required** — an unfiltered screen is a ranking, not a screen, and is rejected with 400.

`spread` is `ask - bid` on one outcome, and a book with only one side cannot be crossed, so it has no spread: any query bounding spread only matches markets quoting both sides. `two_sided` asks for that explicitly without bounding the width. Bounds are compared on one canonical price scale, so a threshold means the same width on every venue whatever scale that venue publishes.



## OpenAPI

````yaml /openapi/data-api.yaml get /search/screener
openapi: 3.1.0
info:
  title: Kairos Data API
  version: 1.0.0
  summary: >-
    The full Kairos data plane — markets, candles, trades, search, discover,
    sports, trader analytics, and PnL.
  description: |
    The Kairos Data API at `data.kairos.trade` is the platform's primary data
    plane: market metadata and prices, OHLCV candles, live venue trade proxies,
    normalized trade history, full-text search, discovery feeds, the sports
    catalog, public trader analytics, and account PnL.

    ## Authentication

    Most endpoints accept a Kairos **API key** (`X-Client-Id` + `X-Api-Key` +
    `X-Api-Secret`, all three) or a first-party session JWT. Endpoints tagged
    `public` (trader analytics, provider configs, several sports feeds) need
    no credentials at all. Some endpoints additionally require an API-key
    **scope** (`trade:read`, `position:read`) — stated per operation.

    For high-volume market-data consumption (candles/trades/metadata at
    scale), prefer the dedicated **Market Data API** at `md.kairos.trade` —
    it has higher budgets, ETag caching, and a binary candle format.

    ## Rate limits

    Rate limits use a sliding window keyed on the authenticated user (JWT
    `sub`) or, for anonymous callers, the trusted client IP. Routes belong to
    a named **bucket** (`x-kairos-bucket`) whose per-minute budget is shared
    by every route in it; routes with no bucket run under the service default
    of 100/minute. `x-kairos-rate-limit` states the bucket's compile-time
    default — operators can raise or lower a bucket at runtime, so treat the
    documented number as the baseline, not a contract. 429 responses carry
    `Retry-After` and `X-RateLimit-*`.

    ## Conventions

    - Prices are on the **0–100 cents scale** unless a field says otherwise
      (trader-analytics position/trade prices use the 0–1 scale; each field's
      description states its scale).
    - Errors: `{"detail": "<message>"}` for handler errors; FastAPI's
      standard validation envelope for 422s. Errors surfaced from a venue
      adapter instead use `{"error", "provider", "operation", "message"}` —
      see `DataProviderError`. Unhandled failures always return the generic
      500 body; internal details are logged, never returned.
    - Every request body is capped at 8 MiB service-wide (413).
  contact:
    name: Kairos
    url: https://app.kairos.trade/docs/api-reference
  termsOfService: https://kairos.trade/terms
servers:
  - url: https://data.kairos.trade
    description: Production
  - url: https://staging-data.kairos.trade
    description: Staging
security:
  - apiKeyClientId: []
    apiKeyKey: []
    apiKeySecret: []
paths:
  /search/screener:
    get:
      tags:
        - Search
      summary: Find markets by what their book and tape are doing
      description: >-
        Screens the whole live catalogue by spread, resting depth, rolling
        volume, open interest and price, rather than by text. Numbers are live
        from the market-vitals cache (sub-second book and tape), joined to the
        discover cache for titles; a market the discover cache has never seen is
        still returned, under its id, with `hasMetadata: false`.


        Prices and spread are in cents, money in dollars. **At least one metric
        bound is required** — an unfiltered screen is a ranking, not a screen,
        and is rejected with 400.


        `spread` is `ask - bid` on one outcome, and a book with only one side
        cannot be crossed, so it has no spread: any query bounding spread only
        matches markets quoting both sides. `two_sided` asks for that explicitly
        without bounding the width. Bounds are compared on one canonical price
        scale, so a threshold means the same width on every venue whatever scale
        that venue publishes.
      operationId: searchScreener
      parameters:
        - name: min_spread
          in: query
          description: Cents. Only two-sided books can satisfy a spread bound.
          schema:
            type: number
            minimum: 0
            maximum: 100
        - name: max_spread
          in: query
          description: Cents.
          schema:
            type: number
            minimum: 0
            maximum: 100
        - name: min_price
          in: query
          description: Cents, on the matched outcome's mid.
          schema:
            type: number
            minimum: 0
            maximum: 100
        - name: max_price
          in: query
          description: Cents, on the matched outcome's mid.
          schema:
            type: number
            minimum: 0
            maximum: 100
        - name: min_volume_24h
          in: query
          description: Dollars traded in the rolling 24h window.
          schema:
            type: number
            minimum: 0
        - name: min_volume_1h
          in: query
          description: Dollars traded in the rolling 1h window.
          schema:
            type: number
            minimum: 0
        - name: min_liquidity
          in: query
          description: Dollars resting across every outcome, both sides.
          schema:
            type: number
            minimum: 0
        - name: max_liquidity
          in: query
          description: >-
            Dollars resting across every outcome, both sides. Pair with a volume
            floor to find thin books carrying heavy flow.
          schema:
            type: number
            minimum: 0
        - name: min_outcome_liquidity
          in: query
          description: Dollars resting on the matched outcome alone, both sides.
          schema:
            type: number
            minimum: 0
        - name: min_open_interest
          in: query
          description: Contracts outstanding.
          schema:
            type: number
            minimum: 0
        - name: two_sided
          in: query
          description: >-
            Only markets quoting a bid and an ask. A settled market has no
            quotes at all, which would otherwise read as infinitely thin.
          schema:
            type: boolean
            default: false
        - name: max_book_age_minutes
          in: query
          description: >-
            Drop markets whose top of book has not been published within this
            many minutes. Settled and delisted markets stop being streamed, so
            their frozen book would otherwise screen as a live quote — on a live
            catalogue that is more than half the records. `0` removes the bound
            and returns them.


            The timestamp is the streamer's publish clock, so this detects
            "nothing is publishing this market any more", not "the venue's book
            went quiet": a wedged venue connection re-publishing a cached book
            still stamps now.
          schema:
            type: number
            minimum: 0
            default: 10
        - name: provider
          in: query
          description: Repeatable. Restricts the screen to these venues.
          schema:
            type: array
            items:
              type: string
        - name: sort
          in: query
          schema:
            type: string
            default: volume_24h
            enum:
              - volume_24h
              - volume_1h
              - spread_desc
              - spread_asc
              - liquidity
              - open_interest
        - name: limit
          in: query
          schema:
            type: integer
            minimum: 1
            maximum: 500
            default: 50
        - name: offset
          in: query
          schema:
            type: integer
            minimum: 0
            maximum: 10000
            default: 0
      responses:
        '200':
          description: A page of matching markets, most relevant to the chosen sort first.
          content:
            application/json:
              schema:
                $ref: '#/components/schemas/ScreenerResponse'
        '400':
          description: >-
            No metric bound given, an inverted range, an unknown provider or
            sort, or a query the vitals service refused.
          content:
            application/json:
              schema:
                $ref: '#/components/schemas/SearchBadRequestError'
        '401':
          $ref: '#/components/responses/DataUnauthorized'
        '403':
          $ref: '#/components/responses/DataForbidden'
        '422':
          $ref: '#/components/responses/DataValidationError'
        '429':
          $ref: '#/components/responses/DataRateLimited'
        '500':
          description: Screener query failed.
          content:
            application/json:
              schema:
                $ref: '#/components/schemas/DataError'
components:
  schemas:
    ScreenerResponse:
      type: object
      required:
        - markets
        - total
        - available
      properties:
        markets:
          type: array
          items:
            $ref: '#/components/schemas/ScreenerMarket'
        total:
          type: integer
          description: >-
            Matches before paging. **-1** means the ordered walk stopped at the
            page bound and the rest were not counted — render it as "top N", not
            as a count.
        available:
          type: boolean
          description: >-
            False when the live vitals service could not be reached. Distinct
            from a screen that matched nothing, which is available with an empty
            `markets`.
        plan:
          type: string
          description: >-
            How the scan ran (`index_ordered`, `index_range`, `scan`).
            Diagnostic.
        limit:
          type: integer
        offset:
          type: integer
    SearchBadRequestError:
      type: object
      description: FastAPI `HTTPException(400)` body shape used across the search router.
      required:
        - detail
      properties:
        detail:
          type: string
          example: 'Invalid provider: acme'
    DataError:
      type: object
      description: Handler error envelope (FastAPI HTTPException).
      required:
        - detail
      properties:
        detail:
          type: string
          description: Human-readable error message.
          example: Invalid provider
    ScreenerMarket:
      type: object
      description: >-
        One screener hit — the market, and the outcome the screen matched on.
        Prices and spread are cents; money is dollars.
      required:
        - id
        - marketId
        - provider
      properties:
        id:
          type: string
          description: >-
            The id this market's book is published under. For Polymarket that is
            the venue condition id, not the market id search returns — see
            `marketId`.
        marketId:
          type: string
          nullable: true
          description: >-
            The id to address this market by everywhere else — the terminal, the
            market-data websocket, the trades API. Always present, and equal to
            `id` on every venue keyed by its market id. Null only on a
            Polymarket row the metadata cache could not resolve; a null row
            cannot be opened, and `id` is not a substitute for it.
        provider:
          type: string
        title:
          type: string
          nullable: true
          description: >-
            Null when the discover cache has never seen this market, alongside
            `hasMetadata` false. Presenting that — usually as the id — is the
            client's call.
        eventTitle:
          type: string
          nullable: true
        image:
          type: string
          nullable: true
        icon:
          type: string
          nullable: true
        category:
          type: string
          nullable: true
        status:
          type: string
          nullable: true
        hasMetadata:
          type: boolean
          description: False when only live numbers are known and the title is the id.
        outcomeIndex:
          type: integer
          nullable: true
          description: The outcome the quote figures below describe.
        matchedLeg:
          type: boolean
          description: >-
            True when the screen picked this outcome. False when the screen was
            contract-level only and this is just the primary outcome.
        outcomeName:
          type: string
          nullable: true
        bid:
          type: number
          nullable: true
          description: Cents.
        ask:
          type: number
          nullable: true
          description: Cents.
        mid:
          type: number
          nullable: true
          description: Cents.
        spread:
          type: number
          nullable: true
          description: Cents wide on this outcome; null when it is not quoting both sides.
        price:
          type: number
          description: Cents; the record's headline price.
        volume24h:
          type: number
          description: Dollars.
        volume1h:
          type: number
          description: Dollars.
        liquidity:
          type: number
          description: Dollars resting across every outcome, both sides.
        outcomeLiquidity:
          type: number
          nullable: true
          description: Dollars resting on this outcome alone, both sides.
        bookAgeSeconds:
          type: number
          nullable: true
          description: >-
            Seconds since this leg's top of book was published; null when it
            never has been. A large value means nothing is streaming this
            market.
        openInterest:
          type: number
          description: Contracts.
        openInterestNotional:
          type: number
          description: Dollars.
        updatedAt:
          type: string
          format: date-time
  responses:
    DataUnauthorized:
      description: >
        No valid credential presented — missing/invalid API-key headers, or an
        invalid/expired session token.
      content:
        application/json:
          schema:
            $ref: '#/components/schemas/DataError'
          example:
            detail: Not authenticated
    DataForbidden:
      description: >
        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.
      content:
        application/json:
          schema:
            $ref: '#/components/schemas/DataError'
          example:
            detail: Invite required
    DataValidationError:
      description: Request failed FastAPI parameter validation.
      content:
        application/json:
          schema:
            type: object
            required:
              - detail
            properties:
              detail:
                type: array
                description: >-
                  One entry per failed field, with location, message, and error
                  type.
                items:
                  type: object
                  additionalProperties: true
    DataRateLimited:
      description: >
        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.
      headers:
        Retry-After:
          schema:
            type: integer
        X-RateLimit-Limit:
          schema:
            type: integer
        X-RateLimit-Remaining:
          schema:
            type: integer
        X-RateLimit-Reset:
          schema:
            type: integer
      content:
        application/json:
          schema:
            type: object
            properties:
              error:
                type: string
                example: 'Rate limit exceeded: 100 per 1 minute'
  securitySchemes:
    apiKeyClientId:
      type: apiKey
      in: header
      name: X-Client-Id
      description: >-
        Credential client id (`kairos_ck_...`). Must be sent together with
        X-Api-Key and X-Api-Secret.
    apiKeyKey:
      type: apiKey
      in: header
      name: X-Api-Key
      description: 64-char hex API key.
    apiKeySecret:
      type: apiKey
      in: header
      name: X-Api-Secret
      description: 64-char hex API secret.

````

This documentation is built and hosted on [Mintlify](https://mintlify.com), a developer documentation platform.