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

# Why two markets are related

> The proof trail behind a relation in the latest graph, stated from the
left market's side: both contract versions with the structured terms the
proof read (event, variable, Yes interval, rules hash, settlement), the
ordered proof steps, the evidence recorded when the relation was found,
and the graph runs that found and last confirmed it.

Steps are rebuilt from the stored contract versions with the same code
that builds the graph. `reproduced` is `false` when that rebuild no longer
yields the stored relation, for example because the venue changed
metadata outside the rules hash; the steps then end with
`not_reproduced` and the recorded conclusion.

Step kinds: `usable`, `same_event`, `same_variable`, `intervals`,
`mutually_exclusive`, `game_quantity`, `same_game`, `orient_margin`,
`econ_quantity`, `not_reproduced` and `conclusion`. Intervals use
interval notation over exact decimals: `(20.5, ∞)` is "more than 20.5".

Either market may be given first. `404` with `relation_not_found` means
both markets are listed but the latest graph does not relate their
current contract versions.




## OpenAPI

````yaml /openapi.yaml get /v1/relations/{left_venue}/{left_id}/{right_venue}/{right_id}
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/relations/{left_venue}/{left_id}/{right_venue}/{right_id}:
    parameters:
      - name: left_venue
        in: path
        required: true
        description: The left market's venue.
        schema:
          $ref: '#/components/schemas/Venue'
        example: kalshi
      - name: left_id
        in: path
        required: true
        description: The left market's id.
        schema:
          $ref: '#/components/schemas/MarketID'
        example: KXNFLSPREAD-26SEP14DENKC-KC21
      - name: right_venue
        in: path
        required: true
        description: The right market's venue.
        schema:
          $ref: '#/components/schemas/Venue'
        example: polymarket
      - name: right_id
        in: path
        required: true
        description: The right market's id.
        schema:
          $ref: '#/components/schemas/MarketID'
        example: nfl-den-kc-2026-09-15-spread-away-1pt5
    get:
      tags:
        - Graph
      summary: Why two markets are related
      description: >
        The proof trail behind a relation in the latest graph, stated from the

        left market's side: both contract versions with the structured terms the

        proof read (event, variable, Yes interval, rules hash, settlement), the

        ordered proof steps, the evidence recorded when the relation was found,

        and the graph runs that found and last confirmed it.


        Steps are rebuilt from the stored contract versions with the same code

        that builds the graph. `reproduced` is `false` when that rebuild no
        longer

        yields the stored relation, for example because the venue changed

        metadata outside the rules hash; the steps then end with

        `not_reproduced` and the recorded conclusion.


        Step kinds: `usable`, `same_event`, `same_variable`, `intervals`,

        `mutually_exclusive`, `game_quantity`, `same_game`, `orient_margin`,

        `econ_quantity`, `not_reproduced` and `conclusion`. Intervals use

        interval notation over exact decimals: `(20.5, ∞)` is "more than 20.5".


        Either market may be given first. `404` with `relation_not_found` means

        both markets are listed but the latest graph does not relate their

        current contract versions.
      operationId: getRelationProof
      responses:
        '200':
          description: The proof trail.
          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:
            application/json:
              schema:
                type: object
                additionalProperties: false
                required:
                  - data
                  - graph_run
                properties:
                  data:
                    $ref: '#/components/schemas/ProofTrail'
                  graph_run:
                    $ref: '#/components/schemas/GraphRunID'
              example:
                graph_run: 812
                data:
                  relation: contradicts
                  graph_relation: contradicts
                  direction: mutually_exclusive
                  cross_venue: true
                  left:
                    venue: kalshi
                    id: KXNFLSPREAD-26SEP14DENKC-KC21
                    event_id: KXNFLSPREAD-26SEP14DENKC
                    title: Kansas City wins by over 20.5 points?
                    yes_label: 'Yes'
                    no_label: 'No'
                    rules_hash: 9f2c61d0a4be
                    strike:
                      type: greater
                      floor: 41/2
                      rules_template_hash: 51c0e2
                    event_mutually_exclusive: false
                    variable: >-
                      NFL margin of Kansas City, Denver vs Kansas City,
                      2026-09-14 ET
                    interval: (20.5, ∞)
                    settlement:
                      payout_currency: USD
                      scope: the game's final score; neither venue restricts periods
                    first_seen_at: '2026-09-08T14:00:00Z'
                  right:
                    venue: polymarket
                    id: nfl-den-kc-2026-09-15-spread-away-1pt5
                    event_id: '99'
                    title: 'Spread: Broncos (-1.5)'
                    yes_label: Broncos
                    no_label: Chiefs
                    rules_hash: 0c77e1b9d2aa
                    strike:
                      type: greater
                      floor: 3/2
                      rules_template_hash: a1d4f0
                    event_mutually_exclusive: false
                    variable: NFL margin of Broncos, Broncos vs Chiefs, 2026-09-14 ET
                    interval: (1.5, ∞)
                    settlement:
                      payout_currency: USDC
                      scope: the game's final score; neither venue restricts periods
                      game_starts_at: '2026-09-15T00:15:00Z'
                    first_seen_at: '2026-09-09T02:30:00Z'
                  steps:
                    - step: game_quantity
                      left: >-
                        NFL margin of Kansas City, Denver vs Kansas City,
                        2026-09-14 ET
                      right: NFL margin of Broncos, Broncos vs Chiefs, 2026-09-14 ET
                      result: same_kind
                    - step: same_game
                      detail: >-
                        cross-venue: same nfl game scheduled 2026-09-14 ET,
                        matched by team codes and team names; both settle on the
                        game's final score; neither venue restricts periods;
                        postponement and cancellation settle differently
                      result: same
                    - step: orient_margin
                      detail: >-
                        the contracts name opposite teams' margins; Broncos's
                        margin (1.5, ∞) is (-∞, -1.5) on Kansas City's margin
                      right: (-∞, -1.5)
                      result: negated
                    - step: intervals
                      left: (20.5, ∞)
                      right: (-∞, -1.5)
                      result: contradicts
                    - step: conclusion
                      relation: contradicts
                  evidence:
                    - >-
                      cross-venue: same nfl game scheduled 2026-09-14 ET,
                      matched by team codes and team names; both settle on the
                      game's final score; neither venue restricts periods;
                      postponement and cancellation settle differently
                  reproduced: true
                  found_by:
                    id: 790
                    graph_version: contract-graph/v1
                    started_at: '2026-09-09T03:00:00Z'
                    finished_at: '2026-09-09T03:04:12Z'
                  confirmed_by:
                    id: 812
                    graph_version: contract-graph/v1
                    started_at: '2026-09-14T11:00:00Z'
                    finished_at: '2026-09-14T11:03:58Z'
                  first_seen_at: '2026-09-09T03:04:12Z'
                  last_confirmed_at: '2026-09-14T11:03:58Z'
        '400':
          $ref: '#/components/responses/BadRequest'
        '401':
          $ref: '#/components/responses/Unauthorized'
        '404':
          description: >-
            A market is not in the latest graph (`market_not_found`), or no
            current proof relates the two (`relation_not_found`).
          content:
            application/json:
              schema:
                $ref: '#/components/schemas/Error'
              example:
                error:
                  code: relation_not_found
                  message: >-
                    No current proof relates these markets: the latest graph
                    does not relate their current contract versions.
        '429':
          $ref: '#/components/responses/TooManyRequests'
        '503':
          $ref: '#/components/responses/Unavailable'
        default:
          $ref: '#/components/responses/Error'
