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

# RFQs

> Stream every live RFQ river-natively with server-side filters, quote them as a maker, and follow your quotes' lifecycle on the same connection.

## Endpoint

```
wss://api.rivermarkets.com/v1/ws/rfqs?subaccount_id=<uuid>
```

One connection streams two things: the **public RFQ feed** (every request-for-quote posted on supported exchanges, filtered to your subscription) and the **private lifecycle of your own maker quotes** on the bound subaccount.

### Query parameters

| Parameter | Required | Default | Description |
| - | - | - | - |
| `subaccount_id` | yes | | UUID of the subaccount whose quote updates ride this socket. Must belong to the authenticated user. |
| `key_id` / `ts` / `sig` | yes (programmatic) | | Ed25519 signed-handshake params. See [Authentication](/api-reference/authentication). |

Authentication is identical to the other WS streams: signed handshake for programmatic clients. Invalid credentials close with `4401`; a missing `subaccount_id` closes with `4400`.

## Protocol

All frames are JSON. You only receive `rfq_canceled` frames for RFQs that were streamed in your feed.

### Client → server

```json theme={null}
{ "action": "subscribe", "filters": { "exchanges": ["kalshi"], "min_legs": 2, "max_legs": 6, "min_notional": 100, "subcategories": ["football", "basketball"], "river_ids": [17476015, 18340012] } }
```

One subscription per socket: a new `subscribe` frame **atomically replaces** the previous filter set. An empty `filters` object matches every RFQ. We highly recommend using filters.

| Filter | Type | Semantics |
| - | - | - |
| `exchanges` | string\[] | Exchange allowlist: `"kalshi"`, `"polymarket_us"`. |
| `min_legs` / `max_legs` | int | Bounds on the number of legs, both inclusive. A single-market RFQ has 1 leg. |
| `river_ids` | int\[] (≤1000) | Deliver only RFQs touching at least one of these instruments. |
| `min_notional` | number | Dollar floor. An RFQ is sized by contracts **or** by target cost; notional is `target_cost` or `contracts × $1` face respectively. |
| `subcategories` | string\[] | Lowercase instrument subcategories. Sports are subcategories (`football`, `basketball`, `soccer`, `baseball`, `hockey`, `tennis`, `golf`, …). Every leg must belong to a requested subcategory. |

### Server → client

```json theme={null}
{ "type": "connected" }
{ "type": "subscribed", "filters": { "min_legs": 2, "min_notional": 100.0 } }
{ "type": "rfq", "river_rfq_id": "5a1c…", "exchange": "kalshi",
  "qty": 250.0, "target_cost": null,
  "legs": [ { "river_id": 12345, "buy_flag": true, "subcategory": "football" } ],
  "created_ts": "2026-09-27T19:53:22.455389Z" }
{ "type": "rfq_canceled", "river_rfq_id": "5a1c…" }
{ "type": "quote", "river_order_id": "9f2e…", "river_rfq_id": "5a1c…",
  "status": "accepted", "bid": 0.45, "ask": 0.55, "qty": 250.0,
  "accepted_side": "no", "confirm_deadline_ts": "2026-09-27T19:54:01.120000", "reason": null }
{ "type": "error", "code": "invalid_filters", "message": "…" }
```

An RFQ has **no direction**: `legs` is the composition of the (combo) market, where `buy_flag: true` means the combo holds YES of that leg, and the taker picks a side only when accepting a quote. Exactly one of `qty` (contracts) and `target_cost` (dollars) is set, matching how the requester sized it.

`quote` frames are private to the socket's subaccount and delivered regardless of filters.

## Recovering after a reconnect

This stream has no replay, so a client that reconnects (or a page that reloads) has no way to learn about quotes it already owns from the socket alone. Call `GET /v1/rfqs/quotes?subaccount_id=<uuid>` on connect and after every reconnect: it returns your live quotes with their current status, `auto_confirm` and, for an accepted one, the `confirm_deadline_ts` you are racing. Then follow the socket's `quote` frames from there.

## Quoting an RFQ (maker flow)

Quotes are two-sided and priced in **YES-space**: `bid` is your price to buy YES, `ask` your price to sell YES.

1. `POST /v1/rfqs/{river_rfq_id}/quotes` with `{ subaccount_id, bid, ask, auto_confirm }` → `201 { river_order_id }`. The exchange is the RFQ's; the request is the same on Kalshi and Polymarket US. On Polymarket US the subaccount must hold institutional credentials (a retail subaccount gets `400`). A quote carries no size: it answers the RFQ whole, so the exchange holds collateral against the requester's size, not yours. On the ask side that is `(1 - ask)` per contract, which is what a cheap-looking ask on a large RFQ actually costs. The quote appears in your orders as an `RFQ_MAKER` order with no side (`buy_flag: null`) until a taker picks one.
2. When a taker accepts, a `quote` frame arrives with `status: "accepted"`, the side that was hit, and `confirm_deadline_ts`. The exchange gives you **3 seconds** to confirm.
3. `POST /v1/rfqs/quotes/{river_order_id}/confirm` inside the window. Or set `auto_confirm: true` at creation and skip this step: confirmation then fires server-side as soon as the exchange reports the acceptance. Refuse or cancel any time before confirmation with `DELETE /v1/rfqs/quotes/{river_order_id}`.
4. `status: "executed"` follows; the trade lands as a normal fill on the [fills](/ws-api-reference/fills) stream and your order flips to EXECUTED with the executed side and price stamped.

Miss the window and the exchange cancels the quote (`status: "cancelled"`, `reason: "confirm_timeout"`).

A quote the exchange refuses outright never rests: `POST /v1/rfqs/{river_rfq_id}/quotes` still returns `201` (it is queued, not sent inline), then the order lands in `REJECTED` and a `quote` frame arrives with `status: "cancelled"` and `reason` set to the exchange's own error code, for example `insufficient_balance`. The RFQ stays open for you to quote again.


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