Skip to main content
RFQ (request for quote) trades a block privately. A taker asks for a size on a market, makers quote it privately, and the taker accepts one side of the aggregated view. The makers it selects get a short last look, and the trade then settles as one sweep against their quotes. RFQ works on spot and perpetual markets. RFQ is available to accounts it is enabled for. Every RFQ call from another account is refused with 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 side to BUY or SELL. 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 with RFQ_SIDE_NOT_REQUESTED.
  • Perpetuals: set leverage (and optionally marginMode, ISOLATED by default). It applies to whichever side you accept.
You may create 5 RFQs per rolling 30 seconds. Past that, creation is refused with 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 (ownLatestVersion on GET /api/v1/rfqs/{rfqId}).
  • Withdrawing: DELETE /api/v1/rfqs/{rfqId}/quote withdraws 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;
  • grossVwap and 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, as SKIP and the default CANCEL_MAKER do. A book order of yours under CANCEL_TAKER or CANCEL_BOTH stops at your first own order instead, so it could fill less than shown. It is display only and never blocks an accept.
Allocation is price-time priority over at most 10 makers: the best price first, then the earliest quote. Each maker’s part is rounded down to the market’s size step, and a part below the market’s minimum size is skipped. The view carries a 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.
  • REJECT takes the maker out of this RFQ.
  • IMPROVE with improvedTerms gives a strictly better price or a higher capacity. An improvement never lowers the quoted capacity; a maker who wants to trade less must reject.
An answer counts by when it reaches Monaco: one that arrives by the deadline counts even if processing it ends later. One that arrives at or after the deadline is refused with 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 live rfq 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 vwap is all-in, both fees included. monacoFeeCbps and builderFeeCbps give the rates in centi-bps.
  • Maker: the selection shows the maker’s frozen Monaco rate (makerFeeCbps on the RfqSelected push, ownSelection.monacoFeeCbps on GET /api/v1/rfqs/{rfqId}, negative for a rebate), its frontend’s builder add-on as a maximum (maxBuilderFeeCbps), and estimatedNet at the selected size. The maximum is the rate stored on the selected quote: a fill of that quote charges it or nothing, never more. An IMPROVE re-places the quote at the add-on configured at that moment, so after an improvement the selection’s maximum and estimatedNet no longer bound the fee.

Receipts and history

  • Taker: GET /api/v1/rfqs/{rfqId} returns:
    • outcome, once the RFQ ends: executed and unfilled quantity, the gross vwap, the all-in allInVwap, and each fill’s price, quantity and fee (its Monaco and builder parts), with no maker identity;
    • acceptance, beside outcome (not inside it), once you accept: a record of what your view showed when you accepted.
  • Maker: the same call returns ownFills once the RFQ settles: only your own fills, with the fees as charged.
  • Your RFQs: GET /api/v1/rfqs lists them as the taker, newest first, in every state, including those that filled nothing. Page with pageSize (1 to 100, default 50): send the response’s nextPageToken back as pageToken for 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-book trades channel.
  • 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 public rfq_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_trades first returns the market’s newest 50 RFQ prints, newest first.
  • Recovering a gap: GET /api/v1/trades/{tradingPairId}?executionSource=RFQ (gRPC field execution_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, tagged executionSource: "RFQ". executionSource also accepts CLOB for 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 are TradeEvents, so their key is data.tradeId, which equals a live print’s data.executionId: deduplicate on that. complete: false means 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

Every write takes an idempotencyKey: a retry with the same key and body returns the first result. The SDK and MCP tools generate one when omitted.