# GENERATED FILE — do not edit.
# Composed by scripts/docs/compose-specs.mjs from the sources in scripts/docs/compose.json.
# Regenerate with `pnpm docs:generate`; `pnpm docs:check` fails on drift.

openapi: 3.1.0
info:
  title: Joyride Core HTTP API
  version: 0.1.0
  description: |
    HTTP endpoints served by joyride-core v8 components. Decimal money,
    price, and quantity fields are strings with up to 8 decimal places unless
    noted otherwise.
servers:
  - url: https://joyride.exchange/api
    description: Production
security:
  - bearerAuth: []
tags:
  - name: Public
  - name: Query
  - name: Withdrawals
  - name: Market Data
    description: Publicly accessible market data. No authentication required.
  - name: Account
    description: Account-scoped history and provisioning. Requires a bearer JWT.
paths:
  /health:
    get:
      tags:
        - Public
      operationId: getPublicHealth
      summary: Health check
      security: []
      responses:
        "200":
          description: Service is healthy
          content:
            application/json:
              schema:
                $ref: "#/components/schemas/PublicHealthResponse"
  /v1/market/config:
    get:
      tags:
        - Public
      operationId: getPublicMarketConfig
      summary: Get public market configuration
      security: []
      responses:
        "200":
          description: Public market configuration
          content:
            application/json:
              schema:
                type: object
                required:
                  - version
                  - price_decimals
                  - quantity_decimals
                properties:
                  version:
                    type: string
                  price_decimals:
                    type: integer
                  quantity_decimals:
                    type: integer
  /v1/market/instruments:
    get:
      tags:
        - Public
      operationId: listPublicInstruments
      summary: List public instrument metadata
      description: Returns only instruments whose lifecycle state is `open`.
      security: []
      responses:
        "200":
          description: Public instrument metadata
          content:
            application/json:
              schema:
                $ref: "#/components/schemas/PublicInstrumentsResponse"
  /v1/market/quotes:
    get:
      tags:
        - Public
      operationId: listPublicInstrumentQuotes
      summary: List available implied volatilities for open instruments
      description: Returns the latest validated BlockScholes mark IVs that are less than two minutes old. Missing or older quotes are omitted.
      security: []
      responses:
        "200":
          description: Available instrument implied volatilities
          content:
            application/json:
              schema:
                $ref: "#/components/schemas/PublicQuotesResponse"
  /v1/market/quotes/{symbol}:
    get:
      tags:
        - Public
      operationId: getPublicInstrumentQuote
      summary: Get the current implied volatility for an instrument
      description: Returns the latest validated BlockScholes mark IV for an open instrument. Quotes at least two minutes old are unavailable. This display-data window does not change the five-second trading risk gate.
      security: []
      parameters:
        - name: symbol
          in: path
          required: true
          schema:
            type: string
          example: BTC_USDC-2MAY26-77000-C
      responses:
        "200":
          description: Current instrument implied volatility
          content:
            application/json:
              schema:
                $ref: "#/components/schemas/PublicQuoteResponse"
        "404":
          description: Symbol is unknown or not open
          content:
            application/json:
              schema:
                $ref: "#/components/schemas/ErrorResponse"
        "503":
          description: IV has not arrived or is stale
          content:
            application/json:
              schema:
                $ref: "#/components/schemas/ErrorResponse"
  /query/vault-subaccount:
    get:
      tags:
        - Query
      operationId: listVaultSubaccount
      summary: The caller's RFQ collateral, as the vault-watcher last projected it
      description: "Zero or one row. A wallet that has never funded the RFQ side has no subaccount, and an empty `rows` array means exactly that: a real zero, not an error and not an outage. `free_balance` and `locked_total` are NULLABLE: a subaccount row can exist before its first projected balance, and a null there means UNKNOWN, never zero. Cursor pagination is not supported on this endpoint (`after` returns 400). The app reads the same account straight off the chain; this projection exists so a server-side consumer can see where a user's money is without an RPC round trip, and it lags the chain by the vault-watcher's projection delay."
      parameters:
        - $ref: "#/components/parameters/AccountId"
        - $ref: "#/components/parameters/Limit"
      responses:
        "200":
          description: Zero or one vault-subaccount row
          content:
            application/json:
              schema:
                $ref: "#/components/schemas/VaultSubaccountRows"
        "400":
          $ref: "#/components/responses/QueryError"
        "401":
          $ref: "#/components/responses/QueryUnauthorized"
  /query/rfq-positions:
    get:
      tags:
        - Query
      operationId: listRfqPositions
      summary: Net RFQ contracts per instrument for the authenticated account
      description: One row per (account, instrument) with a non-zero net position folded from RFQ strategy fills; long is positive. Legs of a package fold into their own instruments, so a filled straddle is two rows. Rows carry the instrument's `expiry` (unix seconds) and `settled`; `status` selects `open` (default, the instrument has not settled), `settled`, or `all`. A reverted fill unwinds its rows. Cursor pagination is not supported (`after` returns 400). This is the RFQ analogue of `/api/query/positions`, which lists central-limit-order-book positions only.
      parameters:
        - $ref: "#/components/parameters/AccountId"
        - $ref: "#/components/parameters/Limit"
        - name: status
          in: query
          schema:
            type: string
            enum:
              - open
              - settled
              - all
            default: open
      responses:
        "200":
          description: RFQ position rows
          content:
            application/json:
              schema:
                $ref: "#/components/schemas/CursorRows"
        "400":
          $ref: "#/components/responses/QueryError"
        "401":
          $ref: "#/components/responses/QueryUnauthorized"
  /query/rfq-fills:
    get:
      tags:
        - Query
      operationId: listRfqFills
      summary: List RFQ fill history for the authenticated maker or taker
      description: Strategy fills retain nullable one-leg compatibility columns and carry the authoritative package in `legs`. Every leg includes its instrument registry id, canonical symbol, and signed vault-decimal quantity. `side` is the taker's side for the whole package. Use `/api/query/rfq-positions` for current net exposure rather than replaying this append-only history.
      parameters:
        - $ref: "#/components/parameters/AccountId"
        - $ref: "#/components/parameters/Limit"
        - $ref: "#/components/parameters/AfterCursor"
        - name: rfq_id
          in: query
          schema:
            $ref: "#/components/schemas/Hex16"
        - name: status
          in: query
          schema:
            type: string
            enum:
              - confirmed
              - finalized
              - reverted
        - name: role
          in: query
          description: Narrows the participant scope to one role. Default is both. `side` on a fill is the taker's, so a consumer describing the caller's own trades wants `taker`; a maker reconciling its quotes wants `maker`.
          schema:
            type: string
            enum:
              - taker
              - maker
      responses:
        "200":
          description: RFQ fill rows with an optional continuation cursor
          content:
            application/json:
              schema:
                $ref: "#/components/schemas/RfqFillRows"
        "400":
          $ref: "#/components/responses/QueryError"
        "401":
          $ref: "#/components/responses/QueryUnauthorized"
  /v1/deposits:
    get:
      tags:
        - Account
      summary: List deposit history
      operationId: listVaultDeposits
      security:
        - aiAuth: []
      description: Returns the authenticated account's newest deposit audit entries.
      parameters:
        - name: limit
          in: query
          required: false
          description: Requested number of entries. Defaults to 20 and is capped at 100.
          schema:
            type: integer
            format: int32
            default: 20
        - name: after
          in: query
          required: false
          description: Exclusive sequence cursor returned as next_cursor. The decimal value must fit a non-negative signed 64-bit integer; overflow returns 400.
          schema:
            type: string
            pattern: ^[0-9]+$
      responses:
        "200":
          description: Deposit history
          content:
            application/json:
              schema:
                $ref: "#/components/schemas/PlatformDepositHistoryResponse"
        "400":
          description: Malformed keyset cursor
          content:
            application/json:
              schema:
                $ref: "#/components/schemas/PlatformError"
        "401":
          $ref: "#/components/responses/PlatformUnauthorized"
        "404":
          $ref: "#/components/responses/PlatformNotFound"
        "502":
          description: Deposit history is temporarily unavailable
          content:
            application/json:
              schema:
                $ref: "#/components/schemas/PlatformError"
  /v1/rfq/subaccount:
    post:
      tags:
        - Account
      summary: Ensure the caller's RFQ subaccount exists (Joyride pays the rent once)
      operationId: ensureRfqSubaccount
      security:
        - aiAuth: []
      description: |
        Asks core to make sure the authenticated wallet's margin-vault
        `Subaccount` PDA exists, with Joyride paying its rent at most once per
        wallet. The owner is always the bearer's wallet — the request has no
        body fields and cannot provision another wallet. Idempotent and safe
        to retry: `exists` costs nothing, `submitted` reports the in-flight
        `init_subaccount` signature (concurrent callers share one send), and
        `refused` is an answer, not an error. Paced at one call per minute
        per wallet. On-chain presence is visible within seconds; the RFQ
        desk's `RFQ_NOT_READY` gate clears once the vault watcher attributes
        the new subaccount, up to about a minute later.
      responses:
        "200":
          description: Provisioning outcome
          content:
            application/json:
              schema:
                $ref: "#/components/schemas/PlatformRfqSubaccountResponse"
        "401":
          $ref: "#/components/responses/PlatformUnauthorized"
        "404":
          $ref: "#/components/responses/PlatformNotFound"
        "429":
          description: More than one call per minute for this wallet; `retry-after` carries the wait in seconds
          content:
            application/json:
              schema:
                $ref: "#/components/schemas/PlatformError"
        "502":
          description: Core or the provisioner is temporarily unavailable; retry shortly
          content:
            application/json:
              schema:
                $ref: "#/components/schemas/PlatformError"
        "503":
          description: Provisioning is not configured on this deployment
          content:
            application/json:
              schema:
                $ref: "#/components/schemas/PlatformError"
  /v1/withdrawals:
    get:
      tags:
        - Withdrawals
      summary: List withdrawal history
      operationId: listVaultWithdrawals
      security:
        - aiAuth: []
      description: Returns the authenticated account's newest withdrawal audit entries.
      parameters:
        - name: limit
          in: query
          required: false
          description: Requested number of entries. Defaults to 20 and is capped at 100.
          schema:
            type: integer
            format: int32
            default: 20
        - name: after
          in: query
          required: false
          description: Exclusive sequence cursor returned as next_cursor. The decimal value must fit a non-negative signed 64-bit integer; overflow returns 400.
          schema:
            type: string
            pattern: ^[0-9]+$
      responses:
        "200":
          description: Withdrawal history
          content:
            application/json:
              schema:
                $ref: "#/components/schemas/PlatformWithdrawalHistoryResponse"
        "400":
          description: Malformed keyset cursor
          content:
            application/json:
              schema:
                $ref: "#/components/schemas/PlatformError"
        "401":
          description: Missing or invalid bearer token
          content:
            application/json:
              schema:
                $ref: "#/components/schemas/PlatformError"
        "404":
          description: Authenticated user has not been provisioned with a Joyride account
          content:
            application/json:
              schema:
                $ref: "#/components/schemas/PlatformError"
        "502":
          description: Withdrawal history is temporarily unavailable
          content:
            application/json:
              schema:
                $ref: "#/components/schemas/PlatformError"
  /v1/greeks:
    get:
      tags:
        - Market Data
      summary: Get greeks, model price, and probability of profit
      description: |
        Per-instrument option greeks computed by Joyride:
        Black-76 (r = 0, forward = spot) model price, delta, gamma, theta,
        vega, probability-of-profit, and the BlockScholes-derived `mark_iv`
        passthrough. Served entirely from an in-process cache refreshed by a
        background poller — no upstream call is made per request.

        Conventions:
        - All numeric fields are fixed-8 decimal **strings**.
        - `null` means unknown/stale — `"0.00000000"` is never emitted for an
          unknown value. Rows whose source inputs (quote `as_of_ms` / oracle
          spot publish time) are older than the staleness window (default 30s)
          serve null fields while retaining their last timestamps.
        - `model_price` is a display-only theoretical value. It is NOT the
          margin/liquidation-authoritative mark and must never be used as a
          PnL/margin/liquidation price source.
        - `rho` is intentionally omitted (convention-dependent, unrendered).
        - Rows are sorted by `symbol`. Staleness never yields a 5xx — the
          endpoint returns 200 with null fields.
        - Adding response fields is backward-compatible; removing or renaming
          fields is a breaking change.
      operationId: getBffGreeks
      security: []
      parameters:
        - name: asset
          in: query
          required: false
          description: |
            Optional base-asset filter. Validated against the canonical
            configured asset set (`BTC`, `ETH`, `SOL`), case-insensitively;
            values longer than 64 bytes or outside the set yield 400
            `INVALID_ASSET`. A valid asset with no cached rows returns an
            empty `data` array.
          schema:
            type: string
            maxLength: 64
            enum:
              - BTC
              - ETH
              - SOL
      responses:
        "200":
          description: Cached greeks rows (empty array when the cache is empty)
          content:
            application/json:
              schema:
                type: object
                properties:
                  data:
                    type: array
                    items:
                      type: object
                      properties:
                        symbol:
                          type: string
                          description: Instrument symbol (quote-pair format)
                          examples:
                            - BTC_USDC-25JUL26-120000-C
                        model_price:
                          type:
                            - string
                            - "null"
                          description: |
                            BS(spot, K, T, r=0, mark_iv) theoretical price,
                            fixed-8 decimal string. Display-only — never a
                            margin/liquidation price source.
                        mark_iv:
                          type:
                            - string
                            - "null"
                          description: BlockScholes-derived mark IV passthrough (fixed-8 string)
                        delta:
                          type:
                            - string
                            - "null"
                          description: In [-1, 1], sign-consistent with option type (fixed-8 string)
                        gamma:
                          type:
                            - string
                            - "null"
                          description: Non-negative (fixed-8 string)
                        theta:
                          type:
                            - string
                            - "null"
                          description: Per-day, non-positive at r = 0 (fixed-8 string)
                        vega:
                          type:
                            - string
                            - "null"
                          description: Per 1% IV change, non-negative (fixed-8 string)
                        pop:
                          type:
                            - string
                            - "null"
                          description: Long-side probability of profit at expiry, in [0, 1] (fixed-8 string)
                        as_of:
                          type:
                            - integer
                            - "null"
                          description: Unix ms of the OLDEST source input (never request time)
                        computed_at:
                          type:
                            - integer
                            - "null"
                          description: Unix ms the row was last computed; null only when never computed
                      required:
                        - symbol
                        - model_price
                        - mark_iv
                        - delta
                        - gamma
                        - theta
                        - vega
                        - pop
                        - as_of
                        - computed_at
                required:
                  - data
              example:
                data:
                  - symbol: BTC_USDC-25JUL26-120000-C
                    model_price: "1588.21000000"
                    mark_iv: "0.43210000"
                    delta: "0.45120000"
                    gamma: "0.00001200"
                    theta: "-42.10000000"
                    vega: "88.40000000"
                    pop: "0.38000000"
                    as_of: 1753100000123
                    computed_at: 1753100000500
        "400":
          description: Invalid `asset` filter (the input is never reflected back)
          content:
            application/json:
              schema:
                type: object
                properties:
                  error:
                    type: string
                    const: INVALID_ASSET
                  message:
                    type: string
                    const: asset must be one of the listed base assets
                required:
                  - error
                  - message
