asyncapi: 3.0.0
channels:
  makerPriceIngest:
    address: /v1/rfq/prices/ws
    messages:
      ErrorFrame:
        $ref: "#/components/messages/ErrorFrame"
      LevelsFrame:
        $ref: "#/components/messages/LevelsFrame"
  ws:
    address: /v1/rfq/ws
    messages:
      AuthFrame:
        $ref: "#/components/messages/AuthFrame"
      BalanceSnapshot:
        $ref: "#/components/messages/BalanceSnapshot"
      ErrorFrame:
        $ref: "#/components/messages/ErrorFrame"
      Health:
        $ref: "#/components/messages/Health"
      MakerQuote:
        $ref: "#/components/messages/MakerQuote"
      OpenRfq:
        $ref: "#/components/messages/OpenRfq"
      PingFrame:
        $ref: "#/components/messages/PingFrame"
      PongFrame:
        $ref: "#/components/messages/PongFrame"
      PriceFrame:
        $ref: "#/components/messages/PriceFrame"
      Quote:
        $ref: "#/components/messages/Quote"
      Rfq:
        $ref: "#/components/messages/Rfq"
      SubscribeFrame:
        $ref: "#/components/messages/SubscribeFrame"
      SubscribedFrame:
        $ref: "#/components/messages/SubscribedFrame"
      UnsubscribeFrame:
        $ref: "#/components/messages/UnsubscribeFrame"
      UnsubscribedFrame:
        $ref: "#/components/messages/UnsubscribedFrame"
