Skip to main content
Skip to main content

RFQ WebSocket API

The WebSocket API streams live updates: RFQ and quote status, balance snapshots, and top-of-book prices. It is read-only: every client-initiated mutation goes over the REST API; the socket carries the handshake, the subscriptions, and the pushes.

Frame-level documentation lives in the interactive WebSocket reference, rendered from the AsyncAPI 3.0 spec at /api/rfq-asyncapi.yaml.

Connecting​

The shared client stream is wss://rfq-api.silhouette.exchange/v1/rfq/ws. Frames are JSON, tagged with a kind discriminator. After connecting, the client authenticates with an in-band auth frame (the credential pair from SIWE login signs a canonical GET /v1/rfq/ws, the same recipe as a signed REST request) and the server replies with a health frame. The socket is authenticated once; later frames carry no signature.

Liveness is an application-level keepalive: send {"kind": "ping"} roughly every 15 seconds and the server answers {"kind": "pong"}. A reconnect needs a fresh auth frame and fresh subscriptions. Pinging faster than that cadence draws on the account's request budget.

An account holds a bounded number of concurrent authenticated sessions, and a bounded number of topic subscriptions across them (see rate limits). A session beyond the first bound is answered AUTH_REQUIRED and closed, so close the old socket before reconnecting rather than opening another beside it. A subscribe beyond the second is answered RATE_LIMITED and leaves the socket and its existing subscriptions alone; re-subscribing to a topic the session already holds records nothing and is acknowledged rather than refused.

Topics​

Dispatch is opt-in: after the health frame the server sends nothing until the client subscribes. A subscribe frame names a topic and is acknowledged with subscribed; unsubscribe opts back out.

{ "kind": "subscribe", "topic": "prices", "instrumentId": "TSLAX-USDC-SPOT" }
TopicScopeStreams
pricesPublic, per instrumentA price frame per top-of-book change, with the current top of book on subscribe
balancesThe authenticated userA balanceSnapshot on every committed change, with a full snapshot on subscribe
rfqStatusThe authenticated user's RFQsAn rfqStatus frame per status transition, with the user's open RFQs on subscribe
quotesThe authenticated user's RFQsA quoteUpdate frame per competing quote received, with up to 100 recent quotes on the user's open RFQs on subscribe
openRfqsEligible makers, per instrumentAn rfq frame per open RFQ on that instrument (live-only)
quoteStatusMakers onlyA quoteStatus frame per transition of the maker's own quotes, with recent quotes replayed on subscribe

Topics with a baseline reply with current state on subscribe. On quotes that baseline is a quoteUpdate for each of up to 100 of the most recent quotes on the account's open RFQs, each carrying the status it has reached; read past that limit with GET /v1/rfq/requests/{id}/quotes. A quote committed as the subscription opens can arrive both in the baseline and as a live push, and the later receivedAt carries the current terms. openRfqs is live-only, so a maker joining mid-auction recovers what it missed over REST. Balance snapshots are full state delivered in per-user commit order, so the last snapshot a subscriber sees is always its true current balance.

The two maker topics are refused with UNSUPPORTED_CHANNEL when the account does not qualify, and the socket stays open. quoteStatus needs an active maker record; openRfqs additionally needs approval for the instrument's pair and at least one operated settlement adapter, re-checked as each RFQ is pushed rather than only at subscribe.

Errors arrive as {"kind":"error","code":…,"message":…}. AUTH_REQUIRED closes the session, and INVALID_MESSAGE closes it too unless it answers a frame sent out of sequence. UNSUPPORTED_CHANNEL, RATE_LIMITED, INVALID_INSTRUMENT_ID, UNKNOWN_INSTRUMENT and UNAVAILABLE refuse the offending frame and leave the socket open; UNAVAILABLE changed nothing, so retry the frame.

Maker price ingest​

Makers publish indicative price ladders to a separate, maker-only socket at wss://rfq-api.silhouette.exchange/v1/rfq/prices/ws. The HTTP upgrade request is authenticated with the standard HMAC headers over a canonical GET /v1/rfq/prices/ws, and the account must be an active maker operating at least one settlement adapter; approval to quote a market is not enough on its own. Each message is a bare ladder object with no kind envelope. Eligibility is re-read while the socket is open, so a maker that stops qualifying has its ladders purged from the public book and its socket closed, and redials once it can settle again. Taker price consumption stays on the prices topic of the shared stream.

Errors on this socket use the same {"kind":"error","code":…} envelope as the shared stream, so one reader dispatching on kind handles both.