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

# Live stream

> Quotes, trades, new relations and leads pushed as they are recorded, as
[Server-Sent Events](https://html.spec.whatwg.org/multipage/server-sent-events.html)
over one keyed connection. Authenticate as for any request; a browser
`EventSource` cannot send the key header, so connect from a server.

## Topics
- `quotes`: a market's top-of-book asks changed. Recorded on each crawl
  of a venue, so quotes arrive in bursts.
- `trades`: a public fill was recorded, usually within a minute of
  execution. `recorded_at` shows when; a recording backlog can deliver
  older fills.
- `books`: the top of a market's order book changed (best prices or
  the size at them), at most once a second per market, with mid,
  microprice, spread, 5-cent depth and imbalance. Only markets whose
  book is recorded from the venue feed appear; name them in
  `markets` to follow a few.
- `relations`: relations a finished graph run proved for the first
  time. A run that finds more than 10,000 new relations (a new graph
  version) sends one `relations_truncated` event instead; read them
  from the REST endpoints.
- `leads`: every lead of a finished graph run, up to 1,000, in the
  order of `/v1/opportunities`.
- `rules`: a market's terms changed: a new contract version replaced
  the previous one, a rulebook above it (series or event terms,
  resolution metadata) changed under an unchanged contract, or the
  venue's contract terms document changed. `rules_path` names where
  to read the versions and diffs.

`venue` and `markets` narrow quotes, trades, books and rule changes by
their market, and relations and leads by either side; `relations_truncated`
is always sent to relations streams. `min_usd` drops smaller trades.

## Resuming
Every data event has an `id`. After a drop, reconnect with the last id
in the `Last-Event-ID` header (or as `cursor`) and the same filters:
events after it are replayed in order, then the stream continues live,
with nothing missed or repeated. Ids are opaque, ordered strings. A
cursor from the last hour always resumes; an older or unknown cursor
gets a `reset` event and the stream continues from now.

## Connection
The stream opens with `ready`. A comment line (`: heartbeat`) is sent
every 15 seconds; treat 45 seconds of silence as a dead connection.
Streams end themselves after 45 to 55 minutes, and when a server
restarts or a client falls a full buffer behind, with a `reconnect`
event carrying the cursor to resume from. Reconnect after the `retry`
interval (3 seconds), backing off on errors.

## Limits
A key may hold one concurrent stream per 60 requests a minute of its
limit, at least 1 and at most 20 (per API instance). Opening a stream
counts as a request, and an open stream counts one more request per
minute against the daily quota; a stream whose key exhausts the quota
receives an `error` event and ends.




## OpenAPI