components:
  messages:
    AuthFrame:
      contentType: application/json
      name: AuthFrame
      payload:
        description: |-
          client → us: handshake. The client signs the canonical request string for
          `GET /v1/rfq/ws` with an empty body using its HMAC secret — the same recipe as a
          REST request — and sends the access key, the signing time, and the base64
          signature. Silhouette verifies the HMAC and resolves the access key to the
          account it acts for. The socket is authenticated once here; later frames carry
          no signature.

          This frame must arrive within the server's handshake deadline — five seconds
          by default, operator-configurable. A connection that has not authenticated by
          then is sent an `error` frame carrying `AUTH_REQUIRED` and closed. A source's
          concurrent unauthenticated connections are capped as well, and a connection
          refused by that cap is answered with the same code before closing.
        properties:
          accessKey:
            description: "The public access key, as on the REST `Authorization: Bearer` header."
            type: string
          signature:
            description: Base64 HMAC-SHA256 signature over the canonical `GET /v1/rfq/ws` request.
            type: string
          signedAt:
            description: |-
              When the frame was signed, and the first line of the string the signature
              is computed over. It must fall within 30 seconds of receipt, be unique per
              access key, and be assigned in increasing order at issuance (arrival order
              is not constrained — the wire may reorder frames). This is the value the
              REST transport carries in the `Silhouette-API-Timestamp` header.
            examples:
              - 1700000000000
            format: int64
            minimum: 0
            type: integer
        required:
          - accessKey
          - signedAt
          - signature
        title: AuthFrame
        type: object
      summary: Authenticate the session (HMAC)
      title: AuthFrame
    BalanceSnapshot:
      contentType: application/json
      name: BalanceSnapshot
      payload:
        description: |-
          us → client: the client's current balances, one entry per token the account
          holds. Each entry is the same balance line `GET /v1/rfq/balances` returns.
          Sent once in answer to a `balances` subscription, then again on every
          committed change, delivered in per-user commit order. Each frame is the
          latest committed snapshot the client has received; a dropped push is
          recovered by the next snapshot or a REST refetch.
        properties:
          balances:
            items:
              $ref: "#/components/schemas/Balance"
            type: array
        required:
          - balances
        title: BalanceSnapshot
        type: object
      summary: Account balance snapshot
      title: BalanceSnapshot
    ErrorFrame:
      contentType: application/json
      name: ErrorFrame
      payload:
        description: |-
          us → client: an error for the offending frame, sent as
          `{"kind":"error","code":"RATE_LIMITED","message":"…"}`. The socket may
          stay open for recoverable rejections, or close after terminal
          handshake/protocol failures. The maker price-ingest socket at
          `/v1/rfq/prices/ws` sends it in the same envelope, so one reader
          dispatching on `kind` handles both sockets.
        properties:
          code:
            $ref: "#/components/schemas/ErrorCode"
          message:
            type: string
        required:
          - code
          - message
        title: ErrorFrame
        type: object
      summary: Error
      title: ErrorFrame
    Health:
      contentType: application/json
      name: Health
      payload:
        description: |-
          us → client: handshake — authentication accepted. `isMaker` reflects
          whether the account is registered as an active market maker.
        properties:
          isMaker:
            type: boolean
          userId:
            format: uuid
            type: string
        required:
          - userId
          - isMaker
        title: Health
        type: object
      summary: Session health
      title: Health
    LevelsFrame:
      contentType: application/json
      name: LevelsFrame
      payload:
        description: "MM → us: indicative price ladder on `/v1/rfq/prices/ws`."
        properties:
          asks:
            items:
              items:
                type: string
              maxItems: 2
              minItems: 2
              type: array
            type: array
          bids:
            items:
              items:
                type: string
              maxItems: 2
              minItems: 2
              type: array
            type: array
          instrumentId:
            $ref: "#/components/schemas/InstrumentId"
          ts:
            format: int64
            type: integer
        required:
          - instrumentId
          - bids
          - asks
          - ts
        title: LevelsFrame
        type: object
      summary: Maker price ladder
      title: LevelsFrame
    MakerQuote:
      contentType: application/json
      name: MakerQuote
      payload:
        description: |-
          us → maker: a maker's own quote and its outcome — pushed on the
          `quoteStatus` topic as each transition commits. When `status` is `SELECTED`
          on a settlement mode that settles with a Silhouette-signed Permit2, the
          frame carries that authorisation for the maker to relay, together with the
          `deliveryDeadline` the authorisation binds when the win is a promised
          delivery; a mode whose settlement needs no Silhouette signature (the maker
          delivers first, toward the omnibus) renders without the permit fields. The
          topic replays on subscribe and is delivered in per-user commit order, so a
          maker that reconnects re-fetches any outcome, and any Permit2, it missed.
        properties:
          acceptableForMs:
            description: |-
              The maker's acceptance window as submitted: how long the quote stays
              selectable and acceptable from `receivedAt`. A re-quote restamps
              `receivedAt`, so the window restarts with each submission. Absent
              when the submission named none — no maker cutoff.
            examples:
              - 60000
            format: int64
            minimum: 1
            type:
              - integer
              - "null"
          deliveryDeadline:
            description: |-
              Deadline for a promised delivery, in unix **seconds** — the exact value
              the Silhouette-signed authorisation binds, to pass unchanged into the
              settlement call. Present from `SELECTED` onward on a quote whose
              settlement is a promised delivery rather than an atomic fill, so a maker
              that reconnects while `PENDING_DELIVERY` still has the deadline its
              escrow needs.
            examples:
              - 1700000000
            format: int64
            minimum: 0
            type:
              - integer
              - "null"
          instrumentId:
            $ref: "#/components/schemas/InstrumentId"
          makerPays:
            $ref: "#/components/schemas/QuoteLeg"
          makerReceives:
            $ref: "#/components/schemas/QuoteLeg"
          permitSignature:
            description: |-
              Silhouette-signed Permit2 signature (0x-hex). Present only when
              `SELECTED`.
            type:
              - string
              - "null"
          perpFillAvgPx:
            description: |-
              Perp matched execution only: the realised average fill price for the
              settled size, in the quote token, as a decimal string. Present alongside
              `perpFilledSize`.
            examples:
              - "10.5"
            maxLength: 64
            pattern: ^(0|[1-9][0-9]*)(\.[0-9]+)?$
            type:
              - string
              - "null"
          perpFilledSize:
            description: |-
              Perp matched execution only: the taker's realised fill size, as a
              decimal string. A partially filled immediate-or-cancel order settles for
              this size, not the quoted size — present on a `SETTLED` perp quote,
              absent otherwise.
            examples:
              - "10.5"
            maxLength: 64
            pattern: ^(0|[1-9][0-9]*)(\.[0-9]+)?$
            type:
              - string
              - "null"
          position:
            description: |-
              The quote's ordinal behind the winner of a HyperCore cascade
              (1-indexed). Present only while the quote is a cascade backup.
            format: uint32
            minimum: 0
            type:
              - integer
              - "null"
          quoteId:
            type: string
          receivedAt:
            description: The time Silhouette received the quote, anchoring `acceptableForMs`.
            examples:
              - 1700000000000
            format: int64
            minimum: 0
            type: integer
          rfqId:
            type: string
          settledAt:
            description: |-
              When this quote's RFQ settled. Carried on the outcome so a maker that
              reconnects replays the settlement rather than re-deriving it.
            examples:
              - 1700000000000
            format: int64
            minimum: 0
            type:
              - integer
              - "null"
          settlementDeadline:
            description: |-
              The settlement deadline, in unix **seconds** — the latest instant this
              win may settle. An xchange authorisation is signed with Backed's own
              swap expiry, at or before it. Never scaled: the chain reads
              seconds, so the contract carries seconds (the `Deadline` suffix
              rule; `deliveryDeadline` is the other). Present from `SELECTED`
              onward — the same value the quote request published; here it stays
              on the outcome a reconnecting maker replays. Absent on a quote that
              never won.
            examples:
              - 1700000000
            format: int64
            minimum: 0
            type:
              - integer
              - "null"
          side:
            $ref: "#/components/schemas/Side"
          spender:
            description: Permit2 spender (the maker's wrapper). Present only when `SELECTED`.
            type:
              - string
              - "null"
          status:
            $ref: "#/components/schemas/QuoteStatus"
          txHash:
            description: The transaction that carried that settlement, alongside `settledAt`.
            type:
              - string
              - "null"
          windowEndsAt:
            description: |-
              The instant the window closes: the deadline by which a
              `PENDING_DELIVERY` quote's delivery must land. Present only on a
              promised quote that has a delivery window.
            examples:
              - 1700000000000
            format: int64
            minimum: 0
            type:
              - integer
              - "null"
          windowStartsAt:
            description: |-
              The instant the maker's promised delivery window opens. Present only on
              a promised quote that has a delivery window.
            examples:
              - 1700000000000
            format: int64
            minimum: 0
            type:
              - integer
              - "null"
        required:
          - rfqId
          - quoteId
          - instrumentId
          - side
          - status
          - makerPays
          - makerReceives
          - receivedAt
        title: MakerQuote
        type: object
      summary: Maker quote outcome
      title: MakerQuote
    OpenRfq:
      contentType: application/json
      name: OpenRfq
      payload:
        description: |-
          One open RFQ a maker may quote, as delivered on the `openRfqs` topic and
          listed by `GET /v1/rfq/maker/requests`. It carries what a maker needs to
          price the trade and nothing more: the taker's quote limit stays private to
          the taker.
        properties:
          auctionEndsAt:
            description: |-
              The instant the auction ends — the RFQ stops accepting quotes and the
              winner is selected.
            examples:
              - 1700000000000
            format: int64
            minimum: 0
            type: integer
          autoAccept:
            description: |-
              `true` if the winning quote auto-fills at the deadline unless the
              taker accepts sooner; `false` if the taker must accept a quote
              themselves.
            type: boolean
          baseQty:
            description: |-
              The quantity to quote on, in base-token units. On a `SELL` the taker fee
              is carved out of the base the taker delivers, so this is what the maker
              actually buys and a quote priced on any other quantity cannot win; on a
              `BUY` it is the full size the taker asked for.
            examples:
              - "10.5"
            maxLength: 64
            pattern: ^(0|[1-9][0-9]*)(\.[0-9]+)?$
            type: string
          createdAt:
            description: The instant the taker submitted the RFQ.
            examples:
              - 1700000000000
            format: int64
            minimum: 0
            type: integer
          id:
            description: |-
              Opaque identifier of the RFQ to quote, carrying the `rfq_` prefix. Send
              it back verbatim as the quote's `rfqId`; never parse it.
            type: string
          instrumentId:
            $ref: "#/components/schemas/InstrumentId"
          settlementDeadline:
            description: |-
              The settlement deadline for every quote on this RFQ, in unix
              seconds: `auctionEndsAt` rounded up to the next whole second plus
              the operator's settlement headroom. The latest instant a winning
              quote can settle: an inventory win is signed with exactly this, and an
              xchange win is signed with the expiry Backed stamped on its own swap,
              which must fall at or before it. Never derive this value.
            examples:
              - 1700000000
            format: int64
            minimum: 0
            type: integer
          side:
            $ref: "#/components/schemas/Side"
        required:
          - id
          - instrumentId
          - side
          - baseQty
          - autoAccept
          - auctionEndsAt
          - settlementDeadline
          - createdAt
        title: OpenRfq
        type: object
      summary: Open RFQ (to maker)
      title: OpenRfq
    PingFrame:
      contentType: application/json
      description: Application-level keepalive, sent by either side as `{"kind":"ping"}` and carrying no fields. A pair is exchanged roughly every 15 seconds; the receiver answers with `pong`. The maker price-ingest socket at `/v1/rfq/prices/ws` uses native WebSocket ping/pong instead and carries no application keepalive.
      name: PingFrame
      payload:
        description: The `ping` frame carries no fields.
        title: PingFrame
        type: object
      summary: Keepalive ping
      title: PingFrame
    PongFrame:
      contentType: application/json
      description: The answer to a `ping`, sent by either side as `{"kind":"pong"}` and carrying no fields. A `pong` is never itself answered, and an unsolicited one is ignored.
      name: PongFrame
      payload:
        description: The `pong` frame carries no fields.
        title: PongFrame
        type: object
      summary: Keepalive pong
      title: PongFrame
    PriceFrame:
      contentType: application/json
      description: "The current best bid and best ask for one instrument, aggregated across every maker streaming prices for it and attributed to none of them. Sent as `{\"kind\":\"price\", …}` with the top of book's fields flattened in alongside. A `prices` subscription receives the instrument's current top of book where one is known, then one frame per change to it. The price is indicative, not tradeable: a taker acts on it by raising an RFQ and choosing among the quotes it draws. Either side is `null` when no maker is showing that side."
      name: PriceFrame
      payload:
        allOf:
          - $ref: "#/components/schemas/TopOfBook"
        description: "The top of one instrument's book: the best bid and the best ask, each `null` when no maker is showing that side."
        title: PriceFrame
      summary: Top-of-book price tick
      title: PriceFrame
    Quote:
      contentType: application/json
      name: Quote
      payload:
        description: |-
          One competing quote on a taker's own RFQ, as the taker sees it when choosing
          which to accept. It carries the two legs, the all-in cost, and the quote's
          own lifecycle status; it carries no Permit2 authorisation, because that is
          the winning maker's settlement secret, and it names no maker, because the
          auction is anonymous to the taker.

          Read it from `GET /v1/rfq/requests/{id}/quotes`, or subscribe to `quotes`
          and receive the same object as each maker responds.
        properties:
          acceptableForMs:
            description: |-
              The maker's acceptance window: how long the quote stays acceptable,
              counted from `receivedAt`. A re-quote restamps `receivedAt`, so the
              window restarts with each submission. Absent when the maker named
              none — the quote then stands until the auction closes. The auction
              close bounds acceptance either way.
            examples:
              - 60000
            format: int64
            minimum: 1
            type:
              - integer
              - "null"
          builderFeeTenthBp:
            description: |-
              The market's builder fee rate, in tenths of a basis point of the
              taker's notional (10 = 1 basis point). The venue charges it on the
              taker's order and credits Silhouette's builder address for the
              environment, which Silhouette publishes to integrators out of band and
              holds for every market on it. Sign the accept's pre-signed order with
              that address and this exact rate. Absent when the market has no builder
              fee, and the order must then carry no attribution. The venue caps a
              perp builder fee at 0.1%, so a rate never exceeds 100.
            format: uint16
            maximum: 100
            minimum: 1
            type:
              - integer
              - "null"
          instrumentId:
            $ref: "#/components/schemas/InstrumentId"
          makerPays:
            $ref: "#/components/schemas/QuoteLeg"
          makerReceives:
            $ref: "#/components/schemas/QuoteLeg"
          perpFillAvgPx:
            description: |-
              Perp matched execution only: the realised average fill price for the
              settled size, in the quote token, as a decimal string. Present alongside
              `perpFilledSize`.
            examples:
              - "10.5"
            maxLength: 64
            pattern: ^(0|[1-9][0-9]*)(\.[0-9]+)?$
            type:
              - string
              - "null"
          perpFilledSize:
            description: |-
              Perp matched execution only: the taker's realised fill size, as a decimal
              string. A partially filled immediate-or-cancel order settles for this
              size, not the quoted size — present on a `SETTLED` perp quote, absent
              otherwise.
            examples:
              - "10.5"
            maxLength: 64
            pattern: ^(0|[1-9][0-9]*)(\.[0-9]+)?$
            type:
              - string
              - "null"
          quoteId:
            description: |-
              Opaque identifier of this quote, carrying the `qt_` prefix. Send it back
              verbatim when accepting; never parse it.
            type: string
          receivedAt:
            description: |-
              The time Silhouette received the quote, for ordering competing quotes
              and anchoring `acceptableForMs`.
            examples:
              - 1700000000000
            format: int64
            minimum: 0
            type: integer
          rfqId:
            description: |-
              The RFQ this quote answers. A quote is a resource in its own right, so
              it names its RFQ rather than relying on where it was read from.
            type: string
          side:
            $ref: "#/components/schemas/Side"
          status:
            $ref: "#/components/schemas/QuoteStatus"
          takerTotal:
            description: |-
              The taker's all-in cost in the pay token: `makerReceives.amount` plus the
              taker fee, baked into one price. This is what the taker pays for
              `makerPays.amount` of the received asset; the fee is never a separate
              line.
            examples:
              - "10.5"
            maxLength: 64
            pattern: ^(0|[1-9][0-9]*)(\.[0-9]+)?$
            type: string
          version:
            description: |-
              Which version of this quote's terms you are reading. A maker re-pricing
              its quote keeps the same `quoteId` and advances this, so the id alone
              does not tell one set of terms from the next.

              Send it back as `expectedVersion` when accepting and the acceptance is
              refused, rather than executed at terms you never saw, if the quote moved
              in between. Opaque and monotonic: compare for equality, never order or
              arithmetic.
            format: int64
            type: integer
        required:
          - rfqId
          - quoteId
          - version
          - instrumentId
          - side
          - status
          - makerPays
          - makerReceives
          - takerTotal
          - receivedAt
        title: Quote
        type: object
      summary: Competing quote (to taker)
      title: Quote
    Rfq:
      contentType: application/json
      name: Rfq
      payload:
        description: |-
          One RFQ as its owning taker sees it, across the whole lifecycle. A trade is
          an RFQ in `SETTLED` carrying the settlement transaction hash, not a separate
          resource. Amounts are canonical decimal strings and every instant is unix
          milliseconds.

          Read it from `GET /v1/rfq/requests/{id}`, or subscribe to `rfqStatus` and
          receive the same object on each transition — the push carries the full view
          rather than an id and a status, so a subscriber never refetches to render
          it.
        properties:
          auctionEndsAt:
            description: |-
              The instant the auction ends: quoting closes and the winner is
              selected. The RFQ itself lives on through settlement — the pair with
              the request's `windowMs` ("send a duration, read back the instant").
            examples:
              - 1700000000000
            format: int64
            minimum: 0
            type: integer
          autoAccept:
            description: |-
              `true` when the request auto-fills at the deadline, though the taker
              may still accept a quote before then; `false` when only an explicit
              accept fills it. A client uses this to decide whether to offer an
              accept action.
            type: boolean
          baseQty:
            description: Exact non-negative canonical decimal string in human-readable units. Responses use canonical formatting with no scientific notation, separators, leading plus sign, unnecessary leading zeros, trailing fractional zeros, or decimal point for integer values. Request parsing accepts and normalises semantically equivalent trailing fractional zeros such as `1.0` or `0.000`; leading plus signs, unnecessary leading zeros, whitespace, scientific notation, missing integer digits, and missing fractional digits are rejected. Once the trailing zeros are stripped, a request value carrying more fractional digits than the field's token supports is rejected as `AMOUNT_OVER_PRECISE`, whose `message` names the maximum — never silently truncated.
            examples:
              - "10.5"
            maxLength: 64
            pattern: ^(0|[1-9][0-9]*)(\.[0-9]+)?$
            type: string
          createdAt:
            description: The instant the taker submitted the RFQ.
            examples:
              - 1700000000000
            format: int64
            minimum: 0
            type: integer
          executedPayAmount:
            description: |-
              What you paid all in, in the token you paid with: the winning quote's
              taker-side leg plus `takerFee`. Present once the RFQ settles, absent on
              every other status and on a perp cross, whose realised fill lives on
              the quote as `perpFilledSize` and `perpFillAvgPx`. This is the executed amount, not the amount
              requested — a `quoteLimit` bounds it, it does not name it.
            examples:
              - "10.5"
            maxLength: 64
            pattern: ^(0|[1-9][0-9]*)(\.[0-9]+)?$
            type:
              - string
              - "null"
          executedRecvAmount:
            description: |-
              What you received, in the token you received. Present once the RFQ
              settles, on the same terms as `executedPayAmount`. Divide it by `executedPayAmount` for the all-in rate; both
              amounts are exact, so no rounding is imposed on that ratio here.
            examples:
              - "10.5"
            maxLength: 64
            pattern: ^(0|[1-9][0-9]*)(\.[0-9]+)?$
            type:
              - string
              - "null"
          failureCode:
            anyOf:
              - $ref: "#/components/schemas/RfqFailureCode"
              - type: "null"
            description: |-
              The same failure as a stable code. Branch on this; the reason's
              wording is not a contract. Absent on a request that ended before the
              field existed, so treat it as optional even beside a `failureReason`.
          failureReason:
            anyOf:
              - $ref: "#/components/schemas/PublicReason"
              - type: "null"
            description: |-
              Why an RFQ ended without settling, worded for a person. Present on a
              `FAILED` request, and on a `CANCELLED` one the taker called off;
              absent on every other status.
          id:
            description: |-
              Opaque identifier of this RFQ, carrying the `rfq_` prefix. Echo it
              verbatim on the endpoints that take an RFQ id; never parse it.
            type: string
          instrumentId:
            $ref: "#/components/schemas/InstrumentId"
          quoteLimit:
            description: |-
              The taker's own price bound, in the quote token. Never shown to makers.
              Absent on a taker-driven RFQ submitted without one, where the taker
              accepts a quote explicitly and no bound is applied.
            examples:
              - "10.5"
            maxLength: 64
            pattern: ^(0|[1-9][0-9]*)(\.[0-9]+)?$
            type:
              - string
              - "null"
          quotedAt:
            description: The instant a winning quote was committed. Absent until one is.
            examples:
              - 1700000000000
            format: int64
            minimum: 0
            type:
              - integer
              - "null"
          settledAt:
            description: The instant the trade settled. Absent until it does.
            examples:
              - 1700000000000
            format: int64
            minimum: 0
            type:
              - integer
              - "null"
          side:
            $ref: "#/components/schemas/Side"
          status:
            $ref: "#/components/schemas/RfqStatus"
          takerFee:
            description: |-
              The taker fee on this RFQ, in the pay token, frozen when the pay side
              was committed. `executedPayAmount` already includes it, so this says
              what that price was made of rather than naming a second charge. Absent
              until the RFQ settles: an RFQ that ends without settling refunds it
              with the rest of the lock, and a perp cross charges none.
            examples:
              - "10.5"
            maxLength: 64
            pattern: ^(0|[1-9][0-9]*)(\.[0-9]+)?$
            type:
              - string
              - "null"
          txHash:
            description: The settlement transaction hash. Present once the RFQ is `SETTLED`.
            type:
              - string
              - "null"
        required:
          - id
          - instrumentId
          - side
          - baseQty
          - status
          - autoAccept
          - createdAt
          - auctionEndsAt
        title: Rfq
        type: object
      summary: RFQ status update
      title: Rfq
    SubscribeFrame:
      contentType: application/json
      description: "Opts the session in to one topic. Sent as `{\"kind\":\"subscribe\"}` with the selector's `topic` and its parameters flattened in alongside — `{\"kind\":\"subscribe\",\"topic\":\"prices\",\"instrumentId\":\"XTSLA-USDC-SPOT\"}`. The server answers with `subscribed`, followed by the topic's current state where it has one. Dispatch is opt-in: nothing is pushed on a topic until the session subscribes to it, and a subscription lasts for the life of the connection. A topic the session may not hold is refused with an `error` frame carrying `UNSUPPORTED_CHANNEL`, and the socket stays open. That covers an `openRfqs` or `quoteStatus` subscription from an account with no active maker record, and — for `openRfqs` alone — a maker that holds the record but may not quote *this* instrument: it must also be approved for the instrument's pair and operate at least one settlement adapter."
      name: SubscribeFrame
      payload:
        description: The topic the `subscribe` frame opts in to, tagged by `topic` and carrying that topic's parameters.
        oneOf:
          - description: |-
              Top-of-book ticks for one instrument. Public market data: any
              authenticated session may subscribe to any market. The current tick is
              sent on subscribe where one is known, then a `price` frame per
              top-of-book change.
            properties:
              instrumentId:
                $ref: "#/components/schemas/InstrumentId"
              topic:
                const: prices
                type: string
            required:
              - topic
              - instrumentId
            type: object
          - description: |-
              The authenticated account's whole-account balances: a `balanceSnapshot`
              of every token on subscribe, then a fresh snapshot on every committed
              change, in commit order.
            properties:
              topic:
                const: balances
                type: string
            required:
              - topic
            type: object
          - description: |-
              Status transitions for the authenticated account's own RFQs: an
              `rfqStatus` for each open RFQ on subscribe, then one per transition.
            properties:
              topic:
                const: rfqStatus
                type: string
            required:
              - topic
            type: object
          - description: |-
              Competing quotes as they arrive on the authenticated account's own RFQs,
              one `quoteUpdate` per quote. Live-only, so nothing is replayed on
              subscribe.
            properties:
              topic:
                const: quotes
                type: string
            required:
              - topic
            type: object
          - description: |-
              Outcome updates for a maker's own quotes, one `quoteStatus` per
              transition; requires an active maker record. The maker's recent quotes
              are replayed on subscribe, so a reconnecting maker recovers the
              outcomes it missed, and any Silhouette-signed Permit2 they carry.
            properties:
              topic:
                const: quoteStatus
                type: string
            required:
              - topic
            type: object
          - description: |-
              Open RFQs for one instrument, as `rfq` frames. Requires an active
              maker record, approval for the instrument's pair, and at least one
              operated settlement adapter. Holding this subscription is also what
              makes the maker eligible to be quoted on that instrument.
            properties:
              instrumentId:
                $ref: "#/components/schemas/InstrumentId"
              topic:
                const: openRfqs
                type: string
            required:
              - topic
              - instrumentId
            type: object
        title: SubscribeFrame
      summary: Subscribe to a topic
      title: SubscribeFrame
    SubscribedFrame:
      contentType: application/json
      description: "Acknowledges a `subscribe`, echoing the selector back — `{\"kind\":\"subscribed\",\"topic\":\"balances\"}`. On the topics that carry a baseline the acknowledgement is followed by the current state before the stream of updates begins: a `balanceSnapshot` for `balances`, an `rfqStatus` for each of the account's open RFQs, a `quoteStatus` for each of the maker's recent quotes, and the current tick for `prices` where one is known. The `quotes` and `openRfqs` topics are live-only, so the acknowledgement is all that precedes their first push."
      name: SubscribedFrame
      payload:
        description: The topic now subscribed, echoed back exactly as the `subscribe` frame named it.
        oneOf:
          - description: |-
              Top-of-book ticks for one instrument. Public market data: any
              authenticated session may subscribe to any market. The current tick is
              sent on subscribe where one is known, then a `price` frame per
              top-of-book change.
            properties:
              instrumentId:
                $ref: "#/components/schemas/InstrumentId"
              topic:
                const: prices
                type: string
            required:
              - topic
              - instrumentId
            type: object
          - description: |-
              The authenticated account's whole-account balances: a `balanceSnapshot`
              of every token on subscribe, then a fresh snapshot on every committed
              change, in commit order.
            properties:
              topic:
                const: balances
                type: string
            required:
              - topic
            type: object
          - description: |-
              Status transitions for the authenticated account's own RFQs: an
              `rfqStatus` for each open RFQ on subscribe, then one per transition.
            properties:
              topic:
                const: rfqStatus
                type: string
            required:
              - topic
            type: object
          - description: |-
              Competing quotes as they arrive on the authenticated account's own RFQs,
              one `quoteUpdate` per quote. Live-only, so nothing is replayed on
              subscribe.
            properties:
              topic:
                const: quotes
                type: string
            required:
              - topic
            type: object
          - description: |-
              Outcome updates for a maker's own quotes, one `quoteStatus` per
              transition; requires an active maker record. The maker's recent quotes
              are replayed on subscribe, so a reconnecting maker recovers the
              outcomes it missed, and any Silhouette-signed Permit2 they carry.
            properties:
              topic:
                const: quoteStatus
                type: string
            required:
              - topic
            type: object
          - description: |-
              Open RFQs for one instrument, as `rfq` frames. Requires an active
              maker record, approval for the instrument's pair, and at least one
              operated settlement adapter. Holding this subscription is also what
              makes the maker eligible to be quoted on that instrument.
            properties:
              instrumentId:
                $ref: "#/components/schemas/InstrumentId"
              topic:
                const: openRfqs
                type: string
            required:
              - topic
              - instrumentId
            type: object
        title: SubscribedFrame
      summary: Subscription accepted
      title: SubscribedFrame
    UnsubscribeFrame:
      contentType: application/json
      description: Opts the session back out of one topic, named by the same selector the `subscribe` carried — `{"kind":"unsubscribe","topic":"prices","instrumentId":"XTSLA-USDC-SPOT"}`. The server answers with `unsubscribed` and pushes no further frames for that topic. Unsubscribing from a topic the session does not hold is accepted and acknowledged rather than refused.
      name: UnsubscribeFrame
      payload:
        description: The topic the `unsubscribe` frame opts out of, tagged by `topic` and carrying that topic's parameters.
        oneOf:
          - description: |-
              Top-of-book ticks for one instrument. Public market data: any
              authenticated session may subscribe to any market. The current tick is
              sent on subscribe where one is known, then a `price` frame per
              top-of-book change.
            properties:
              instrumentId:
                $ref: "#/components/schemas/InstrumentId"
              topic:
                const: prices
                type: string
            required:
              - topic
              - instrumentId
            type: object
          - description: |-
              The authenticated account's whole-account balances: a `balanceSnapshot`
              of every token on subscribe, then a fresh snapshot on every committed
              change, in commit order.
            properties:
              topic:
                const: balances
                type: string
            required:
              - topic
            type: object
          - description: |-
              Status transitions for the authenticated account's own RFQs: an
              `rfqStatus` for each open RFQ on subscribe, then one per transition.
            properties:
              topic:
                const: rfqStatus
                type: string
            required:
              - topic
            type: object
          - description: |-
              Competing quotes as they arrive on the authenticated account's own RFQs,
              one `quoteUpdate` per quote. Live-only, so nothing is replayed on
              subscribe.
            properties:
              topic:
                const: quotes
                type: string
            required:
              - topic
            type: object
          - description: |-
              Outcome updates for a maker's own quotes, one `quoteStatus` per
              transition; requires an active maker record. The maker's recent quotes
              are replayed on subscribe, so a reconnecting maker recovers the
              outcomes it missed, and any Silhouette-signed Permit2 they carry.
            properties:
              topic:
                const: quoteStatus
                type: string
            required:
              - topic
            type: object
          - description: |-
              Open RFQs for one instrument, as `rfq` frames. Requires an active
              maker record, approval for the instrument's pair, and at least one
              operated settlement adapter. Holding this subscription is also what
              makes the maker eligible to be quoted on that instrument.
            properties:
              instrumentId:
                $ref: "#/components/schemas/InstrumentId"
              topic:
                const: openRfqs
                type: string
            required:
              - topic
              - instrumentId
            type: object
        title: UnsubscribeFrame
      summary: Unsubscribe from a topic
      title: UnsubscribeFrame
    UnsubscribedFrame:
      contentType: application/json
      description: Acknowledges an `unsubscribe`, echoing the selector back — `{"kind":"unsubscribed","topic":"openRfqs","instrumentId":"XTSLA-USDC-SPOT"}`. It is the last frame the session receives for that topic.
      name: UnsubscribedFrame
      payload:
        description: The topic no longer subscribed, echoed back exactly as the `unsubscribe` frame named it.
        oneOf:
          - description: |-
              Top-of-book ticks for one instrument. Public market data: any
              authenticated session may subscribe to any market. The current tick is
              sent on subscribe where one is known, then a `price` frame per
              top-of-book change.
            properties:
              instrumentId:
                $ref: "#/components/schemas/InstrumentId"
              topic:
                const: prices
                type: string
            required:
              - topic
              - instrumentId
            type: object
          - description: |-
              The authenticated account's whole-account balances: a `balanceSnapshot`
              of every token on subscribe, then a fresh snapshot on every committed
              change, in commit order.
            properties:
              topic:
                const: balances
                type: string
            required:
              - topic
            type: object
          - description: |-
              Status transitions for the authenticated account's own RFQs: an
              `rfqStatus` for each open RFQ on subscribe, then one per transition.
            properties:
              topic:
                const: rfqStatus
                type: string
            required:
              - topic
            type: object
          - description: |-
              Competing quotes as they arrive on the authenticated account's own RFQs,
              one `quoteUpdate` per quote. Live-only, so nothing is replayed on
              subscribe.
            properties:
              topic:
                const: quotes
                type: string
            required:
              - topic
            type: object
          - description: |-
              Outcome updates for a maker's own quotes, one `quoteStatus` per
              transition; requires an active maker record. The maker's recent quotes
              are replayed on subscribe, so a reconnecting maker recovers the
              outcomes it missed, and any Silhouette-signed Permit2 they carry.
            properties:
              topic:
                const: quoteStatus
                type: string
            required:
              - topic
            type: object
          - description: |-
              Open RFQs for one instrument, as `rfq` frames. Requires an active
              maker record, approval for the instrument's pair, and at least one
              operated settlement adapter. Holding this subscription is also what
              makes the maker eligible to be quoted on that instrument.
            properties:
              instrumentId:
                $ref: "#/components/schemas/InstrumentId"
              topic:
                const: openRfqs
                type: string
            required:
              - topic
              - instrumentId
            type: object
        title: UnsubscribedFrame
      summary: Subscription dropped
      title: UnsubscribedFrame
  schemas:
    Balance:
      description: |-
        One token's balance on the account, split into the three sub-balances that
        together make up the account's claim on it. Amounts are canonical decimal
        strings in human token units, and the whole claim is their sum.

        Read it from `GET /v1/rfq/balances`, or subscribe to `balances` and receive
        the same object inside every snapshot.
      properties:
        available:
          description: |-
            The account's unreserved claim on this token. A withdrawal or RFQ that
            must complete in one request may accept less than this aggregate.
          examples:
            - "10.5"
          maxLength: 64
          pattern: ^(0|[1-9][0-9]*)(\.[0-9]+)?$
          type: string
        locked:
          description: |-
            Held against a committed RFQ and unspendable until it resolves.
            Settlement consumes the hold; an RFQ that ends without settling releases
            it back to `available`.
          examples:
            - "10.5"
          maxLength: 64
          pattern: ^(0|[1-9][0-9]*)(\.[0-9]+)?$
          type: string
        pending:
          description: |-
            Value credited for a committed trade the maker has not delivered yet
            (a promised-mode settlement inside its delivery window). Owed to the
            account but not withdrawable or tradeable until delivery makes it
            available; a failed delivery reverses it and the account is made
            whole. `"0"` outside a delivery window.
          examples:
            - "10.5"
          maxLength: 64
          pattern: ^(0|[1-9][0-9]*)(\.[0-9]+)?$
          type: string
        token:
          allOf:
            - $ref: "#/components/schemas/TokenSymbol"
          description: |-
            Canonical symbol of the token this line is for. Resolve it to an ERC-20
            contract, when one is needed on-chain, via `GET /v1/rfq/tokens`.
        withdrawable:
          description: |-
            The largest amount one request can withdraw. Size a request against this
            figure: one above it is refused however much `available` holds.

            A snapshot, not a reservation. It moves with the account's activity.
          examples:
            - "10.5"
          maxLength: 64
          pattern: ^(0|[1-9][0-9]*)(\.[0-9]+)?$
          type: string
      required:
        - token
        - available
        - locked
        - pending
        - withdrawable
      type: object
    ErrorCode:
      description: |-
        Stable machine-readable code for a WebSocket `error` frame. The code names
        the condition, not the disposition — whether the socket survives is
        per-case: `AUTH_REQUIRED` closes the session, `UNSUPPORTED_CHANNEL`,
        `RATE_LIMITED`, `INVALID_INSTRUMENT_ID`, `UNKNOWN_INSTRUMENT` and
        `UNAVAILABLE` refuse the offending frame and leave it open, and
        `INVALID_MESSAGE` closes the session unless it answers a frame sent out of
        sequence.

        `UNAVAILABLE` means the request could not be completed right now and
        nothing was changed — transient by construction, so retry the frame.
      enum:
        - AUTH_REQUIRED
        - INVALID_MESSAGE
        - UNSUPPORTED_CHANNEL
        - RATE_LIMITED
        - INVALID_INSTRUMENT_ID
        - UNKNOWN_INSTRUMENT
        - UNAVAILABLE
      type: string
    InstrumentId:
      description: "Canonical instrument identifier: uppercase `BASE-QUOTE-TYPE` (e.g. `XTSLA-USDC-SPOT`). Every response carries the canonical form. A request may send a lenient variant such as `xTSLA/USDC/SPOT`, which is normalised on the way in — which is why the pattern here admits either separator and either case, and surrounding whitespace, rather than the canonical form alone. The length cap applies to the normalised value. Anything that is not a well-formed `BASE-QUOTE-TYPE` naming a known instrument type is rejected."
      examples:
        - XTSLA-USDC-SPOT
      maxLength: 128
      pattern: ^\s*[A-Za-z0-9]+[-/][A-Za-z0-9]+[-/](?:[Ss][Pp][Oo][Tt]|[Pp][Ee][Rr][Pp])\s*$
      type: string
    PublicReason:
      description: "Human-readable reason for a failure or refusal, written for the account reading it. A fixed phrase, not a stable machine contract: display it, and expect the wording to change."
      examples:
        - no quote arrived before the deadline
      type: string
    QuoteLeg:
      description: |-
        One leg of a two-leg quote: a token and the amount of it. A quote is
        described by the two legs the maker commits to, what it pays out and what it
        receives, so a leg's meaning never flips with the RFQ's side.
      properties:
        amount:
          description: Leg amount in human token units, as a decimal string.
          examples:
            - "10.5"
          maxLength: 64
          pattern: ^(0|[1-9][0-9]*)(\.[0-9]+)?$
          type: string
        token:
          allOf:
            - $ref: "#/components/schemas/TokenSymbol"
          description: |-
            Canonical symbol of this leg's token — the instrument's base or quote
            token. Resolve it to an ERC-20 contract, when one is needed on-chain,
            via `GET /v1/rfq/tokens`.
      required:
        - token
        - amount
      type: object
    QuoteStatus:
      description: |-
        Lifecycle of an RFQ quote as clients see it. A quote arrives `SUBMITTED` and
        competes in the open auction. At the auction deadline one quote is
        `SELECTED` and the rest are `NOT_SELECTED`, or every quote is `EXPIRED` when
        the window closed with no conforming quote; a maker may instead retract a
        still-`SUBMITTED` quote, taking it to `CANCELLED`. A winner that settles
        atomically goes straight to `SETTLED`, and one whose settlement does not
        complete ends `FAILED`.

        A winning promised quote goes to `PENDING_DELIVERY` for as long as its maker
        owes the on-chain delivery, and from there to `SETTLED` on delivery or
        `DEFAULTED` when the maker misses its window. `DEFAULTED` is distinct from
        `FAILED` because it is attributable to the maker and carries consequences.

        This is an open set: values may be added, so a client must tolerate one it
        does not recognise rather than failing to parse the frame or response.
      oneOf:
        - const: SUBMITTED
          description: The quote was accepted and is competing in the open auction.
          type: string
        - const: SELECTED
          description: |-
            The RFQ was awarded to this quote. At most one of a maker's quotes on
            an RFQ reaches this state, and only this state carries the
            Silhouette-signed Permit2 authorisation when the quote's settlement
            mode settles with one.
          type: string
        - const: NOT_SELECTED
          description: A conforming quote that lost the auction.
          type: string
        - const: EXPIRED
          description: The auction closed without selecting this quote.
          type: string
        - const: PENDING_DELIVERY
          description: |-
            A `SELECTED` promised quote whose maker owes an on-chain delivery
            within its delivery window. The window bounds ride on the maker's own
            view of the quote; the taker's funds stay locked until the delivery
            resolves.
          type: string
        - const: SETTLED
          description: Settlement is observed and the RFQ is done.
          type: string
        - const: FAILED
          description: A `SELECTED` quote whose settlement did not complete.
          type: string
        - const: DEFAULTED
          description: |-
            A `SELECTED` promised quote whose maker did not deliver within its
            window. The taker is made whole.
          type: string
        - const: CANCELLED
          description: The maker retracted the quote before selection.
          type: string
    RfqFailureCode:
      description: |-
        Why an RFQ ended without a settlement.

        Present exactly on a `FAILED` or `CANCELLED` request, and absent on one that
        ended before this field existed. `CANCELLED_BY_TAKER` is the only code a
        `CANCELLED` request carries; every other value rides a `FAILED` one.

        Branch on this rather than `failureReason`, whose wording may change. An
        open set: tolerate a value you do not recognise rather than failing.

        Every value says no trade happened except `EXECUTION_UNCONFIRMED`, which
        says the outcome is unknown — check your venue position before re-trading.

        **No quote won the auction.**
        - `NO_QUOTES` — no maker quoted before the auction deadline. Market hours
          may affect quoting: an instrument tied to an underlying market session
          draws the most quotes while that market is open, whatever the session
          was at the deadline.
        - `NO_CONFORMING_QUOTE` — quotes arrived, but none matched the request's
          terms.
        - `PRICE_LIMIT_NOT_MET` — quotes arrived and were well-formed, but none came
          within the `quoteLimit` the request set.
        - `NOT_ACCEPTED` — a taker-driven request reached its deadline with no quote
          accepted. Nothing was locked and nothing is owed.

        **A quote won, and then the trade did not complete.**
        - `QUOTE_EXPIRED` — the winning quote's deadline passed before its
          settlement was observed.
        - `DELIVERY_WINDOW_EXHAUSTED` — a winning quote's delivery window closed with
          no delivery. The taker is made whole.
        - `SETTLEMENT_FAILED` — a maker, its adapter, or the venue refused, at
          selection or after. The funds are released.
        - `EXECUTION_NOT_FILLED` — a matched execution produced no fill for the
          taker: the venue said so, or the cross never started. Safe to try again.
        - `EXECUTION_UNCONFIRMED` — a matched execution's outcome is **not known**.
          Check your position at the venue before opening new exposure; retrying
          blind can double it against a fill that did land.

        **The request could not proceed.**
        - `INSUFFICIENT_BALANCE` — the account could not cover the side the
          acceptance committed it to.
        - `EXCEEDS_WITHDRAWABLE` — the claim covers the request but no single
          part of the balance does. Resize from the bound the refusal reported.
        - `TRADING_PAUSED` — trading is paused, so the request could not proceed.
        - `INSTRUMENT_UNAVAILABLE` — the instrument is unknown or not currently
          tradable.
        - `INTERNAL_ERROR` — something on our side failed while handling the
          request. No trade happened and anything held against it is released.

        **The taker ended it.**
        - `CANCELLED_BY_TAKER` — the taker called its own still-open request off.
      oneOf:
        - const: NO_QUOTES
          description: |-
            No maker quoted before the auction deadline. Market hours may affect
            quoting: an instrument tied to an underlying market session draws the
            most quotes while that market is open.
          type: string
        - const: NO_CONFORMING_QUOTE
          description: Quotes arrived, but none matched the request's terms.
          type: string
        - const: PRICE_LIMIT_NOT_MET
          description: |-
            Quotes arrived, but none came within the `quoteLimit` the request set.
            The quotes were well-formed; the price was not acceptable.
          type: string
        - const: NOT_ACCEPTED
          description: |-
            A taker-driven request reached its deadline with no quote accepted.
            Nothing was locked and nothing is owed.
          type: string
        - const: QUOTE_EXPIRED
          description: The winning quote's deadline passed before its settlement was observed.
          type: string
        - const: DELIVERY_WINDOW_EXHAUSTED
          description: |-
            A winning quote's delivery window closed with no delivery. The taker is
            made whole.
          type: string
        - const: SETTLEMENT_FAILED
          description: |-
            No quote could be settled — a maker, its adapter, or the venue refused,
            at selection or after. The trade did not happen and the funds are
            released.
          type: string
        - const: EXECUTION_NOT_FILLED
          description: |-
            A matched execution produced no fill for the taker — the venue said so,
            or the cross never started. Nothing was traded, so it is safe to try
            again.
          type: string
        - const: EXECUTION_UNCONFIRMED
          description: |-
            A matched execution's outcome is **not known**.
            The one code that is not a statement that nothing happened: check your
            position at the venue before opening new exposure. Retrying blind can
            double your exposure against a fill that did land.
          type: string
        - const: INSUFFICIENT_BALANCE
          description: The account could not cover the side the acceptance committed it to.
          type: string
        - const: EXCEEDS_WITHDRAWABLE
          description: The claim covers the request but no single part of the balance does.
          type: string
        - const: TRADING_PAUSED
          description: Trading is paused, so the request could not proceed.
          type: string
        - const: INSTRUMENT_UNAVAILABLE
          description: The instrument is unknown or not currently tradable.
          type: string
        - const: INTERNAL_ERROR
          description: |-
            Something on our side failed while handling the request. No trade
            happened and anything held against it is released.
          type: string
        - const: CANCELLED_BY_TAKER
          description: |-
            The taker called its own still-open request off. The only code a
            `CANCELLED` request carries.
          type: string
    RfqStatus:
      description: |-
        Lifecycle of an RFQ. `PENDING` is the request while it collects quotes.
        Selection at the deadline (or an explicit taker accept) moves it to
        `QUOTED` when the winning quote carries a settlement artefact to observe —
        a signed Permit2 permit or an on-chain escrow — or straight to the terminal
        `SETTLED` when the winning quote's settlement mode settles atomically in
        the selection transaction. A winning promised quote with no artefact to
        watch instead moves to `PENDING_DELIVERY`.

        Both in-flight states are non-terminal, and they wait on different things.
        A `QUOTED` request waits for its settlement to be observed — the permit
        spent, or the escrow funded — and reaches `SETTLED` when it is, `FAILED`
        when the deadline passes without it. A `PENDING_DELIVERY` request waits for
        the maker to deliver within its window, and reaches `SETTLED` on the
        observed delivery, `FAILED` once the window is exhausted. A trade is an RFQ
        in `SETTLED`.

        The two unsettled terminals divide on whether the trade was called off.
        `CANCELLED` means a party ended it deliberately before settlement: in this
        version, the taker cancelling its own still-`PENDING` request. `FAILED`
        covers every other ending without a settlement — no quotes arrived, none
        conformed, no quote was accepted before the deadline, a committed permit
        deadline passed with no fill, or a delivery window was exhausted. Which
        one it was is carried by `failureCode`, not by the status; locked funds
        are released either way.

        This is an open set: values may be added, so a client must tolerate one it
        does not recognise rather than failing to parse the frame or response.
      oneOf:
        - enum:
            - PENDING
            - QUOTED
            - SETTLED
            - FAILED
            - CANCELLED
          type: string
        - const: PENDING_DELIVERY
          description: |-
            The winning maker has committed but its promised delivery has not
            landed yet — in flight, not terminal. The taker's pending balance
            rides on it, and it resolves to `SETTLED` once the delivery is
            observed or `FAILED` once the delivery window is exhausted.
          type: string
    Side:
      enum:
        - BUY
        - SELL
      type: string
    TokenSymbol:
      description: Canonical uppercase token symbol, e.g. `USDC`.
      examples:
        - USDC
      maxLength: 32
      pattern: ^[A-Z0-9]+$
      type: string
    TopOfBook:
      description: |-
        The top of one instrument's book at a moment in time: the best bid and the
        best ask, aggregated across every maker streaming prices for the instrument
        and attributed to none of them. Either side is `null` when no maker is
        showing that side.

        Indicative rather than tradeable. A taker acts on it by raising an RFQ and
        choosing among the quotes it draws, not by hitting the price. The same
        object serves both surfaces: the latest one known rides on an instrument as
        `topOfBook`, giving a client a value before it opens the live feed, and the
        `price` frame on a `prices` subscription carries each change as it happens.
        The two surfaces diverge once the last maker stops quoting the instrument:
        `topOfBook` is omitted from then on, but the `price` frame still carries one
        final, both-sides-`null` frame clearing the instrument before subscribers
        hear nothing further.
      properties:
        ask:
          description: Best ask as a decimal string; `null` if there is no ask.
          type:
            - string
            - "null"
        bid:
          description: Best bid as a decimal string; `null` if there is no bid.
          type:
            - string
            - "null"
        instrumentId:
          allOf:
            - $ref: "#/components/schemas/InstrumentId"
          description: The instrument these prices are for.
        ts:
          description: The instant this top of book was published, in unix milliseconds.
          examples:
            - 1700000000000
          format: int64
          minimum: 0
          type: integer
      required:
        - instrumentId
        - ts
      type: object
info:
  contact:
    name: Silhouette API support
    url: https://docs.silhouette.exchange
  description: "WebSocket streaming for Silhouette. `/v1/rfq/ws` is the shared client stream: on connect a client sends an HMAC auth frame — the login credential pair signs a canonical `GET /v1/rfq/ws`, the same recipe as REST — then opts in to topics. That auth frame must arrive within the server's handshake deadline, five seconds by default and operator-configurable, and one source's concurrent unauthenticated connections are capped; a connection closed or refused by either bound is sent an `error` frame carrying `AUTH_REQUIRED`. `/v1/rfq/prices/ws` is maker-only price ingest: the HTTP upgrade request is HMAC-authenticated with the normal REST headers and each message is a bare maker price ladder with no envelope; neither pre-authentication bound applies to it. Every client-initiated mutation goes over REST; these sockets carry the handshake, the subscriptions, and the pushes."
  license:
    name: Proprietary
    url: https://silhouette.exchange/terms
  tags:
    - description: Requesting quotes, watching an RFQ, and reading the quotes competing on it.
      name: rfq
    - description: "Prices: a maker's ladder in on the ingest stream, top of book out on the shared one."
      name: market-data
    - description: Session authentication, the topics a session selects, and the account's own balances.
      name: account
  title: Silhouette Streaming API v1
  version: 1.0.0
operations:
  receiveClientFrame:
    action: receive
    channel:
      $ref: "#/channels/ws"
    description: "Frames a client sends on the shared stream: the opening HMAC authentication, the subscription selector for every topic, and the keepalive."
    messages:
      - $ref: "#/channels/ws/messages/AuthFrame"
      - $ref: "#/channels/ws/messages/PingFrame"
      - $ref: "#/channels/ws/messages/PongFrame"
      - $ref: "#/channels/ws/messages/SubscribeFrame"
      - $ref: "#/channels/ws/messages/UnsubscribeFrame"
    tags:
      - description: Session authentication, the topics a session selects, and the account's own balances.
        name: account
      - description: Requesting quotes, watching an RFQ, and reading the quotes competing on it.
        name: rfq
      - description: "Prices: a maker's ladder in on the ingest stream, top of book out on the shared one."
        name: market-data
  receiveMakerPriceLadder:
    action: receive
    channel:
      $ref: "#/channels/makerPriceIngest"
    description: A maker's indicative price ladder for one instrument, sent bare on the maker-only price-ingest stream with no envelope.
    messages:
      - $ref: "#/channels/makerPriceIngest/messages/LevelsFrame"
    tags:
      - description: "Prices: a maker's ladder in on the ingest stream, top of book out on the shared one."
        name: market-data
  sendMakerPriceIngestError:
    action: send
    channel:
      $ref: "#/channels/makerPriceIngest"
    description: Silhouette's refusal of a price ladder, on the maker-only price-ingest stream.
    messages:
      - $ref: "#/channels/makerPriceIngest/messages/ErrorFrame"
    tags:
      - description: "Prices: a maker's ladder in on the ingest stream, top of book out on the shared one."
        name: market-data
  sendServerFrame:
    action: send
    channel:
      $ref: "#/channels/ws"
    description: "Frames Silhouette pushes on the shared stream: an RFQ on each transition and the quotes competing on it, a maker's own quote outcomes and the open RFQs it may quote, top of book, whole-account balances, the maker-surface health signal, subscription acknowledgements, and refusals."
    messages:
      - $ref: "#/channels/ws/messages/BalanceSnapshot"
      - $ref: "#/channels/ws/messages/ErrorFrame"
      - $ref: "#/channels/ws/messages/Health"
      - $ref: "#/channels/ws/messages/MakerQuote"
      - $ref: "#/channels/ws/messages/OpenRfq"
      - $ref: "#/channels/ws/messages/PingFrame"
      - $ref: "#/channels/ws/messages/PongFrame"
      - $ref: "#/channels/ws/messages/PriceFrame"
      - $ref: "#/channels/ws/messages/Quote"
      - $ref: "#/channels/ws/messages/Rfq"
      - $ref: "#/channels/ws/messages/SubscribedFrame"
      - $ref: "#/channels/ws/messages/UnsubscribedFrame"
    tags:
      - description: Requesting quotes, watching an RFQ, and reading the quotes competing on it.
        name: rfq
      - description: "Prices: a maker's ladder in on the ingest stream, top of book out on the shared one."
        name: market-data
      - description: Session authentication, the topics a session selects, and the account's own balances.
        name: account
servers:
  production:
    description: Production WebSocket endpoint.
    host: rfq-api.silhouette.exchange
    protocol: wss
