> ## 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.

# Current valid tick grid for one market

> Returns the grid of valid price increments the venue CURRENTLY enforces for a market — the same grid the order executor validates against. It describes the grid; it never snaps or rounds a price. Served from the market metadata cache, which the orderbook streamer keeps current with the venue's live tick changes. Rate-limit bucket: LIGHT.

- **polymarket** — a single flat range over `[0, 1]`; the venue flips 0.01↔0.001 at the price extremes. Pass `asset_id` (the CLOB token id) to read the per-token tick, which is where a live change lands first.
- **kalshi** — the market's `price_ranges` (a tapered grid). `min_tick` is the finest step. If only the deprecated flat `tick_size` is present the response is a single flat range flagged `synthetic: true`.
- **predictfun** — `supported: false`; the venue exposes no per-market tick. No grid is fabricated.

Query parameters and the response body are wire-compatible with the Data API's `GET /markets/tick-size`; `as_of` (when the grid was read from the metadata cache) is additive. `start` / `end` / `step` / `min_tick` are decimal strings so precision is never lost. Cached for 5 seconds (`public, max-age=5` with a strong ETag).



## OpenAPI

````yaml /openapi/market-data-api.yaml get /v1/markets/tick-size
openapi: 3.1.0
info:
  title: Kairos Market Data API
  version: 1.2.0
  summary: Read-only prediction and perpetual market data across venues.
  description: >
    The Kairos Market Data API provides read-only access

    to normalized prediction-market data across every venue Kairos integrates:

    OHLCV candles, the trade tape, market metadata, resolution lifecycles, and

    latest mark prices.


    ## No signup required


    Every endpoint works **without credentials** on the anonymous free tier,

    rate-limited per source IP (120 light units/min, 20 heavy units/min). Try
    any

    request from the interactive docs at

    [app.kairos.trade/docs/market-data](https://app.kairos.trade/docs/market-data)

    or straight from curl. For production budgets (1,200 light and 150 heavy

    units/min by default, raisable per credential), request an API key from the

    Kairos team.


    ## Authentication


    Three modes, resolved per request:


    - **Anonymous** — no credential headers at all. Free tier, IP-keyed limits.

    - **API key** — `X-Client-Id` + `X-Api-Key` + `X-Api-Secret` headers
      (all three; partial header sets are rejected with 401 rather than
      degrading to anonymous).
    - **First-party session** — `Authorization: Bearer` JWT (Kairos web app
      sessions only; not offered to API consumers).

    Resolution order is fixed: an `Authorization` header takes the bearer

    path, any of the three API-key headers takes the API-key path, and only

    a request carrying neither is eligible for the anonymous tier.


    On the API-key path, an unreachable credential store surfaces on **any**

    endpoint as `500` with `code: internal` and the message

    `authentication backend unavailable`. A source IP outside a credential's

    whitelist is `403 ip_not_whitelisted`; that is the only 403 this service

    emits.


    ## Rate limiting


    Weighted sliding-window budgets over one minute, in **units**, across two

    independent buckets: light (candles, metadata, resolutions, trade

    metrics) and heavy (trade history, marks, perpetual snapshots). Most

    requests cost 1 unit; larger requests may consume more of your quota —

    each operation's `x-kairos-rate-limit` states its exact cost model.

    Every admitted response carries `X-RateLimit-Bucket`, `X-RateLimit-Tier`,

    `X-RateLimit-Remaining`, and `X-RateLimit-Reset`; 429s add `Retry-After`.


    A separate per-IP admission gate runs *before* authentication to keep a

    flood off shared infrastructure. It also answers 429 `rate_limited`, but

    with `Retry-After: 60` and no `X-RateLimit-*` headers.


    Redis backs the weighted limiter and it fails **closed**: if it is

    unreachable the request is rejected with 503 `rate_limiter_unavailable`

    rather than let through unmetered.


    ## Conventions


    - **Prediction-market prices** are on the 0–100 scale (implied probability
      × 100) in the prediction candle, trade, and mark endpoints. Prediction
      USD notional = `size × price / 100`.
    - **Perpetual prices are direct venue prices**, not probabilities and not
      0–100 values. Perpetual quantities retain their declared native unit
      (`base_asset` or `contracts`). Never divide a perpetual price by 100 or
      compute contract notional without the instrument's authoritative contract
      multiplier; the snapshot does not synthesize a cross-venue notional.
    - **Hyperliquid is two products on one venue.** Its HIP-4 outcome markets
      are prediction markets, read with `provider=hyperliquid` on the
      prediction endpoints; its perpetuals are bare coins read under
      `/v1/perpetuals/hyperliquid/{instrument}/snapshot`, where the instrument
      is the canonical id `hl-mainnet-<symbol>-<quote>` (e.g.
      `hl-mainnet-btc-usdt`). Different id schemes, different price semantics;
      they are never interchangeable.
    - **Errors** all use one envelope:
      `{"error": {"code": "...", "message": "..."}}`.
    - **Providers** are case-insensitive; `kalshi_offchain` aliases `kalshi`,
      `dome` aliases `polymarket`, and `opinion` is resolvable for historic
      reads only.
  contact:
    name: Kairos
    url: https://app.kairos.trade/docs/market-data
  termsOfService: https://kairos.trade/terms
servers:
  - url: https://md.kairos.trade
    description: Production
  - url: https://staging-md.kairos.trade
    description: Staging
security:
  - {}
  - apiKeyClientId: []
    apiKeyKey: []
    apiKeySecret: []
tags:
  - name: Candles
    description: OHLCV candle series (1s → 1d), JSON or compact columnar binary.
  - name: Trades
    description: Trade tape and aggregate volume metrics.
  - name: Markets
    description: Market metadata, batch lookup, identifier resolution, and enumeration.
  - name: Resolutions
    description: Resolution outcomes and UMA-style lifecycle state/timelines.
  - name: Marks
    description: Latest mark (last trade price) per outcome token.
  - name: Perpetuals
    description: Live perpetual books, trades, candles, funding, and market state. Beta.
  - name: Status
    description: Health and readiness probes.
paths:
  /v1/markets/tick-size:
    get:
      tags:
        - Markets
      summary: Current valid tick grid for one market
      description: >-
        Returns the grid of valid price increments the venue CURRENTLY enforces
        for a market — the same grid the order executor validates against. It
        describes the grid; it never snaps or rounds a price. Served from the
        market metadata cache, which the orderbook streamer keeps current with
        the venue's live tick changes. Rate-limit bucket: LIGHT.


        - **polymarket** — a single flat range over `[0, 1]`; the venue flips
        0.01↔0.001 at the price extremes. Pass `asset_id` (the CLOB token id) to
        read the per-token tick, which is where a live change lands first.

        - **kalshi** — the market's `price_ranges` (a tapered grid). `min_tick`
        is the finest step. If only the deprecated flat `tick_size` is present
        the response is a single flat range flagged `synthetic: true`.

        - **predictfun** — `supported: false`; the venue exposes no per-market
        tick. No grid is fabricated.


        Query parameters and the response body are wire-compatible with the Data
        API's `GET /markets/tick-size`; `as_of` (when the grid was read from the
        metadata cache) is additive. `start` / `end` / `step` / `min_tick` are
        decimal strings so precision is never lost. Cached for 5 seconds
        (`public, max-age=5` with a strong ETag).
      operationId: getMarketTickSize
      parameters:
        - name: provider
          in: query
          required: true
          schema:
            type: string
            enum:
              - kalshi
              - polymarket
              - predictfun
          example: kalshi
        - name: contract_id
          in: query
          required: true
          description: Kalshi ticker, or Polymarket condition id / market id.
          schema:
            type: string
          example: KXBTC15M-26JUL221600-00
        - name: asset_id
          in: query
          required: false
          description: >-
            Polymarket CLOB token id. Enables the per-token read; ignored for
            other providers.
          schema:
            type: string
      responses:
        '200':
          description: >-
            The market's current tick grid, or the explicit unsupported payload
            for predictfun.
          headers:
            ETag:
              schema:
                type: string
            Cache-Control:
              schema:
                type: string
                example: public, max-age=5
          content:
            application/json:
              schema:
                oneOf:
                  - $ref: '#/components/schemas/TickGrid'
                  - $ref: '#/components/schemas/TickGridUnsupported'
              examples:
                kalshi:
                  value:
                    provider: kalshi
                    contract_id: KXBTC15M-26JUL221600-00
                    asset_id: null
                    ranges:
                      - start: '0'
                        end: '0.04'
                        step: '0.001'
                      - start: '0.04'
                        end: '0.96'
                        step: '0.01'
                      - start: '0.96'
                        end: '1'
                        step: '0.001'
                    min_tick: '0.001'
                    source: kalshi_price_ranges
                    synthetic: false
                    price_level_structure: tapered
                    as_of: '2026-09-18T12:00:00Z'
                polymarket:
                  value:
                    provider: polymarket
                    contract_id: '0xabc123'
                    asset_id: >-
                      18812649149814341758733697580460697418474693998558159483117
                    ranges:
                      - start: '0'
                        end: '1'
                        step: '0.001'
                    min_tick: '0.001'
                    source: metadata_cache
                    synthetic: false
                    price_level_structure: null
                    as_of: '2026-09-18T12:00:00Z'
                predictfun:
                  value:
                    provider: predictfun
                    contract_id: '42'
                    supported: false
                    reason: >-
                      predict.fun exposes no per-market tick endpoint or tick
                      change event; treat its tick as static/default. See
                      docs/exchange-docs.
        '304':
          description: Not Modified — `If-None-Match` matched the current ETag.
        '400':
          description: >-
            Unknown provider, a provider with no per-market grid (hyperliquid,
            opinion), or an empty `contract_id`.
          content:
            application/json:
              schema:
                $ref: '#/components/schemas/ErrorResponse'
              example:
                error:
                  code: invalid_request
                  message: >-
                    Provider 'hyperliquid' has no per-market tick grid.
                    Supported: kalshi, polymarket (predictfun is static).
        '401':
          $ref: '#/components/responses/Unauthorized'
        '403':
          $ref: '#/components/responses/IPNotWhitelisted'
        '404':
          description: >-
            The metadata cache has no such market (after its ClickHouse and
            live-venue read-through).
          content:
            application/json:
              schema:
                $ref: '#/components/schemas/ErrorResponse'
              example:
                error:
                  code: not_found
                  message: market not found
        '429':
          $ref: '#/components/responses/RateLimited'
        '502':
          description: >-
            `tick_unavailable` — the market exists but carries no usable grid
            (no Polymarket tick; a Kalshi market whose cached `raw` is the
            catalog row rather than the venue object, so it has no
            `price_ranges`; or a venue object with neither `price_ranges` nor
            `tick_size`); `upstream` — the metadata cache answered with an
            unexpected status.
          content:
            application/json:
              schema:
                $ref: '#/components/schemas/ErrorResponse'
              example:
                error:
                  code: tick_unavailable
                  message: Kalshi market KXFOO has neither price_ranges nor tick_size
        '503':
          description: >-
            `cache_cold` — the metadata cache is still warming (with
            `Retry-After: 5`); `unavailable` — no metadata cache is configured;
            or `rate_limiter_unavailable`.
          headers:
            Retry-After:
              schema:
                type: string
              description: Present only for `cache_cold`.
          content:
            application/json:
              schema:
                $ref: '#/components/schemas/ErrorResponse'