````yaml /openapi.yaml get /v1/stream
openapi: 3.1.0
info:
  title: Routeur Graph API
  version: '2026-09-15'
  summary: >-
    Proven relations between Kalshi and Polymarket contracts, with the history
    and signals built on them.
  description: >
    Routeur reads every open market on Kalshi and global Polymarket, and proves

    which ones imply, exclude or equal each other from each venue's structured

    contract terms and exact arithmetic. No relation is inferred from prose, and

    ambiguous or conflicting evidence is withheld.


    ## Authentication

    Send your key in the `X-API-Key` header, or as `Authorization: Bearer
    <key>`.

    Keys look like `rk_live_<12-character id>_<43-character secret>` and are

    shown once when issued. Keep them server-side.


    ## Rate limits

    Every key has a per-minute limit and a per-day quota set by its tier:


    | Tier | Requests per minute | Requests per day |

    |---|---|---|

    | free | 30 | 2,000 |

    | trader | 120 | 50,000 |

    | pro | 600 | 500,000 |

    | institutional | 3,000 | 10,000,000 |


    Responses carry `X-RateLimit-Limit`, `X-RateLimit-Remaining` and

    `X-RateLimit-Reset` (Unix seconds) for the minute window, and

    `X-Daily-Quota-Limit` and `X-Daily-Quota-Remaining` for the day (UTC).

    A request over either limit returns `429` with `Retry-After` in seconds.


    ## Conventions

    - Money and prices are decimal strings in USD (for example `"0.4100"`),
    never
      floats. Prices are per contract that pays $1.
    - Times are RFC 3339 in UTC.

    - Successful responses wrap results in `data`. Graph-derived responses also
      name the `graph_run` they were read from; runs finish hourly.
    - Paginated lists return `next_cursor` when more results exist. Pass it back
      as `cursor` unchanged; cursors are opaque.
    - Unknown or repeated query parameters are rejected with `400`, so typos
    never
      silently widen a query.

    ## Liquidity

    Every object that names a market carries its 24-hour volume, its spread and

    a `liquidity` grade, because a price nobody can trade at is not a price:


    | Grade | 24-hour volume or open interest | Round-trip spread |

    |---|---|---|

    | `deep` | $25,000 traded, or $250,000 open interest | 2¢ or less |

    | `tradeable` | $1,000 traded, or $25,000 open interest | 5¢ or less |

    | `thin` | anything less, or no quoted round trip | |


    Lists that suggest something to act on — price gaps, leads, movers, flows,

    large fills, closing soon, strategy matches — leave thin markets out by

    default, and rank what is left by the money behind it. Three parameters

    change that wherever it applies: `min_volume_usd`, `max_spread_usd` and

    `include_thin=true`, which drops both floors. Search and event listings show

    thin markets but rank them below what can be traded.


    ## Errors

    Errors return a JSON body `{"error": {"code": "...", "message": "..."}}`.

    Branch on `code`; messages may change.


    | Status | Code | Meaning |

    |---|---|---|

    | 400 | `invalid_parameter` | A query parameter is unknown, repeated or out
    of range. |

    | 400 | `invalid_market` | The venue is not `kalshi` or `polymarket`, or the
    market id is malformed. |

    | 400 | `invalid_rulebook` | The venue, rulebook kind or id is malformed. |

    | 401 | `api_key_required` | No key was sent. |

    | 401 | `invalid_api_key` | The key is malformed or unknown. |

    | 401 | `api_key_revoked` | The key was revoked. |

    | 404 | `market_not_found` | The market is not in the latest graph. |

    | 404 | `relation_not_found` | Both markets are listed, but no current proof
    relates them. |

    | 404 | `rulebook_not_found` | No such rulebook has been recorded. |

    | 404 | `book_not_found` | No order book is recorded for the market at that
    time. |

    | 404 | `not_found` | No such endpoint. |

    | 429 | `rate_limited` | The per-minute limit was exceeded. |

    | 429 | `daily_quota_exceeded` | The daily quota is used up. |

    | 429 | `too_many_streams` | The key already holds its concurrent live
    streams. |

    | 503 | `graph_unavailable` | No graph run has finished yet. |

    | 503 | `stream_unavailable` | No live stream capacity is free; retry
    shortly. |

    | 504 | `timeout` | The request took longer than 15 seconds. |

    | 500 | `internal` | Something failed on our side. |


    ## Webhooks

    Mispricing alerts are delivered as signed `POST` requests; see the

    `webhooks` section. Each request carries

    `Routeur-Signature: t=<unix seconds>,v1=<hex>`, where `v1` is

    HMAC-SHA256 of `<t>.<raw body>` keyed by your subscription's signing

    secret. Reject requests whose signature does not match or whose `t` is more

    than five minutes old, and respond `2xx` within 10 seconds.


    `Routeur-Delivery` identifies the delivery and stays the same across
    retries,

    so use it to ignore duplicates. Any response other than `2xx` or `410`, or
    no

    response, is retried with exponential backoff from 30 seconds for up to 8

    attempts; responding `410 Gone` disables the subscription. Endpoints must be

    HTTPS on a public address, and redirects are not followed.
  contact:
    name: Routeur
    url: https://routeur.app
servers:
  - url: https://api.routeur.app
security:
  - apiKey: []
  - bearer: []
tags:
  - name: Events
    description: Real-world events across both venues, the way people browse them.
  - name: Graph
    description: Markets and the relations proven between them.
  - name: History
    description: Recorded quotes and trades.
  - name: Rules
    description: |
      Contract terms and their history. Every version of every market's rule
      text is kept from the day it was first seen, along with the rulebooks
      above it (Kalshi series and event terms, Polymarket resolution metadata)
      and the venue's contract terms documents, with diffs between versions.
  - name: Books
    description: >-
      Order books recorded live from each venue's feed, their history, and the
      health of the feeds.
  - name: Signals
    description: Mispricing leads and unusual trading derived from the graph and history.
  - name: Live
    description: Quotes, trades, relations and leads pushed as they happen.
  - name: Service
    description: Service status and this document.