components:
  schemas:
    Venue:
      type: string
      enum:
        - kalshi
        - polymarket
    MarketID:
      type: string
      pattern: ^[A-Za-z0-9][A-Za-z0-9_.-]{0,199}$
    ProofTrail:
      type: object
      additionalProperties: false
      required:
        - relation
        - graph_relation
        - direction
        - cross_venue
        - left
        - right
        - steps
        - evidence
        - reproduced
        - found_by
        - confirmed_by
        - first_seen_at
        - last_confirmed_at
      properties:
        relation:
          $ref: '#/components/schemas/RelationFromMarket'
        graph_relation:
          $ref: '#/components/schemas/GraphRelation'
        direction:
          type: string
          description: Which way the implication runs between left and right.
          enum:
            - left_implies_right
            - right_implies_left
            - both_ways
            - mutually_exclusive
            - neither
        cross_venue:
          type: boolean
        left:
          $ref: '#/components/schemas/ProofContract'
        right:
          $ref: '#/components/schemas/ProofContract'
        steps:
          type: array
          items:
            $ref: '#/components/schemas/ProofStep'
        evidence:
          type: array
          description: The evidence the graph recorded when it found the relation.
          items:
            type: string
        reproduced:
          type: boolean
          description: >-
            Rebuilding the graph from the stored contract versions yields the
            same relation.
        found_by:
          $ref: '#/components/schemas/ProofRun'
        confirmed_by:
          $ref: '#/components/schemas/ProofRun'
        first_seen_at:
          $ref: '#/components/schemas/Timestamp'
          description: When the run that found the relation finished.
        last_confirmed_at:
          $ref: '#/components/schemas/Timestamp'
          description: >-
            When the latest run that still lists both contract versions
            finished.
    GraphRunID:
      type: integer
      description: The graph run the response was read from.
      minimum: 1
    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.
    RelationFromMarket:
      type: string
      description: |
        A relation from the requested market's side. `implies`: if this market
        resolves Yes, the other does. `implied_by`: the reverse. `contradicts`:
        both cannot resolve Yes. `overlaps`: the outcome sets intersect without
        containment.
      enum:
        - equivalent
        - implies
        - implied_by
        - contradicts
        - overlaps
    GraphRelation:
      type: string
      description: A relation stated from the left market's side.
      enum:
        - equivalent
        - subset
        - superset
        - contradicts
        - overlaps
    ProofContract:
      type: object
      additionalProperties: false
      required:
        - venue
        - id
        - event_id
        - title
        - yes_label
        - no_label
        - rules_hash
        - event_mutually_exclusive
        - settlement
        - first_seen_at
      properties:
        venue:
          $ref: '#/components/schemas/Venue'
        id:
          $ref: '#/components/schemas/MarketID'
        event_id:
          type: string
        event_title:
          type: string
        title:
          type: string
        yes_label:
          type: string
        no_label:
          type: string
        rules_hash:
          type: string
          description: >-
            The contract version the proof holds for; a rules change is a new
            version.
        rules:
          type: string
        strike:
          $ref: '#/components/schemas/Strike'
        event_mutually_exclusive:
          type: boolean
        variable:
          type: string
          description: The quantity whose range the contract pays on, as the proof read it.
        interval:
          type: string
          description: The Yes range on that variable in interval notation.
        settlement:
          type: object
          additionalProperties: false
          properties:
            payout_currency:
              type: string
              enum:
                - USD
                - USDC
            scope:
              type: string
              description: >-
                What settlement reads: for a game, which periods count; for an
                economic variable, its source and observation time.
            closes_at:
              $ref: '#/components/schemas/Timestamp'
            game_starts_at:
              $ref: '#/components/schemas/Timestamp'
        first_seen_at:
          $ref: '#/components/schemas/Timestamp'
          description: When this contract version was first recorded.
    ProofStep:
      type: object
      additionalProperties: false
      required:
        - step
      properties:
        step:
          type: string
          enum:
            - usable
            - same_event
            - same_variable
            - intervals
            - mutually_exclusive
            - game_quantity
            - same_game
            - orient_margin
            - econ_quantity
            - not_reproduced
            - conclusion
        detail:
          type: string
        left:
          type: string
          description: What the left contract contributed to this step.
        right:
          type: string
          description: What the right contract contributed to this step.
        result:
          type: string
          description: What the step established, such as `same`, `subset` or `negated`.
        relation:
          $ref: '#/components/schemas/RelationFromMarket'
          description: 'On the conclusion: the relation from the left market''s side.'
    ProofRun:
      type: object
      additionalProperties: false
      required:
        - id
        - graph_version
        - started_at
        - finished_at
      properties:
        id:
          $ref: '#/components/schemas/GraphRunID'
        graph_version:
          type: string
        started_at:
          $ref: '#/components/schemas/Timestamp'
        finished_at:
          $ref: '#/components/schemas/Timestamp'
    Timestamp:
      type: string
      format: date-time
    Strike:
      type: object
      description: >-
        The market's payoff condition as a structured range on its underlying
        quantity.
      additionalProperties: false
      required:
        - type
        - rules_template_hash
      properties:
        type:
          type: string
          description: The venue's strike type, such as `greater`, `less` or `between`.
        floor:
          type: string
          description: Lower bound as an exact rational, such as `83/2`.
        cap:
          type: string
          description: Upper bound as an exact rational.
        custom:
          type: object
          additionalProperties:
            type: string
        functional:
          type: boolean
        rules_template_hash:
          type: string
          description: >-
            Fingerprint of the rules with bounds masked; markets compare only
            within one template.
  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.
    TooManyRequests:
      description: Rate limit or daily quota exceeded.
      headers:
        Retry-After:
          description: Seconds until the limit resets.
          schema:
            type: integer
      content:
        application/json:
          schema:
            $ref: '#/components/schemas/Error'
    Unavailable:
      description: No graph run has finished yet.
      content:
        application/json:
          schema:
            $ref: '#/components/schemas/Error'
    Error:
      description: Any other error.
      content:
        application/json:
          schema:
            $ref: '#/components/schemas/Error'
  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.

````