components:
  schemas:
    TickGrid:
      type: object
      description: >-
        A market's current valid tick grid. Wire-compatible with the Data API
        `GET /markets/tick-size` response; `as_of` is additive.
      required:
        - provider
        - contract_id
        - asset_id
        - ranges
        - min_tick
        - source
        - synthetic
        - price_level_structure
        - as_of
      properties:
        provider:
          type: string
          enum:
            - kalshi
            - polymarket
        contract_id:
          type: string
        asset_id:
          type:
            - string
            - 'null'
          description: >-
            The Polymarket token id the grid was read for; null when the lookup
            was market-level.
        ranges:
          type: array
          items:
            $ref: '#/components/schemas/TickRange'
        min_tick:
          type: string
          description: >-
            The finest step across all bands — the smallest increment the market
            will ever accept.
          example: '0.001'
        source:
          type: string
          enum:
            - metadata_cache
            - kalshi_price_ranges
            - streamer_projection
          description: >-
            Which upstream fact the grid was built from. `streamer_projection`
            is the orderbook streamer's projected finest venue step, used for a
            Kalshi market whose live band layout is not currently cached.
        synthetic:
          type: boolean
          description: >-
            True when the grid is a single flat band standing in for a layout
            that is not currently known — the minimum tick is correct, the band
            boundaries are not described.
        price_level_structure:
          type:
            - string
            - 'null'
          description: Kalshi's `price_level_structure` when present; null otherwise.
        as_of:
          type: string
          format: date-time
          description: >-
            When this grid was read from the market metadata cache (UTC). The
            cache itself is kept current by the orderbook streamer's live
            tick-change ingestion.
    TickGridUnsupported:
      type: object
      description: >-
        Explicit "no per-market grid" answer for predictfun. No grid is
        fabricated.
      required:
        - provider
        - contract_id
        - supported
        - reason
      properties:
        provider:
          type: string
          enum:
            - predictfun
        contract_id:
          type: string
        supported:
          type: boolean
          enum:
            - false
        reason:
          type: string
    ErrorResponse:
      type: object
      description: Canonical error envelope emitted by every Market Data API endpoint.
      required:
        - error
      properties:
        error:
          type: object
          required:
            - code
            - message
          properties:
            code:
              type: string
              description: Machine-readable error code.
              example: invalid_request
            message:
              type: string
              description: Human-readable detail.
              example: market_ids exceeds maximum 200
    TickRange:
      type: object
      description: >-
        One tick band. `step` applies for prices in `[start, end)`; the last
        band of a grid is inclusive of `end`. Decimal strings on the 0–1
        probability scale.
      required:
        - start
        - end
        - step
      properties:
        start:
          type: string
          example: '0.04'
        end:
          type: string
          example: '0.96'
        step:
          type: string
          example: '0.01'
  responses:
    Unauthorized:
      description: >
        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).
      headers:
        WWW-Authenticate:
          schema:
            type: string
          description: >-
            `Bearer realm="market-data-api"` on bearer-verification failure,
            `APIKey realm="market-data-api", header="X-Client-Id, X-Api-Key,
            X-Api-Secret"` on missing/invalid API-key credentials. Absent on the
            revoked-session and bearer-disabled paths.
      content:
        application/json:
          schema:
            $ref: '#/components/schemas/ErrorResponse'
          example:
            error:
              code: unauthorized
              message: >-
                valid X-Client-Id, X-Api-Key, and X-Api-Secret headers are
                required
    IPNotWhitelisted:
      description: API key presented from a source IP not in that credential's whitelist.
      content:
        application/json:
          schema:
            $ref: '#/components/schemas/ErrorResponse'
          example:
            error:
              code: ip_not_whitelisted
              message: source IP not in credential whitelist
    RateLimited:
      description: >
        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.
      headers:
        Retry-After:
          schema:
            type: integer
          description: >-
            Seconds until the caller may retry — window-rollover seconds for the
            weighted limiter, a flat 60 for the IP admission gate.
        X-RateLimit-Bucket:
          schema:
            type: string
            enum:
              - light
              - heavy
        X-RateLimit-Tier:
          schema:
            type: string
            enum:
              - anonymous
              - api-key
              - user
        X-RateLimit-Remaining:
          schema:
            type: integer
        X-RateLimit-Reset:
          schema:
            type: integer
            description: Unix timestamp (seconds) when the current window resets.
      content:
        application/json:
          schema:
            $ref: '#/components/schemas/ErrorResponse'
          example:
            error:
              code: rate_limited
              message: rate limit exceeded
  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. Must be sent together with X-Client-Id and
        X-Api-Secret.
    apiKeySecret:
      type: apiKey
      in: header
      name: X-Api-Secret
      description: >-
        64-char hex API secret. Must be sent together with X-Client-Id and
        X-Api-Key.

````

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