paths:
  /v1/stream:
    get:
      tags:
        - Live
      summary: Live stream
      description: >
        Quotes, trades, new relations and leads pushed as they are recorded, as

        [Server-Sent
        Events](https://html.spec.whatwg.org/multipage/server-sent-events.html)

        over one keyed connection. Authenticate as for any request; a browser

        `EventSource` cannot send the key header, so connect from a server.


        ## Topics

        - `quotes`: a market's top-of-book asks changed. Recorded on each crawl
          of a venue, so quotes arrive in bursts.
        - `trades`: a public fill was recorded, usually within a minute of
          execution. `recorded_at` shows when; a recording backlog can deliver
          older fills.
        - `books`: the top of a market's order book changed (best prices or
          the size at them), at most once a second per market, with mid,
          microprice, spread, 5-cent depth and imbalance. Only markets whose
          book is recorded from the venue feed appear; name them in
          `markets` to follow a few.
        - `relations`: relations a finished graph run proved for the first
          time. A run that finds more than 10,000 new relations (a new graph
          version) sends one `relations_truncated` event instead; read them
          from the REST endpoints.
        - `leads`: every lead of a finished graph run, up to 1,000, in the
          order of `/v1/opportunities`.
        - `rules`: a market's terms changed: a new contract version replaced
          the previous one, a rulebook above it (series or event terms,
          resolution metadata) changed under an unchanged contract, or the
          venue's contract terms document changed. `rules_path` names where
          to read the versions and diffs.

        `venue` and `markets` narrow quotes, trades, books and rule changes by

        their market, and relations and leads by either side;
        `relations_truncated`

        is always sent to relations streams. `min_usd` drops smaller trades.


        ## Resuming

        Every data event has an `id`. After a drop, reconnect with the last id

        in the `Last-Event-ID` header (or as `cursor`) and the same filters:

        events after it are replayed in order, then the stream continues live,

        with nothing missed or repeated. Ids are opaque, ordered strings. A

        cursor from the last hour always resumes; an older or unknown cursor

        gets a `reset` event and the stream continues from now.


        ## Connection

        The stream opens with `ready`. A comment line (`: heartbeat`) is sent

        every 15 seconds; treat 45 seconds of silence as a dead connection.

        Streams end themselves after 45 to 55 minutes, and when a server

        restarts or a client falls a full buffer behind, with a `reconnect`

        event carrying the cursor to resume from. Reconnect after the `retry`

        interval (3 seconds), backing off on errors.


        ## Limits

        A key may hold one concurrent stream per 60 requests a minute of its

        limit, at least 1 and at most 20 (per API instance). Opening a stream

        counts as a request, and an open stream counts one more request per

        minute against the daily quota; a stream whose key exhausts the quota

        receives an `error` event and ends.
      operationId: streamEvents
      parameters:
        - name: topics
          in: query
          required: true
          description: Comma-separated topics to receive.
          schema:
            type: string
            pattern: >-
              ^(quotes|trades|books|relations|leads|rules)(,(quotes|trades|books|relations|leads|rules))*$
          example: trades,leads
        - $ref: '#/components/parameters/VenueFilter'
        - name: markets
          in: query
          description: Up to 50 comma-separated markets as `venue:id`.
          schema:
            type: string
            maxLength: 10500
          example: >-
            kalshi:KXNFLSPREAD-26SEP14DENKC-KC21,polymarket:nfl-den-kc-2026-09-15-spread-away-1pt5
        - name: min_usd
          in: query
          description: Only trades of at least this notional, in USD.
          schema:
            type: integer
            minimum: 0
            maximum: 100000000
            default: 0
        - name: cursor
          in: query
          description: >-
            Resume after this event id. `Last-Event-ID` does the same; this
            parameter wins when both are sent.
          schema:
            type: string
            maxLength: 40
            pattern: ^[0-9]+(-[0-9]+)?$
        - name: Last-Event-ID
          in: header
          description: The id of the last event received, sent by SSE clients on reconnect.
          schema:
            type: string
            maxLength: 40
      responses:
        '200':
          description: >
            An event stream. Each event's `data` is one JSON object; the schema

            for each event name is in `x-events`: `ready` (StreamReady), `quote`

            (StreamQuote), `trade` (StreamTrade), `book` (StreamBook),
            `relation` (StreamRelation),

            `relations_truncated` (StreamRelationsTruncated), `lead`

            (StreamLead), `rule_change` (StreamRuleChange), `reset`

            (StreamNotice), `reconnect` (StreamReconnect) and `error`

            (StreamError).
          headers:
            X-RateLimit-Limit:
              $ref: '#/components/headers/X-RateLimit-Limit'
            X-RateLimit-Remaining:
              $ref: '#/components/headers/X-RateLimit-Remaining'
            X-RateLimit-Reset:
              $ref: '#/components/headers/X-RateLimit-Reset'
            X-Daily-Quota-Limit:
              $ref: '#/components/headers/X-Daily-Quota-Limit'
            X-Daily-Quota-Remaining:
              $ref: '#/components/headers/X-Daily-Quota-Remaining'
          content:
            text/event-stream:
              schema:
                type: string
              example: >
                retry: 3000


                event: ready

                data:
                {"topics":["trades","leads"],"heartbeat_seconds":15,"reconnect_after_seconds":2917,"replay_window_seconds":3600}


                id: 90412

                event: trade

                data:
                {"venue":"kalshi","id":"d9b1c2e4","market_id":"KXNFLSPREAD-26SEP14DENKC-KC21","outcome":"yes","taker_action":"buy","price_usd":"0.1800","quantity":"250","notional_usd":"45.00","executed_at":"2026-09-14T18:02:11Z","block":false,"recorded_at":"2026-09-14T18:02:40Z"}


                : heartbeat


                id: 90467-0

                event: lead

                data:
                {"graph_run":812,"relation":"contradicts","cross_venue":true,"left":{"venue":"kalshi","id":"KXNFLSPREAD-26SEP14DENKC-KC21","title":"Kansas
                City wins by over 20.5
                points?","rules_hash":"9f2c61d0a4be"},"right":{"venue":"polymarket","id":"nfl-den-kc-2026-09-15-spread-away-1pt5","title":"Spread:
                Broncos
                (-1.5)","rules_hash":"0c77e1b9d2aa"},"legs":[{"venue":"kalshi","market_id":"KXNFLSPREAD-26SEP14DENKC-KC21","side":"no","ask_usd":"0.83"},{"venue":"polymarket","market_id":"nfl-den-kc-2026-09-15-spread-away-1pt5","side":"no","ask_usd":"0.14"}],"cost_usd":"0.97","gross_edge_usd":"0.03","estimated_fee_usd":"0.01","net_edge_usd":"0.02","issues":["settle
                differently across venues"]}


                event: reconnect

                data: {"reason":"lifetime","cursor":"90467-0"}
        '400':
          $ref: '#/components/responses/BadRequest'
        '401':
          $ref: '#/components/responses/Unauthorized'
        '429':
          description: >-
            Rate limit, daily quota, or the key's concurrent streams
            (`too_many_streams`) exceeded.
          headers:
            Retry-After:
              description: Seconds to wait before retrying.
              schema:
                type: integer
          content:
            application/json:
              schema:
                $ref: '#/components/schemas/Error'
        '503':
          description: >-
            No stream capacity is free on the instance reached
            (`stream_unavailable`); retry after `Retry-After`.
          headers:
            Retry-After:
              description: Seconds to wait before retrying.
              schema:
                type: integer
          content:
            application/json:
              schema:
                $ref: '#/components/schemas/Error'
        default:
          $ref: '#/components/responses/Error'
components:
  parameters:
    VenueFilter:
      name: venue
      in: query
      description: Only results on this venue.
      schema:
        $ref: '#/components/schemas/Venue'
  headers:
    X-RateLimit-Limit:
      description: Requests allowed per minute for this key.
      schema:
        type: integer
    X-RateLimit-Remaining:
      description: Requests left in the current minute.
      schema:
        type: integer
    X-RateLimit-Reset:
      description: Unix time when the minute window resets.
      schema:
        type: integer
    X-Daily-Quota-Limit:
      description: Requests allowed per UTC day.
      schema:
        type: integer
    X-Daily-Quota-Remaining:
      description: Requests left today.
      schema:
        type: integer
  responses:
    BadRequest:
      description: Invalid parameter or market.
      content:
        application/json:
          schema:
            $ref: '#/components/schemas/Error'
          example:
            error:
              code: invalid_parameter
              message: 'Invalid or unsupported query parameter: limit.'
    Unauthorized:
      description: Missing, invalid or revoked API key.
      headers:
        WWW-Authenticate:
          schema:
            type: string
      content:
        application/json:
          schema:
            $ref: '#/components/schemas/Error'
          example:
            error:
              code: api_key_required
              message: Send an API key in the X-API-Key header or as a Bearer token.
    Error:
      description: Any other error.
      content:
        application/json:
          schema:
            $ref: '#/components/schemas/Error'
  schemas:
    Error:
      type: object
      additionalProperties: false
      required:
        - error
      properties:
        error:
          type: object
          additionalProperties: false
          required:
            - code
            - message
          properties:
            code:
              type: string
              description: A stable machine-readable code.
            message:
              type: string
              description: A human-readable explanation.
    Venue:
      type: string
      enum:
        - kalshi
        - polymarket
  securitySchemes:
    apiKey:
      type: apiKey
      in: header
      name: X-API-Key
    bearer:
      type: http
      scheme: bearer
      description: The same API key sent as a Bearer token.

````