Skip to main content

Endpoint

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

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

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.

Server → client

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