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

# Search Markets

> Search and browse markets across all exchanges.

Supports:
- Exact match on ticker, condition_id, slug, river_id
- Full-text search on name, description, tags
- Browse by filters (no query required)
- Results ranked by relevance

We default to active markets in the view so things are faster



## OpenAPI

````yaml /openapi.json get /v1/markets/search
openapi: 3.1.0
info:
  title: River Markets API
  description: >+
    # Getting Started



    River Markets provides users with a unified prime brokerage interface for
    prediction markets, including

    - Liquidity aggregation

    - Order routing

    - Market and data search


    This API guide will help you get up and running quickly.


    ## Concepts


    ### River IDs


    Every market across all exchanges is assigned a unique **River ID** - a
    unified identifier that works across our entire platform. This allows you
    to:


    - Reference the same market consistently regardless of which exchange it
    trades on

    - Place orders using a single identifier format

    - Track positions and fills across exchanges with one ID


    When searching for markets, the API returns both the exchange-native
    identifier (e.g., Kalshi ticker, Polymarket slug) and the River ID. Use the
    River ID when placing orders.


    ### Subaccounts


    **Subaccounts** let you organize your trading activity into separate
    containers. Each subaccount:


    - Has its own set of exchange credentials

    - Maintains separate positions and order history

    - Can represent different strategies, clients, or portfolios


    This is useful for:

    - **Fund managers**: Segregate client portfolios

    - **Traders**: Separate strategies (e.g., momentum vs. mean reversion)

    - **Teams**: Give team members isolated trading environments


    ## 1. Create an Account


    Sign up at [app.rivermarkets.com](https://app.rivermarkets.com) to create
    your account.


    ## 2. Generate an API Key


    1. Log in to your dashboard

    2. Navigate to **Settings → API Keys**

    3. Click **Create API Key**

    4. Copy and securely store your key - it will only be shown once


    ## 3. Install required packages


    ```python

    pip install requests

    ```


    ## 4. Make Your First Request


    Test your API key by searching for markets.


    We support ticker (kalshi ticker, polymarket condition_id/slug, river_id)
    and fuzzy search ('bitcoin', 'fed rates').


    > **Using the SDK?** `rivermarkets` (Python) 

    > handle request signing for you — pass your `KEY_ID` and `PRIVATE_KEY` to
    the

    > client constructor and forget about the rest of this section. The raw
    example

    > below is for anyone integrating without an SDK.


    ```python

    import base64, hashlib, time

    from urllib.parse import urlsplit, urlencode, parse_qsl, quote

    from nacl.signing import SigningKey

    import requests


    KEY_ID = "<uuid from Settings → API Keys>"

    PRIVATE_KEY_B64 = "<base64 private key shown once at creation>"

    BASE_URL = "https://api.rivermarkets.com/v1"


    signing_key = SigningKey(base64.b64decode(PRIVATE_KEY_B64))



    def sign(method: str, url: str, body: bytes = b"") -> dict:
        parts = urlsplit(url)
        # quote_via=quote encodes spaces as %20 to match RFC 3986 / the server.
        sorted_q = urlencode(sorted(parse_qsl(parts.query, keep_blank_values=True)), quote_via=quote)
        ts = str(int(time.time()))
        canonical = "\n".join([
            method.upper(), parts.path, sorted_q, ts, hashlib.sha256(body).hexdigest(),
        ]).encode()
        sig = signing_key.sign(canonical).signature
        return {
            "X-River-Key-Id": KEY_ID,
            "X-River-Timestamp": ts,
            "X-River-Signature": base64.b64encode(sig).decode(),
        }


    url = f"{BASE_URL}/markets/search?q=shutdown&exchange=POLYMARKET"

    response = requests.get(url, headers=sign("GET", url))

    for market in response.json()["results"]:
        print(f"River ID: {market['river_id']}, Ticker: {market['slug']}")
    ```


    ## 5. Set Up a Subaccount


    River allows users to setup subaccounts - which allow them to manage
    different trading strategies or client portfolios.


    These can be added through the UI by going to Settings -> Subaccounts

    or programatically.


    ### Video Walkthrough


    Watch this tutorial to see how to create a subaccount, add a Polymarket
    account, and place your first trade:


    <div style="position: relative; padding-bottom: 54.37499999999999%; height:
    0;"><iframe
    src="https://www.loom.com/embed/9b49e269c09e436d98b72f3cb55d7691"
    frameborder="0" webkitallowfullscreen mozallowfullscreen allowfullscreen
    style="position: absolute; top: 0; left: 0; width: 100%; height:
    100%;"></iframe></div>


    ### Programmatic Setup 


    ```python

    # Create a subaccount

    response = requests.post(
        f"{BASE_URL}/subaccounts",
        headers=headers,
        json={"name": "My Trading Account"}
    )

    subaccount = response.json()

    subaccount_id = subaccount["id"]

    ```


    ## 6. Add Exchange Credentials


    Exchange credentials can also be added via UI under Settings -> Subaccounts,

    or programatically as shown below.


    For best results, we recommend

    - Polymarket : create an account using email (not a wallet) and get your
    wallet and PK directly from Polymarket under [Settings -> Export Private
    Key](https://polymarket.com/settings)

    - Kalshi : create a secret/key pair with Read/Write access under [Settings
    -> Account & security -> API keys](https://kalshi.com/account/profile)


    ```python

    # Add Polymarket credentials

    response = requests.post(
        f"{BASE_URL}/credentials",
        headers=headers,
        json={
            "subaccount_id": subaccount_id,
            "exchange": "polymarket",
            "wallet_address": "your_polymarket_wallet_address",
            "private_key": "your_polymarket_private_key"
        }
    )

    # Add Kalshi credentials

    response = requests.post(
        f"{BASE_URL}/credentials",
        headers=headers,
        json={
            "subaccount_id": subaccount_id,
            "exchange": "kalshi",
            "api_key": "your_kalshi_api_key",
            "api_secret": "your_kalshi_secret"
        }
    )

    ```


    We use asymmetric encryption so your credentials are not transferred between
    services or stored in plain text.

    We delete your credentials when you delete a subaccount. 


    ## 7. Place Your First Order


    Use the River ID from your market search to place an order.


    ```python

    # Place a limit order (buy 10 contracts at $0.50)

    response = requests.post(
        f"{BASE_URL}/orders",
        headers=headers,
        json={
            "subaccount_id": subaccount_id,
            "river_id": 4552150,       # From market search
            "order_type": "LIMIT",      # LIMIT or MARKET
            "time_in_force": "GTC",     # FOK, GTC, GTD, or IOC
            "buy_flag": True,           # True=buy, False=sell
            "price": 0.50,              # Limit price (0-1)
            "qty": 10,                  # Number of contracts
        }
    )

    order = response.json()

    print(f"Order placed: {order['river_order_id']}")

    ```


    ## 8. Check Order Status


    Monitor your order's progress by fetching its current state.


    ```python

    # Get order status

    order_id = order['river_order_id']

    response = requests.get(
        f"{BASE_URL}/orders/{order_id}",
        headers=headers
    )

    order_status = response.json()

    print(f"Status: {order_status['status']}, Filled:
    {order_status['traded_qty']}/{order_status['qty']}")

    ```


    Possible order statuses:

    - `pending_submission` - Order received, awaiting processing

    - `resting` - Order live on the exchange (may include partially-filled live
    orders)

    - `filled` - Terminal: order fully executed

    - `partially_filled` - Terminal: order cancelled with some quantity filled

    - `cancelled` - Terminal: order cancelled with no fills

    - `rejected` - Order rejected (see `reject_reason`)


    ## 9. Cancel an Order


    Cancel a resting order before it's fully filled.


    ```python

    # Cancel an order

    order_id = order['river_order_id']

    response = requests.delete(
        f"{BASE_URL}/orders/{order_id}",
        headers=headers
    )

    if response.status_code == 200:
        print("Order cancelled successfully")
    ```


    Note: Only orders with status `pending_submission` or `resting` can be
    cancelled (`partially_filled` is terminal).


    ## Supported Exchanges


    | Exchange | Markets | Status |

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

    | Kalshi | US regulated prediction markets | Available |

    | Polymarket | Crypto prediction markets | Available |

    | Polymarket US | US regulated prediction markets | Available |

    | Novig | US regulated prediction markets | Coming soon |

    | Rothera | US regulated prediction markets | Future integration |


    ## Rate Limits


    - **Standard**: 10 requests/second

    - **Premium**: Contact support for higher limits




    ## Authentication


    All endpoints require authentication via one of:

    - **Bearer Token**: `Authorization: Bearer <jwt_token>` (for web clients)

    - **Signed Request** (programmatic access): three headers per REST request,
    or
      three query params on the WebSocket handshake.

    > **Using the SDK?** `rivermarkets` (Python) 

    > sign every REST request and WS handshake for you. You only need the raw

    > recipes below if you're integrating without an SDK.


    ### REST signing


    Send three headers on every request:

    - `X-River-Key-Id`: UUID of your API key

    - `X-River-Timestamp`: current unix seconds (must be within 30s of server
    time)

    - `X-River-Signature`: base64 Ed25519 signature over the canonical request


    The REST canonical string is LF-joined:

    ```

    METHOD\nPATH\nSORTED_QUERY\nTIMESTAMP\nSHA256(body) hex

    ```


    ```python

    import base64, hashlib, time

    from urllib.parse import urlsplit, urlencode, parse_qsl, quote

    from nacl.signing import SigningKey


    signing_key = SigningKey(base64.b64decode(PRIVATE_KEY_B64))


    def sign(method: str, url: str, body: bytes = b"") -> dict:
        parts = urlsplit(url)
        # quote_via=quote encodes spaces as %20 to match RFC 3986 / the server.
        sorted_q = urlencode(sorted(parse_qsl(parts.query, keep_blank_values=True)), quote_via=quote)
        ts = str(int(time.time()))
        canonical = "\n".join([
            method.upper(), parts.path, sorted_q, ts, hashlib.sha256(body).hexdigest(),
        ]).encode()
        sig = signing_key.sign(canonical).signature
        return {
            "X-River-Key-Id": KEY_ID,
            "X-River-Timestamp": ts,
            "X-River-Signature": base64.b64encode(sig).decode(),
        }
    ```


    ### WebSocket signing


    Browsers can't set custom headers on the WS upgrade, so the same three
    values

    move to query params: `?key_id=&ts=&sig=`. The WS canonical string omits the

    body hash and uses the literal `"WS"` as the method:

    ```

    WS\nPATH\nSORTED_QUERY (excluding key_id/ts/sig)\nTIMESTAMP

    ```


    ```python

    import asyncio, base64, time

    from urllib.parse import urlencode, quote

    import websockets

    from nacl.signing import SigningKey


    signing_key = SigningKey(base64.b64decode(PRIVATE_KEY_B64))



    def sign_ws(path: str, extra_params: dict | None = None) -> str:
        """Return a fully-formed wss:// URL with signing query params appended."""
        params = dict(extra_params or {})
        sorted_q = urlencode(sorted(params.items()), quote_via=quote)
        ts = str(int(time.time()))
        canonical = "\n".join(["WS", path, sorted_q, ts]).encode()
        sig = base64.b64encode(signing_key.sign(canonical).signature).decode()
        params.update({"key_id": KEY_ID, "ts": ts, "sig": sig})
        return f"wss://api.rivermarkets.com{path}?{urlencode(params)}"


    async def stream_orders(subaccount_id: str):
        url = sign_ws("/v1/ws/orders", {"subaccount_id": subaccount_id})
        async with websockets.connect(url) as ws:
            async for frame in ws:
                print(frame)


    asyncio.run(stream_orders("411b3f8c-85f9-41c9-90b7-3ff6d74efc4f"))

    ```


    ### Security notes


    - Your private key never traverses the wire. Generate a keypair at
      `Settings → API Keys`; the private key is shown once.
    - Same signature cannot be submitted twice within 60 seconds (replay
    protection).
      On network retries, re-sign with a fresh timestamp.
    - Timestamps must be within 30s of server time. Sync your clock via NTP.



    ---




    # Order Types Documentation


    Complete guide to all order types available in the River Markets trading
    platform.


    ---


    ## Basic Order Types


    ### Market Order

    Executes immediately at the best available price.

    - **Use case**: When you need immediate execution and price is less
    important

    - **Example**: Market buy 100 contracts → fills at current best ask price


    ### Limit Order

    Rests on the order book at a specified price.

    - **Use case**: When you want price control and can wait for execution

    - **Example**: Limit buy 100 @ $0.65 → only fills at $0.65 or better


    ---


    ## Conditional Order Types


    Conditional orders are **not placed immediately**. They activate ("trigger")
    when specific conditions are met.


    ### 1. Stop Orders (STOP)


    **Stop orders trigger when price moves AGAINST your desired direction.**
    Used primarily for **stop losses** (exit protection) or **breakout
    entries**.


    #### Stop Market

    - **Triggers**: When market trades at or through your stop price

    - **Executes**: As a market order (immediate fill at best available price)

    - **Example**:
      ```
      You're LONG at $0.70, want to exit if price falls to $0.60
      → Stop Market SELL @ $0.60
      → Triggers when price ≤ $0.60
      → Sends market sell order → fills immediately
      ```

    #### Stop Limit

    - **Triggers**: When market trades at or through your stop price

    - **Executes**: As a limit order at your specified limit price

    - **Example**:
      ```
      You're LONG at $0.70, want to exit around $0.60 but not worse than $0.59
      → Stop Limit SELL @ $0.60, Limit $0.59
      → Triggers when price ≤ $0.60
      → Sends limit sell order @ $0.59 → waits for fill at $0.59 or better
      ```
    - **Risk**: May not fill if price moves too fast through your limit


    **Directional Logic:**

    - **BUY stop**: Triggers at or **above** stop price (breakout entry or short
    exit)

    - **SELL stop**: Triggers at or **below** stop price (stop loss or long
    exit)


    ---


    ### 2. Reverse Stop Orders (REVERSE_STOP)


    **Reverse stop orders trigger when price moves IN your desired direction.**
    Used exclusively for **take profit** scenarios.


    **Key Concept**: A "reverse stop" is called "reverse" because it triggers in
    the OPPOSITE direction of a normal stop order.


    #### Reverse Stop Market (TP Market)

    - **Triggers**: When market trades at or through your target price (OPPOSITE
    direction from normal stop)

    - **Executes**: As a market order

    - **Example**:
      ```
      You're LONG at $0.60, want to take profit if price rises to $0.80
      → TP Market (Reverse Stop Market) SELL @ $0.80
      → Normal stop would trigger BELOW, but reverse stop triggers ABOVE
      → Triggers when price ≥ $0.80
      → Sends market sell order → fills immediately
      ```

    **Directional Logic (REVERSED from normal stop):**

    - **BUY reverse stop**: Triggers at or **below** target (opposite of normal
    buy stop)

    - **SELL reverse stop**: Triggers at or **above** target (opposite of normal
    sell stop)


    **Why "reverse"?**

    - Normal **SELL stop** @ $0.60 triggers when price ≤ $0.60 (price falling)

    - **SELL reverse stop** @ $0.80 triggers when price ≥ $0.80 (price rising)

    - The trigger direction is reversed!


    #### Reverse Stop Limit (TP Limit)

    - **Triggers**: When market trades at or through your target price

    - **Executes**: As a limit order at your specified limit price

    - **Example**:
      ```
      You're LONG at $0.60, want to take profit around $0.80 but not worse than $0.79
      → TP Limit (Reverse Stop Limit) SELL @ $0.80, Limit $0.79
      → Triggers when price ≥ $0.80
      → Sends limit sell order @ $0.79 → waits for fill at $0.79 or better
      ```

    ---


    ### 3. Take Profit Orders (TP)


    **Parent-child orders**: Automatically triggers a child order when a parent
    order **fills**.


    #### TP Market

    - **Triggers**: When parent order is **fully filled**

    - **Executes**: Immediately places the child order (can be another TP/SL or
    direct order)

    - **Example**:
      ```
      Place limit buy 100 @ $0.60 WITH TP Market @ $0.80
      → When buy fills → immediately activates TP Market SELL @ $0.80
      → That TP Market is a reverse stop that triggers when price ≥ $0.80
      ```

    #### TP Limit

    - **Triggers**: When parent order is **fully filled**

    - **Executes**: Activates TP Limit order (reverse stop limit)

    - **Example**:
      ```
      Place limit buy 100 @ $0.60 WITH TP Limit @ $0.80 (limit $0.79)
      → When buy fills → activates TP Limit SELL
      → Triggers when price ≥ $0.80 → sends limit sell @ $0.79
      ```

    ---


    ### 4. Stop Loss Orders (SL)


    **Parent-child orders**: Automatically triggers a child order when a parent
    order **fills**.


    #### SL Market

    - **Triggers**: When parent order is **fully filled**

    - **Executes**: Immediately places the child order (can be another SL/TP or
    direct order)

    - **Example**:
      ```
      Place limit buy 100 @ $0.60 WITH SL Market @ $0.55
      → When buy fills → immediately activates SL Market SELL @ $0.55
      → That SL Market is a stop order that triggers when price ≤ $0.55
      ```

    #### SL Limit

    - **Triggers**: When parent order is **fully filled**

    - **Executes**: Activates SL Limit order (stop limit)

    - **Example**:
      ```
      Place limit buy 100 @ $0.60 WITH SL Limit @ $0.55 (limit $0.54)
      → When buy fills → activates SL Limit SELL
      → Triggers when price ≤ $0.55 → sends limit sell @ $0.54
      ```

    ---


    ## Order Type Comparison


    | Type | Trigger Condition | Execution | Primary Use Case |

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

    | **Market** | None (immediate) | Immediate at best price | Need immediate
    fill |

    | **Limit** | None (rests) | At limit price or better | Want price control |

    | **Stop Market** | Price moves AGAINST you | Market order (immediate) |
    Stop loss, breakout entry |

    | **Stop Limit** | Price moves AGAINST you | Limit order (may not fill) |
    Stop loss with price protection |

    | **TP Market** | Parent fills + price moves FOR you | Market order via
    reverse stop | Take profit with immediate exit |

    | **TP Limit** | Parent fills + price moves FOR you | Limit order via
    reverse stop | Take profit with price control |

    | **SL Market** | Parent fills + price moves AGAINST you | Market order via
    stop | Stop loss from entry |

    | **SL Limit** | Parent fills + price moves AGAINST you | Limit order via
    stop | Stop loss with price floor |


    ---


    ## Key Concepts


    ### Stop vs Reverse Stop

    - **Stop**: Triggers when price moves AGAINST you (exit losses, enter
    breakouts)

    - **Reverse Stop**: Triggers when price moves FOR you (take profits)


    ### Parent-Child Orders

    - **TP/SL orders** are "meta-orders" that activate child orders when a
    parent fills

    - The child order itself is usually a stop or reverse stop order

    - This creates a two-step process: Parent fill → Child activation → Price
    trigger → Execution


    ### Trigger Direction Summary

    | Order Side | Stop (Loss/Breakout) | Reverse Stop (Profit) |

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

    | **BUY** | Triggers ≥ stop price | Triggers ≤ target price |

    | **SELL** | Triggers ≤ stop price | Triggers ≥ target price |


    ---


    ## Examples


    ### Complete Trading Scenario

    ```

    1. Place limit BUY 100 @ $0.60
       WITH TP Market @ $0.80 (reverse stop)
       AND SL Market @ $0.55 (normal stop)

    2. Buy fills at $0.60
       → TP Market SELL @ $0.80 activated (reverse stop)
       → SL Market SELL @ $0.55 activated (normal stop)

    3a. If price rises to $0.80:
        → TP triggers (price ≥ $0.80) → market sell executes
        → SL cancelled automatically

    3b. If price falls to $0.55:
        → SL triggers (price ≤ $0.55) → market sell executes
        → TP cancelled automatically
    ```


    ### Breakout Entry with Protection

    ```

    Price is at $0.70, resistance at $0.75, support at $0.65


    1. Place Stop Market BUY @ $0.76 (breakout entry)
       → Triggers when price ≥ $0.76 (breaks resistance)
       → Market buy executes

    2. Add SL Market @ $0.74 to the stop order
       → When breakout buy fills → SL activates
       → Protects if breakout fails
    ```

  contact:
    name: River Markets
    url: https://rivermarkets.com/
  license:
    name: Proprietary
  version: 1.0.0
servers:
  - url: https://api.rivermarkets.com
security:
  - SignedRequestKeyId: []
    SignedRequestTimestamp: []
    SignedRequestSignature: []
  - BearerAuth: []
tags:
  - name: markets
    description: Search and discover prediction markets across Kalshi and Polymarket.
  - name: subaccounts
    description: >-
      Manage trading subaccounts. Each subaccount can have its own exchange
      credentials and positions.
  - name: positions
    description: View current portfolio positions across all connected exchanges.
  - name: fills
    description: Retrieve trade execution history and fill details.
  - name: prices
    description: Get historical price data and candlesticks from exchanges.
  - name: tradeprints
    description: >-
      Recent trades for a market. Pairs with the /v1/ws/tradeprints WebSocket
      for live updates.
  - name: orders
    description: Place and manage orders on prediction market exchanges.
  - name: complex-orders
    description: >-
      Manage complex orders: conditional orders (take-profit and stop-loss),
      TWAP, and other advanced order types.
paths:
  /v1/markets/search:
    get:
      tags:
        - markets
      summary: Search Markets
      description: |-
        Search and browse markets across all exchanges.

        Supports:
        - Exact match on ticker, condition_id, slug, river_id
        - Full-text search on name, description, tags
        - Browse by filters (no query required)
        - Results ranked by relevance

        We default to active markets in the view so things are faster
      operationId: search_markets_v1_markets_search_get
      parameters:
        - name: q
          in: query
          required: false
          schema:
            anyOf:
              - type: string
                minLength: 1
              - type: 'null'
            description: Search query
            title: Q
          description: Search query
        - name: exchange_name
          in: query
          required: false
          schema:
            anyOf:
              - type: string
              - type: 'null'
            description: Filter by exchange name (KALSHI, POLYMARKET)
            title: Exchange Name
          description: Filter by exchange name (KALSHI, POLYMARKET)
        - name: category
          in: query
          required: false
          schema:
            anyOf:
              - type: array
                items:
                  type: string
              - type: 'null'
            description: >-
              Filter by canonical category. Repeat the param to filter to
              multiple categories (Sports, Crypto, Politics, Finance,
              Entertainment, Science & Tech, Weather, World Affairs, Health,
              Social, Other).
            title: Category
          description: >-
            Filter by canonical category. Repeat the param to filter to multiple
            categories (Sports, Crypto, Politics, Finance, Entertainment,
            Science & Tech, Weather, World Affairs, Health, Social, Other).
        - name: subcategory
          in: query
          required: false
          schema:
            anyOf:
              - type: string
              - type: 'null'
            description: Filter by subcategory (e.g. Basketball, Football)
            title: Subcategory
          description: Filter by subcategory (e.g. Basketball, Football)
        - name: status
          in: query
          required: false
          schema:
            anyOf:
              - $ref: '#/components/schemas/InstrumentStatus'
              - type: 'null'
            description: Filter by instrument status
            title: Status
          description: Filter by instrument status
        - name: expiration_date_start
          in: query
          required: false
          schema:
            anyOf:
              - type: string
                format: date-time
              - type: 'null'
            description: Start of expiration date range (inclusive, ISO 8601)
            title: Expiration Date Start
          description: Start of expiration date range (inclusive, ISO 8601)
        - name: expiration_date_end
          in: query
          required: false
          schema:
            anyOf:
              - type: string
                format: date-time
              - type: 'null'
            description: End of expiration date range (exclusive, ISO 8601)
            title: Expiration Date End
          description: End of expiration date range (exclusive, ISO 8601)
        - name: start_datetime_after
          in: query
          required: false
          schema:
            anyOf:
              - type: string
                format: date-time
              - type: 'null'
            description: Filter to markets with start_datetime >= this (ISO 8601)
            title: Start Datetime After
          description: Filter to markets with start_datetime >= this (ISO 8601)
        - name: start_datetime_before
          in: query
          required: false
          schema:
            anyOf:
              - type: string
                format: date-time
              - type: 'null'
            description: Filter to markets with start_datetime < this (ISO 8601)
            title: Start Datetime Before
          description: Filter to markets with start_datetime < this (ISO 8601)
        - name: event_ticker
          in: query
          required: false
          schema:
            anyOf:
              - type: string
              - type: 'null'
            description: Filter by event_ticker (exact match)
            title: Event Ticker
          description: Filter by event_ticker (exact match)
        - name: last_price_min
          in: query
          required: false
          schema:
            anyOf:
              - type: number
                maximum: 1
                minimum: 0
              - type: 'null'
            description: >-
              Filter to markets with last_price >= this value (0.0–1.0 YES
              probability).
            title: Last Price Min
          description: >-
            Filter to markets with last_price >= this value (0.0–1.0 YES
            probability).
        - name: last_price_max
          in: query
          required: false
          schema:
            anyOf:
              - type: number
                maximum: 1
                minimum: 0
              - type: 'null'
            description: >-
              Filter to markets with last_price <= this value (0.0–1.0 YES
              probability).
            title: Last Price Max
          description: >-
            Filter to markets with last_price <= this value (0.0–1.0 YES
            probability).
        - name: volume_min
          in: query
          required: false
          schema:
            anyOf:
              - type: integer
                minimum: 0
              - type: 'null'
            description: Filter to events whose total summed volume >= this value.
            title: Volume Min
          description: Filter to events whose total summed volume >= this value.
        - name: sort_by
          in: query
          required: false
          schema:
            anyOf:
              - type: string
              - type: 'null'
            description: >-
              Sort mode for event-based pagination: trending, volume, newest,
              ending-soon, start-time, price
            title: Sort By
          description: >-
            Sort mode for event-based pagination: trending, volume, newest,
            ending-soon, start-time, price
        - name: limit
          in: query
          required: false
          schema:
            type: integer
            maximum: 1000
            minimum: 1
            description: Maximum number of results
            default: 20
            title: Limit
          description: Maximum number of results
        - name: offset
          in: query
          required: false
          schema:
            type: integer
            minimum: 0
            description: Offset for pagination
            default: 0
            title: Offset
          description: Offset for pagination
        - name: event_limit
          in: query
          required: false
          schema:
            anyOf:
              - type: integer
                maximum: 2000
                minimum: 1
              - type: 'null'
            description: >-
              Paginate by events instead of markets. Up to 200 by default;
              values above that require start_datetime_after or
              start_datetime_before.
            title: Event Limit
          description: >-
            Paginate by events instead of markets. Up to 200 by default; values
            above that require start_datetime_after or start_datetime_before.
        - name: event_offset
          in: query
          required: false
          schema:
            anyOf:
              - type: integer
                minimum: 0
              - type: 'null'
            description: Event offset for event-based pagination
            title: Event Offset
          description: Event offset for event-based pagination
      responses:
        '200':
          description: Successful Response
          content:
            application/json:
              schema:
                $ref: '#/components/schemas/MarketSearchResponse'
        '422':
          description: Validation Error
          content:
            application/json:
              schema:
                $ref: '#/components/schemas/HTTPValidationError'
components:
  schemas:
    InstrumentStatus:
      type: string
      enum:
        - active
        - closed
        - inactive
      title: InstrumentStatus
      description: >-
        Instrument status derived in instruments table from exchange-specific
        fields.


        Kalshi: maps from kalshi_markets.status
            - 'active' -> ACTIVE
            - everything else (closed, determined, finalized, settled, amended, inactive, initialized) -> CLOSED

        Polymarket: maps from polymarket_markets.closed and
        polymarket_markets.active booleans
            - closed=true -> CLOSED
            - active=true (and not closed) -> ACTIVE
            - else (active=false, closed=false) -> INACTIVE
    MarketSearchResponse:
      properties:
        query:
          type: string
          title: Query
        results:
          items:
            $ref: '#/components/schemas/MarketSearchResult'
          type: array
          title: Results
        total:
          type: integer
          title: Total
        total_events:
          anyOf:
            - type: integer
            - type: 'null'
          title: Total Events
      type: object
      required:
        - query
        - results
        - total
      title: MarketSearchResponse
      description: Schema for market search response.
    HTTPValidationError:
      properties:
        detail:
          items:
            $ref: '#/components/schemas/ValidationError'
          type: array
          title: Detail
      type: object
      title: HTTPValidationError
    MarketSearchResult:
      properties:
        river_id:
          type: integer
          title: River Id
        exchange_value:
          type: integer
          title: Exchange Value
        exchange_name:
          type: string
          title: Exchange Name
        ticker:
          anyOf:
            - type: string
            - type: 'null'
          title: Ticker
        token_id_yes:
          anyOf:
            - type: string
            - type: 'null'
          title: Token Id Yes
        token_id_no:
          anyOf:
            - type: string
            - type: 'null'
          title: Token Id No
        slug:
          anyOf:
            - type: string
            - type: 'null'
          title: Slug
        tick_size_price:
          type: number
          title: Tick Size Price
        tick_size_qty:
          type: number
          title: Tick Size Qty
        tick_structure:
          type: string
          title: Tick Structure
          default: uniform
        minimum_order_size:
          type: number
          title: Minimum Order Size
        expiration_datetime:
          anyOf:
            - type: string
            - type: 'null'
          title: Expiration Datetime
          description: Market expiration datetime (UTC)
        start_datetime:
          anyOf:
            - type: string
            - type: 'null'
          title: Start Datetime
          description: >-
            Game/event start datetime (UTC). Populated for sports; null
            otherwise.
        neg_risk:
          type: boolean
          title: Neg Risk
        neg_risk_id:
          anyOf:
            - type: string
            - type: 'null'
          title: Neg Risk Id
        condition_id:
          anyOf:
            - type: string
            - type: 'null'
          title: Condition Id
        name:
          type: string
          title: Name
        subtitle:
          anyOf:
            - type: string
            - type: 'null'
          title: Subtitle
        description:
          anyOf:
            - type: string
            - type: 'null'
          title: Description
        status:
          $ref: '#/components/schemas/InstrumentStatus'
        category:
          type: string
          title: Category
          default: Other
        subcategory:
          anyOf:
            - type: string
            - type: 'null'
          title: Subcategory
        rank:
          anyOf:
            - type: number
            - type: 'null'
          title: Rank
        event_ticker:
          anyOf:
            - type: string
            - type: 'null'
          title: Event Ticker
        event_title:
          anyOf:
            - type: string
            - type: 'null'
          title: Event Title
        volume:
          anyOf:
            - type: integer
            - type: 'null'
          title: Volume
        volume_24h:
          anyOf:
            - type: integer
            - type: 'null'
          title: Volume 24H
        series_ticker:
          anyOf:
            - type: string
            - type: 'null'
          title: Series Ticker
        image:
          anyOf:
            - type: string
            - type: 'null'
          title: Image
        last_price:
          anyOf:
            - type: number
            - type: 'null'
          title: Last Price
          description: >-
            Most recent YES price normalized to 0.0–1.0 (probability). Kalshi
            cents / 100; Polymarket already 0.0–1.0. NULL when no trade has been
            observed.
        primary_entity_name:
          anyOf:
            - type: string
            - type: 'null'
          title: Primary Entity Name
          description: >-
            Resolved display name of the canonical structured-target entity
            (player, team, competitor, …) referenced by the market. NULL for
            markets without a structured target. Kalshi only — Polymarket has no
            equivalent concept.
      type: object
      required:
        - river_id
        - exchange_value
        - exchange_name
        - tick_size_price
        - tick_size_qty
        - minimum_order_size
        - neg_risk
        - name
        - status
      title: MarketSearchResult
      description: Schema for a single market search result.
    ValidationError:
      properties:
        loc:
          items:
            anyOf:
              - type: string
              - type: integer
          type: array
          title: Location
        msg:
          type: string
          title: Message
        type:
          type: string
          title: Error Type
        input:
          title: Input
        ctx:
          type: object
          title: Context
      type: object
      required:
        - loc
        - msg
        - type
      title: ValidationError
  securitySchemes:
    SignedRequestKeyId:
      type: apiKey
      in: header
      name: X-River-Key-Id
      description: UUID of your API key (from Settings → API Keys).
    SignedRequestTimestamp:
      type: apiKey
      in: header
      name: X-River-Timestamp
      description: Current unix seconds. Must be within 30s of server time.
    SignedRequestSignature:
      type: apiKey
      in: header
      name: X-River-Signature
      description: >-
        Base64 Ed25519 signature over the canonical request:
        METHOD\nPATH\nSORTED_QUERY\nTIMESTAMP\nSHA256(body) hex. See
        /api-reference/authentication for the full recipe.
    BearerAuth:
      type: http
      scheme: bearer
      description: JWT bearer token for web client authentication.

````