RFQ_DISABLED. Check GET /api/v1/rfq/capabilities (sdk.rfq.getCapabilities()) first: it is refused with RFQ_DISABLED too when RFQ is not enabled for you, and otherwise lists the markets RFQ is offered on with each one’s minimum and maximum size, the timings and your creation quota.
A delegated agent session cannot trade through RFQ: creating, accepting, cancelling, quoting, withdrawing and answering last look are refused with 403. It can still read capabilities, an RFQ’s status and your RFQ list.
The flow
1
The taker requests quotes
POST /api/v1/rfqs names the market, the product (SPOT or PERPETUAL) and the maximum base quantity. The quantity is not a guaranteed fill. It must sit between the market’s minimum and maximum order size.- Two-way (default): omit
side, and makers never see your direction. - One-way: set
sidetoBUYorSELL. Makers then see it and may quote only the side that fills you: an ask when you buy, a bid when you sell. A quote on the other side is refused withRFQ_SIDE_NOT_REQUESTED. - Perpetuals: set
leverage(and optionallymarginMode,ISOLATEDby default). It applies to whichever side you accept.
RFQ_QUOTA_EXCEEDED; the capabilities response reports quotaRemaining and quotaRetryAfterMs.2
Makers receive it and quote
Every account RFQ is enabled for receives the request on the authenticated
rfq WebSocket channel as an RfqRequested push. The requester’s own wallet family never receives it, and the request never names the taker. Discovery is WebSocket only: there is no call that lists open RFQs to quote.A maker quotes with PUT /api/v1/rfqs/{rfqId}/quote: a bid, an ask or both, each a price and a capacity.- Versions: the first quote names
expectedVersion: 0. Each replacement names the maker’s current version (ownLatestVersiononGET /api/v1/rfqs/{rfqId}). - Withdrawing:
DELETE /api/v1/rfqs/{rfqId}/quotewithdraws the current quote. - Reserved funds: a quote reserves funds as a resting limit order does, each side on its own. A side that cannot be funded is refused, and the version holds only the funded sides.
- Perpetuals: leverage and margin mode are fixed while a side rests. To change them, withdraw and quote again.
- Self-trade: a maker cannot quote an RFQ from its own wallet family. Self-trade covers the whole family: a master and its sub-accounts.
3
The taker watches the view
The taker never sees individual quotes or makers. It sees an aggregated view, pushed as
RfqView on the rfq channel and returned by GET /api/v1/rfqs/{rfqId}. For each side (bid, makers buying, and ask, makers selling) the view shows:vwap: the all-in volume-weighted price of the allocation that would fill the quantity, the taker’s Monaco and builder fees included;availableQuantity;grossVwapand the estimated Monaco, builder and total fees on that quantity;- optionally
book: the same size priced on the public order book with the view’s fee rates, for comparison. It is shown only from a fresh snapshot (at most 500 ms old) that fills the whole size. The book is read the way your own sweep would walk it: orders already past their deadline and your wallet family’s resting orders are left out. That assumes a sweep trades around your orders, asSKIPand the defaultCANCEL_MAKERdo. A book order of yours underCANCEL_TAKERorCANCEL_BOTHstops at your first own order instead, so it could fill less than shown. It is display only and never blocks an accept.
viewVersion, which moves whenever any quote changes, is withdrawn or expires.Follow the view on the RfqView pushes rather than polling GET /api/v1/rfqs/{rfqId}. Pushes can arrive out of order: keep the view with the highest sequence. When quotes change quickly, views in between may be skipped, but the last view pushed always reflects the latest change. Read the RFQ only when a push may have been missed: once after you subscribe (makers can quote before the create returns) and after a reconnect. In TypeScript, waitForRfqView does this for you.4
The taker accepts or cancels
POST /api/v1/rfqs/{rfqId}/accept accepts one side (BUY or SELL) of the view, naming its viewVersion. Acceptance is binding. If the view has moved since, the accept is refused with RFQ_STALE_VIEW and nothing is substituted silently.Before accepting, the taker may cancel with POST /api/v1/rfqs/{rfqId}/cancel. An RFQ nobody accepts within its 120-second request window expires.5
Selected makers get a 250 ms last look
Each selected maker receives an
RfqSelected push with its own selection: the quantity selected from its quote, its frozen price and capacity, its frozen maker fee rate, and the last-look deadline, 250 ms after acceptance. By then it may answer POST /api/v1/rfqs/{rfqId}/last-look:- Silence executes. There is no accept call: a maker that does nothing trades at its quoted terms.
REJECTtakes the maker out of this RFQ.IMPROVEwithimprovedTermsgives a strictly better price or a higher capacity. An improvement never lowers the quoted capacity; a maker who wants to trade less must reject.
RFQ_DEADLINE_PASSED, and the maker stays silent.Last look closes as soon as every selected maker has answered, and otherwise at the deadline. If a selected maker’s on-time answer is still being processed then, closing waits for it, up to a hard cap of 350 ms after acceptance. At the cap, an IMPROVE that has already re-priced the maker’s quote counts and fills at the improved terms. Any other answer still in progress is refused with RFQ_CONFLICT, and that maker is silent at its original terms.6
Settlement
When the last look closes, the allocation is computed again over the selected makers still in, at their final terms. A quote is firm for its whole quoted (or improved) capacity, so if one maker rejects, another selected maker can fill more than its first allocation, up to its capacity and never past the requested quantity. Makers the acceptance did not select never come in.The trade settles as one sweep. Each maker’s part fills at that maker’s own price, or, if it can no longer fill as accepted, is skipped whole while the others fill. Partial fills are normal. Nothing falls back to the order book. The RFQ ends
SETTLED, REJECTED, CANCELLED or EXPIRED, and the taker receives an RfqOutcome push.Timing
The capabilities response returns these values (
requestWindowMs, minQuoteValidityMs, maxQuoteValidityMs, lastLookMs, maxNewRfqs, quotaWindowMs).
Disconnects
Before the taker accepts, a maker whose account has no liverfq WebSocket connection for 5 seconds has its quotes in RFQs still collecting withdrawn. The server pings rfq connections every 5 seconds and closes one that hasn’t answered within 15 seconds. A maker that quotes without ever holding an rfq connection is not affected. After the taker accepts, a disconnect never cancels anything: the last look runs, and silence executes.
Fees
Each side pays its own fee tier, and RFQ volume counts toward fee-tier volume.- Rates are frozen at acceptance. If the taker’s Monaco or builder rate changes before settlement, the trade is refused: nothing fills, and the RFQ ends
REJECTED. - Taker: the view’s
vwapis all-in, both fees included.monacoFeeCbpsandbuilderFeeCbpsgive the rates in centi-bps. - Maker: the selection shows the maker’s frozen Monaco rate (
makerFeeCbpson theRfqSelectedpush,ownSelection.monacoFeeCbpsonGET /api/v1/rfqs/{rfqId}, negative for a rebate), its frontend’s builder add-on as a maximum (maxBuilderFeeCbps), andestimatedNetat the selected size. The maximum is the rate stored on the selected quote: a fill of that quote charges it or nothing, never more. AnIMPROVEre-places the quote at the add-on configured at that moment, so after an improvement the selection’s maximum andestimatedNetno longer bound the fee.
Receipts and history
- Taker:
GET /api/v1/rfqs/{rfqId}returns:outcome, once the RFQ ends: executed and unfilled quantity, the grossvwap, the all-inallInVwap, and each fill’s price, quantity and fee (its Monaco and builder parts), with no maker identity;acceptance, besideoutcome(not inside it), once you accept: a record of what your view showed when you accepted.
- Maker: the same call returns
ownFillsonce the RFQ settles: only your own fills, with the fees as charged. - Your RFQs:
GET /api/v1/rfqslists them as the taker, newest first, in every state, including those that filled nothing. Page withpageSize(1 to 100, default 50): send the response’snextPageTokenback aspageTokenfor the next page. A page token only marks where to resume; a malformed one is refused, and every page lists only your own RFQs. - Trade history: RFQ fills appear in your trade history and in the public trade reads tagged
executionSource: "RFQ", and count in candles. They never print on the order-booktradeschannel. - Market volume: RFQ fills count in market volume. The market reads also split it into order-book and RFQ parts: see Screener and Stats.
Public prints
Every RFQ fill is published anonymously on the publicrfq_trades:{trading_pair_id} WebSocket channel: execution id, price, quantity, execution time and publication time, with no account, counterparty, quote or RFQ id. A print is published once the fill is durable, and a publish that fails is retried until it succeeds, so a print can arrive late and occasionally more than once. A repeated print carries the same values. WebSocket delivery itself is at-most-once, so a connection can still miss a print. Deduplicate on the execution id (execution_id on the wire, executionId in the TypeScript SDK), and recover gaps as below.
- Snapshot: subscribing to
rfq_tradesfirst returns the market’s newest 50 RFQ prints, newest first. - Recovering a gap:
GET /api/v1/trades/{tradingPairId}?executionSource=RFQ(gRPC fieldexecution_source) pages the market’s RFQ fills newest first. Each trade’s id is the print’s execution id. It covers recent fills in the hot trade store; older RFQ fills stay on the unfiltered pages, taggedexecutionSource: "RFQ".executionSourcealso acceptsCLOBfor order-book fills only. - TypeScript:
sdk.trades.recoverRfqTrades(tradingPairId, { sinceExecutionId })returns the RFQ fills newer than the last print you saw, oldest first, ready to replay. It also returns the other fills sharing that print’s execution time, some of which you may already hold. Recovered trades areTradeEvents, so their key isdata.tradeId, which equals a live print’sdata.executionId: deduplicate on that.complete: falsemeans the page budget (maxPages, default 10) ran out before recovery finished, either before reaching the last print you saw or before reading every fill that shares its execution time.
Self-trade
Self-trade covers the whole wallet family, as on the order book: a master and its sub-accounts count as one trader. The taker’s own wallet family never receives its RFQ and can never quote it.Interfaces
- REST: the RFQ endpoints in the API reference.
- gRPC:
RfqService. - WebSocket: the authenticated
rfqchannel and the publicrfq_tradeschannel, in the WebSocket reference. - TypeScript SDK:
sdk.rfq. - MCP: the RFQ tools.
idempotencyKey: a retry with the same key and body returns the first result. The SDK and MCP tools generate one when omitted.