components:
  securitySchemes:
    bearerAuth:
      type: http
      scheme: bearer
      description: SIWS-issued JWT. Admin commands require the `admin` role, `admin:*`, or the exact `admin:<type>` scope; admin reads use their documented read scope.
    aiAuth:
      type: http
      scheme: bearer
      description: |
        Joyride Core session JWT obtained from the SIWS flow
        (`public/get_nonce` + `public/auth` on the Core trading WebSocket).
        Include as: `Authorization: Bearer <jwt>`
  parameters:
    AccountId:
      name: account_id
      in: query
      schema:
        type: integer
        minimum: 0
    Limit:
      name: limit
      in: query
      description: Rows per page. Omitted or above 500, the server uses 500; a page shorter than `limit` does not mean the end, so page until `next_cursor` is absent.
      schema:
        type: integer
        minimum: 1
        maximum: 500
        default: 500
    AfterCursor:
      name: after
      in: query
      description: Exclusive keyset cursor returned as next_cursor. Supported by accounts and monotone sequence-number lists; unsupported sort orders reject it with 400 rather than silently ignoring it. The decimal value must fit the backing non-negative PostgreSQL BIGINT; overflow returns 400.
      schema:
        type: string
        pattern: ^[0-9]+$
  responses:
    QueryError:
      description: Query API error response
      content:
        application/json:
          schema:
            $ref: "#/components/schemas/QueryErrorResponse"
    QueryUnauthorized:
      description: Missing, expired or invalid bearer token. The body is the plain text `unauthorized`, not JSON.
      content:
        text/plain:
          schema:
            type: string
            const: unauthorized
    PlatformNotFound:
      description: Resource not found
      content:
        application/json:
          schema:
            $ref: "#/components/schemas/PlatformError"
    PlatformUnauthorized:
      description: |
        Authentication required or the bearer token is invalid.
        For JWT-protected endpoints, `error` is prefixed with an auth code:
        `auth_missing`, `auth_missing_or_wallet_query`, `auth_malformed`, `auth_expired`, or `auth_signature_invalid`.
      content:
        application/json:
          schema:
            $ref: "#/components/schemas/PlatformError"
  schemas:
    DecimalString:
      type: string
      pattern: ^-?[0-9]+(\.[0-9]{1,8})?$
      examples:
        - "100.00000000"
    Hex32:
      type: string
      pattern: ^[0-9a-fA-F]{64}$
    Hex16:
      type: string
      pattern: ^[0-9a-fA-F]{32}$
    VaultDecimalString:
      type: string
      description: Unsigned margin-vault USDC amount with at most 6 decimal places.
      pattern: ^[0-9]+(\.[0-9]{1,6})?$
    VaultSignedDecimalString:
      type: string
      description: Signed margin-vault fixed-point value with at most 6 decimal places; negative zero is rejected.
      pattern: ^((0|[1-9][0-9]*)(\.[0-9]{1,6})?|-[1-9][0-9]*(\.[0-9]{1,6})?|-0\.(?=[0-9]{0,5}[1-9]$)[0-9]{1,6})$
    ErrorResponse:
      type: object
      required:
        - ok
        - error
      properties:
        ok:
          const: false
        error:
          type: string
    QueryErrorResponse:
      type: object
      required:
        - error
      properties:
        error:
          type: string
    CursorRows:
      type: object
      required:
        - rows
      properties:
        rows:
          type: array
          items:
            type: object
            additionalProperties: true
        next_cursor:
          type: string
          pattern: ^[0-9]+$
    PublicHealthResponse:
      type: object
      required:
        - status
      properties:
        status:
          type: string
          example: ok
    PublicInstrumentsResponse:
      type: object
      required:
        - instruments
      properties:
        instruments:
          type: array
          items:
            $ref: "#/components/schemas/PublicInstrument"
    PublicQuoteResponse:
      type: object
      required:
        - symbol
        - mark_iv
        - as_of_ms
      properties:
        symbol:
          type: string
          example: BTC_USDC-2MAY26-77000-C
        mark_iv:
          allOf:
            - $ref: "#/components/schemas/DecimalString"
          description: Current mark implied volatility as a ratio, not a percentage.
          example: "0.81234567"
        as_of_ms:
          type: integer
          format: int64
          description: Gateway receive time in Unix milliseconds.
    PublicQuotesResponse:
      type: object
      required:
        - quotes
      properties:
        quotes:
          type: array
          items:
            $ref: "#/components/schemas/PublicQuoteResponse"
    PublicInstrument:
      type: object
      description: Public instrument metadata as returned by `/instruments`, `/v1/market/instruments`, and `public/get_instruments`. Clients interface with instruments by `symbol` only; the internal numeric instrument id is never exposed. Public discovery only returns currently open instruments.
      required:
        - symbol
        - underlying
        - strike
        - option_type
        - expiry
        - state
      properties:
        symbol:
          type: string
          description: Human-facing instrument symbol — the primary identifier clients use to submit orders (`instrument_name`) and subscribe to market data.
          example: BTC_USDC-2MAY26-77000-C
        underlying:
          type: string
          description: Underlying ticker, resolved from the internal id.
          example: BTC
        strike:
          $ref: "#/components/schemas/DecimalString"
        option_type:
          type: string
          enum:
            - call
            - put
            - unknown
        expiry:
          type: integer
        state:
          type: string
          enum:
            - open
    VaultSubaccountRow:
      type: object
      required:
        - account_id
      properties:
        account_id:
          type: integer
        subaccount:
          type:
            - string
            - "null"
          description: "Hex-encoded subaccount address. Null on every row today: no projector write path populates the column."
        free_balance:
          oneOf:
            - $ref: "#/components/schemas/VaultDecimalString"
            - type: "null"
          description: "Collateral free to back a new fill, vault-decimal (6 dp), as finalized on chain. Null means not yet projected, which is UNKNOWN, not zero: a subaccount row can exist before its first balance effect. Open quote fills are not debited here until settlement, so this can read higher than the risk state does."
        locked_total:
          oneOf:
            - $ref: "#/components/schemas/VaultDecimalString"
            - type: "null"
          description: Collateral locked under open positions, vault-decimal (6 dp).
        max_locked_balance:
          oneOf:
            - $ref: "#/components/schemas/VaultDecimalString"
            - type: "null"
          description: The collateral ceiling stored on the subaccount by the last fill the account wrote, vault-decimal (6 dp); the largest total lock a later fill or standalone lock may reach. Null before the first such fill.
        open_position_counter:
          type:
            - string
            - "null"
        finalized_slot:
          type:
            - integer
            - "null"
    RfqFillLeg:
      type: object
      required:
        - instrument_registry_id
        - symbol
        - quantity
      properties:
        instrument_registry_id:
          type: integer
        symbol:
          type: string
          description: Canonical instrument symbol resolved by the query read model.
        quantity:
          allOf:
            - $ref: "#/components/schemas/VaultSignedDecimalString"
          description: Signed contracts from the taker-buy perspective.
    RfqFillRow:
      type: object
      required:
        - rfq_id
        - source_event_id
        - taker_account
        - maker_account
        - fill_type
        - price_basis
        - instrument_registry_id
        - symbol
        - size
        - legs
        - side
        - premium
        - taker_fee
        - maker_fee
        - lock_amount
        - lock_a
        - lock_b
        - seq_a
        - seq_b
        - attestation_nonce
        - quote_hash
        - writer_bitmap
        - short_count_a
        - short_count_b
        - slot
        - tx_signature
        - finality
        - filled_at_unix_ms
        - filled_seq_no
      properties:
        rfq_id:
          $ref: "#/components/schemas/Hex16"
        source_event_id:
          $ref: "#/components/schemas/Hex32"
        taker_account:
          type: integer
        maker_account:
          type:
            - integer
            - "null"
        fill_type:
          const: strategy
        price_basis:
          const: package_total
        instrument_registry_id:
          type:
            - integer
            - "null"
          description: One-leg compatibility field; null for multi-leg packages.
        symbol:
          type:
            - string
            - "null"
          description: One-leg compatibility field; null for multi-leg packages.
        size:
          type:
            - string
            - "null"
          pattern: ^[0-9]+(\.[0-9]{1,6})?$
          description: One-leg compatibility field; null for multi-leg packages.
        legs:
          type: array
          description: Authoritative package legs in submitted order.
          items:
            $ref: "#/components/schemas/RfqFillLeg"
        side:
          type: integer
          enum:
            - 0
            - 1
          description: "Taker side: 0 buy, 1 sell."
        premium:
          $ref: "#/components/schemas/VaultSignedDecimalString"
        taker_fee:
          $ref: "#/components/schemas/VaultDecimalString"
        maker_fee:
          $ref: "#/components/schemas/VaultDecimalString"
        lock_amount:
          type:
            - string
            - "null"
        lock_a:
          type:
            - string
            - "null"
        lock_b:
          type:
            - string
            - "null"
        seq_a:
          type:
            - integer
            - "null"
        seq_b:
          type:
            - integer
            - "null"
        attestation_nonce:
          type:
            - integer
            - "null"
        quote_hash:
          type:
            - string
            - "null"
          pattern: ^[0-9a-f]{64}$
        writer_bitmap:
          type:
            - integer
            - "null"
        short_count_a:
          type:
            - integer
            - "null"
        short_count_b:
          type:
            - integer
            - "null"
        slot:
          type:
            - integer
            - "null"
        tx_signature:
          type:
            - string
            - "null"
          description: A 64-byte signature encoded as 128 hex characters, or null before projection.
        finality:
          type: string
          enum:
            - confirmed
            - finalized
            - reverted
        filled_at_unix_ms:
          type:
            - integer
            - "null"
        filled_seq_no:
          type: integer
    VaultSubaccountRows:
      type: object
      required:
        - rows
      properties:
        rows:
          type: array
          description: Zero or one row for a user-scoped caller. An admin token without `account_id` is not narrowed, so it reads every subaccount up to the page limit.
          items:
            $ref: "#/components/schemas/VaultSubaccountRow"
    RfqFillRows:
      type: object
      required:
        - rows
      properties:
        rows:
          type: array
          items:
            $ref: "#/components/schemas/RfqFillRow"
        next_cursor:
          type: string
          pattern: ^[0-9]+$
    PlatformError:
      type: object
      properties:
        error:
          type: string
          description: Human-readable error message (auth failures may include an `auth_*` code prefix)
          example: Instrument not found
      required:
        - error
    PlatformWithdrawalHistoryEntry:
      type: object
      properties:
        externalId:
          type: string
          description: On-chain withdrawal reference. Omitted while no external id is projected.
          example: "812"
        amount:
          type: string
          description: USDC decimal amount at eight-decimal precision.
          example: "25.00000000"
        status:
          type: string
          enum:
            - completed
            - pending
            - needs_review
            - rejected
        createdAt:
          type: string
          format: date-time
          example: 2026-07-20T17:05:32.123456+00:00
      required:
        - amount
        - status
        - createdAt
    PlatformWithdrawalHistoryResponse:
      type: object
      properties:
        withdrawals:
          type: array
          items:
            $ref: "#/components/schemas/PlatformWithdrawalHistoryEntry"
        next_cursor:
          type: string
          pattern: ^[0-9]+$
      required:
        - withdrawals
    PlatformDepositHistoryEntry:
      type: object
      properties:
        externalId:
          type: string
        amount:
          type: string
          example: "25.00000000"
        status:
          type: string
          enum:
            - credited
            - pending
            - needs_review
            - rejected
        createdAt:
          type: string
          format: date-time
      required:
        - amount
        - status
        - createdAt
    PlatformRfqSubaccountResponse:
      type: object
      required:
        - status
      properties:
        status:
          type: string
          enum:
            - exists
            - submitted
            - refused
          description: "`exists`: the subaccount is on chain (nothing sent). `submitted`: an `init_subaccount` is in flight. `refused`: see `reason`."
        signature:
          type: string
          description: Base58 signature of the in-flight `init_subaccount`; present on `submitted` when known.
        reason:
          type: string
          enum:
            - provisioner_disabled
            - invalid_owner
            - already_provisioned
            - payer_unavailable
            - vault_paused
          description: Present on `refused`. `provisioner_disabled` and `payer_unavailable` mean "fund the subaccount from a wallet, or try again shortly"; `vault_paused` means RFQ is paused; `already_provisioned` is the per-wallet spend bound (unreachable while the program has no close path).
      example:
        status: submitted
        signature: 5VERv8NMvzbJMEkV8xnrLkEaWRtSz9CosKDYjCJjBRnbJLgp8uZ6YpUDkH5Gy8dn3sq8tuDdBhCPq8tAgeKxb7hW
    PlatformDepositHistoryResponse:
      type: object
      properties:
        deposits:
          type: array
          items:
            $ref: "#/components/schemas/PlatformDepositHistoryEntry"
        next_cursor:
          type: string
          pattern: ^[0-9]+$
      required:
        - deposits
