asyncapi: 2.6.0
info:
  title: Broker WebSocket Streams API
  version: 1.0.0
  description: |
    Real-time WebSocket streams for order updates and market data.

    Authentication:
      - Keyed BLAKE2b-256 signature via HTTP headers during WebSocket Upgrade:
        * PRE-ACCESS-KEY — broker access key identifier (unique API key)
        * PRE-ACCESS-TIMESTAMP — RFC3339 timestamp with milliseconds (e.g., 2025-10-30T06:00:34.075Z)
        * PRE-ACCESS-SIGNATURE — Keyed BLAKE2b-256 hex lowercase (64 chars)
      - Signature computed over (each line ends with \n):
        1. "GET\n"
        2. path + query string + "\n"
        3. timestamp + "\n"
        4. "" (empty idempotency_key for WebSocket) + "\n"
        5. broker_id + "\n"
        6. access_key + "\n"
        7. body_digest (BLAKE2b-256 of empty body = hash of "")
      - Keyed BLAKE2b-256 is computed using secret_key associated with the access_key
      - The function returns 32 bytes, encoded as hex lowercase (64 chars) for PRE-ACCESS-SIGNATURE.
      - Timestamp must be within clock_skew_tolerance (default 5m)
      - Invalid signature or timestamp → HTTP 401 or WS close 4401

    General Principles:
      - All data frames include monotonic sequence numbers for ordering/gap detection
      - Heartbeat frames do not advance sequence numbers
      - Resume capability via 'since' parameter where applicable ('since' must be >0; value 0 is treated as absent)
      - Idempotent frame delivery (clients drop frames with seq <= last_applied)
servers:
  production:
    url: wss://api.example.com/v1/stream
    protocol: wss
    description: Production WebSocket endpoint
  sandbox:
    url: wss://sandbox.api.example.com/v1/stream
    protocol: wss
    description: Sandbox WebSocket endpoint
channels:
  /orders:
    subscribe:
      summary: Receive real-time order updates
      description: |
        Real-time order updates stream with resume capability.

        Query Parameters:
          - since=&lt;uint64&gt;   Last applied seq for resume (optional; must be >0; absent or 0 → start from live tail)
          - market=&lt;id&gt;      Repeatable; filter by market_id
          - account_id=&lt;id&gt;  Repeatable; filter by account_id

        Resume Semantics:
          1. Client connects with since=S (last applied seq)
          2. Server replays frames with seq >= S in order
          3. Sends backlog_end (last_seq = current max)
          4. Continues live streaming
          If S > current max → immediate live (no backlog)
          If S too old (retention gap) → error frame (code=resume_too_old) then close (4009)

        Idempotency / Ordering:
          - seq strictly monotonic per broker; no gaps (gap ⇒ operational incident)
          - Client drops any frame with seq <= last_applied_seq
          - Expected next seq = last_applied_seq + 1 (else trigger reconnect + resume)

        Heartbeats:
          - Sent when no order_update within configured interval
          - Heartbeat does NOT advance seq; only order_update frames advance seq
          - Heartbeat and backlog_end both repeat the last data seq

        Flow Control:
          - Server may enforce max_pending_frames/bytes and close with code 4008 (flow_control)

        Close Codes:
          1000 normal
          1001 going_away
          1011 internal_error
          4008 flow_control
          4009 resume_too_old / protocol_version
          4401 unauthorized
      message:
        oneOf:
          - $ref: '#/components/messages/StreamFrameOrderUpdate'
          - $ref: '#/components/messages/StreamFrameOrderReject'
          - $ref: '#/components/messages/StreamFrameHeartbeat'
          - $ref: '#/components/messages/StreamFrameBacklogEnd'
          - $ref: '#/components/messages/StreamFrameError'
      bindings:
        ws:
          headers:
            type: object
            required:
              - PRE-ACCESS-KEY
              - PRE-ACCESS-TIMESTAMP
              - PRE-ACCESS-SIGNATURE
            properties:
              PRE-ACCESS-KEY:
                type: string
                pattern: ^[A-Za-z0-9._~-]{1,64}$
                description: Broker access key identifier
              PRE-ACCESS-TIMESTAMP:
                type: string
                format: date-time
                description: RFC3339 timestamp with milliseconds
              PRE-ACCESS-SIGNATURE:
                type: string
                pattern: ^[0-9a-f]{64}$
                description: Keyed BLAKE2b-256 signature hex lowercase
          query:
            type: object
            properties:
              since:
                type: integer
                format: int64
                minimum: 1
                description: Resume from last applied seq (inclusive). Must be >0. If omitted or 0, start from live tail.
              market:
                type: array
                items:
                  type: string
                description: Repeatable filter by market_id. Absent → all markets accessible to broker.
              event_id:
                type: array
                items:
                  type: string
                description: Repeatable filter by event_id. Absent → all events accessible to broker.
              account_id:
                type: array
                items:
                  type: string
                description: Repeatable filter by account_id. Absent → all accounts of broker.
  /accounting:
    subscribe:
      summary: Receive real-time accounting and ledger updates
      description: |
        Real-time accounting updates including fills, cancellations, merges, and payouts.
        This stream provides atomic updates for both cash and position balances.

        Query Parameters:
          - since=&lt;uint64&gt;   Last applied seq for resume (optional; must be >0; absent or 0 → start from live tail)
          - market=&lt;id&gt;      Repeatable; filter by market_id
          - account_id=&lt;id&gt;  Repeatable; filter by account_id

        Resume semantics are identical to the /orders stream.
      message:
        oneOf:
          - $ref: '#/components/messages/StreamFrameAccountingUpdate'
          - $ref: '#/components/messages/StreamFrameAccountingPayout'
          - $ref: '#/components/messages/StreamFrameHeartbeat'
          - $ref: '#/components/messages/StreamFrameBacklogEnd'
          - $ref: '#/components/messages/StreamFrameError'
      bindings:
        ws:
          headers:
            type: object
            required:
              - PRE-ACCESS-KEY
              - PRE-ACCESS-TIMESTAMP
              - PRE-ACCESS-SIGNATURE
            properties:
              PRE-ACCESS-KEY:
                type: string
                pattern: ^[A-Za-z0-9._~-]{1,64}$
              PRE-ACCESS-TIMESTAMP:
                type: string
                format: date-time
              PRE-ACCESS-SIGNATURE:
                type: string
                pattern: ^[0-9a-f]{64}$
          query:
            type: object
            properties:
              since:
                type: integer
                format: int64
                minimum: 1
                description: Resume from last applied seq (inclusive). Must be >0. If omitted or 0, start from live tail.
              market:
                type: array
                items:
                  type: string
              account_id:
                type: array
                items:
                  type: string
  /payouts:
    deprecated: true
    subscribe:
      summary: Receive real-time payout/settlement updates (DEPRECATED)
      description: |
        Real-time settlement payouts stream with resume capability.
        NOTE: This stream is deprecated. Please migrate to /accounting.

        Query Parameters:
          - since=&lt;uint64&gt;   Last applied seq for resume (optional; must be >0; absent or 0 → start from live tail)
          - market=&lt;id&gt;      Repeatable; filter by market_id
          - account_id=&lt;id&gt;  Repeatable; filter by account_id

        Resume Semantics:
          1. Client connects with since=S (last applied seq)
          2. Server replays frames with seq >= S in order
          3. Sends backlog_end (last_seq = current max)
          4. Continues live streaming
          If S > current max → immediate live (no backlog)
          If S too old (retention gap) → error frame (code=resume_too_old) then close (4009)

        Idempotency / Ordering:
          - seq strictly monotonic per broker (shared or separate sequence depending on implementation details, typically separate per stream type); no gaps
          - Client drops any frame with seq <= last_applied_seq
          - Expected next seq = last_applied_seq + 1

        Heartbeats:
          - Sent when no payout within configured interval
          - Heartbeat does NOT advance seq
          - Heartbeat and backlog_end both repeat the last data seq

        Close Codes:
          Same as /orders
      message:
        oneOf:
          - $ref: '#/components/messages/StreamFramePayout'
          - $ref: '#/components/messages/StreamFrameHeartbeat'
          - $ref: '#/components/messages/StreamFrameBacklogEnd'
          - $ref: '#/components/messages/StreamFrameError'
      bindings:
        ws:
          headers:
            type: object
            required:
              - PRE-ACCESS-KEY
              - PRE-ACCESS-TIMESTAMP
              - PRE-ACCESS-SIGNATURE
            properties:
              PRE-ACCESS-KEY:
                type: string
                pattern: ^[A-Za-z0-9._~-]{1,64}$
                description: Broker access key identifier
              PRE-ACCESS-TIMESTAMP:
                type: string
                format: date-time
                description: RFC3339 timestamp with milliseconds
              PRE-ACCESS-SIGNATURE:
                type: string
                pattern: ^[0-9a-f]{64}$
                description: Keyed BLAKE2b-256 signature hex lowercase
          query:
            type: object
            properties:
              since:
                type: integer
                format: int64
                minimum: 1
                description: Resume from last applied seq (inclusive). Must be >0. If omitted or 0, start from live tail.
              market:
                type: array
                items:
                  type: string
                description: Repeatable filter by market_id. Absent → all markets accessible to broker.
              event_id:
                type: array
                items:
                  type: string
                description: Repeatable filter by event_id. Absent → all events accessible to broker.
              account_id:
                type: array
                items:
                  type: string
                description: Repeatable filter by account_id. Absent → all accounts of broker.
  /market-data:
    subscribe:
      summary: Receive real-time market data
      description: |
        Real-time market data stream with dynamic topic subscription (order book snapshots/diffs, trades).

        Subscription Model (no query parameters):
          - Connection starts with zero markets subscribed
          - Client sends a subscribe frame (MDClientSubscribe) listing market_id values
          - Server replies with exactly one MDBookSnapshot per NEW market, then streams MDBookDiff / MDTrade
          - Client may send unsubscribe (MDClientUnsubscribe) to stop updates for specific markets
          - Repeated subscription for already active market without force_snapshot is idempotent (no extra snapshot)

        Ordering / Monotonicity:
          - md_seq is dense and strictly increasing per market_id ONLY on data frames (book_snapshot, book_diff, trade)
          - heartbeat does not advance md_seq
          - Gaps in md_seq per market indicate operational incident; client SHOULD resync (fresh snapshot + re-subscribe)

        Initial Snapshots:
          - Snapshot sent only upon successful subscription (or re-subscription with force_snapshot=true)

        Snapshots Cadence:
          - Periodic server-initiated book_snapshot frames limit diff chain length
          - Clients MUST apply snapshot then contiguous diffs/trades with md_seq = snapshot.md_seq+1 … without gaps
      message:
        oneOf:
          - $ref: '#/components/messages/MDBookSnapshot'
          - $ref: '#/components/messages/MDBookDiff'
          - $ref: '#/components/messages/MDTrade'
          - $ref: '#/components/messages/MDHeartbeat'
          - $ref: '#/components/messages/MDError'
      bindings:
        ws:
          headers:
            type: object
            required:
              - PRE-ACCESS-KEY
              - PRE-ACCESS-TIMESTAMP
              - PRE-ACCESS-SIGNATURE
            properties:
              PRE-ACCESS-KEY:
                type: string
                pattern: ^[A-Za-z0-9._~-]{1,64}$
                description: Broker access key identifier
              PRE-ACCESS-TIMESTAMP:
                type: string
                format: date-time
                description: RFC3339 timestamp with milliseconds
              PRE-ACCESS-SIGNATURE:
                type: string
                pattern: ^[0-9a-f]{64}$
                description: Keyed BLAKE2b-256 signature hex lowercase
    publish:
      summary: Send subscription control messages
      message:
        oneOf:
          - $ref: '#/components/messages/MDClientSubscribe'
          - $ref: '#/components/messages/MDClientUnsubscribe'
  /events:
    subscribe:
      summary: Receive real-time event/market lifecycle updates
      description: |
        Real-time lifecycle events stream for events and markets with resume capability.

        Query Parameters:
          - since=&lt;uint64&gt;   Last applied seq for resume (optional; must be >0; absent or 0 → start from live tail)
          - event_id=&lt;id&gt;    Repeatable; filter by event_id (includes all markets of the event)
          - market_id=&lt;id&gt;   Repeatable; filter by market_id
          - lang=&lt;code&gt;      Repeatable; filter by language code (en, pt, id). Filters LocalizedText fields in payloads. If omitted, all available locales are included.

        Resume Semantics:
          1. Client connects with since=S (last applied seq)
          2. Server replays frames with seq >= S in order
          3. Sends backlog_end (last_seq = current max)
          4. Continues live streaming
          If S > current max → immediate live (no backlog)
          If S too old (retention gap) → error frame (code=resume_too_old) then close (4009)

        Idempotency / Ordering:
          - seq strictly monotonic; no gaps (gap ⇒ operational incident)
          - Client drops any frame with seq <= last_applied_seq
          - Expected next seq = last_applied_seq + 1 (else trigger reconnect + resume)

        Event Types:
          - event.created: New event created with full definition
          - event.status_changed: Event status changed (only status fields)
          - event.settled: Event resolved (includes expiration_value and resolved_market_id; either may be null)
          - event.voided: Event cancelled (includes void_reason)
          - market.created: New market created with full definition
          - market.status_changed: Market status changed (only status fields)
          - market.details_changed: Market details changed (title, subtitles, floor_strike)
          - market.settled: Market resolved (includes result, settlement_price, expiration_value)
          - market.voided: Market cancelled

        Market Details At Resolution:
          - market.details_changed can arrive on a closed market at resolution, with a new floor_strike
          - It always comes before market.settled and event.settled of the same event
          - expiration_value in those two events matches the new floor_strike, and can carry more decimals than usual

        Heartbeats:
          - Sent when no lifecycle_update within configured interval
          - Heartbeat does NOT advance seq; only lifecycle_update frames advance seq
          - Heartbeat and backlog_end both repeat the last data seq

        Flow Control:
          - Server may enforce max_pending_frames/bytes and close with code 4008 (flow_control)

        Close Codes:
          1000 normal
          1001 going_away
          1011 internal_error
          4008 flow_control
          4009 resume_too_old / protocol_version
          4401 unauthorized
      message:
        oneOf:
          - $ref: '#/components/messages/StreamFrameEventUpdate'
          - $ref: '#/components/messages/StreamFrameHeartbeat'
          - $ref: '#/components/messages/StreamFrameBacklogEnd'
          - $ref: '#/components/messages/StreamFrameError'
      bindings:
        ws:
          headers:
            type: object
            required:
              - PRE-ACCESS-KEY
              - PRE-ACCESS-TIMESTAMP
              - PRE-ACCESS-SIGNATURE
            properties:
              PRE-ACCESS-KEY:
                type: string
                pattern: ^[A-Za-z0-9._~-]{1,64}$
                description: Broker access key identifier
              PRE-ACCESS-TIMESTAMP:
                type: string
                format: date-time
                description: RFC3339 timestamp with milliseconds
              PRE-ACCESS-SIGNATURE:
                type: string
                pattern: ^[0-9a-f]{64}$
                description: Keyed BLAKE2b-256 signature hex lowercase
          query:
            type: object
            properties:
              since:
                type: integer
                format: int64
                minimum: 1
                description: Resume from last applied seq (inclusive). Must be >0. If omitted or 0, start from live tail.
              event_id:
                type: array
                items:
                  type: string
                description: Repeatable filter by event_id. Includes all markets of the event. Absent → all events accessible to broker.
              market_id:
                type: array
                items:
                  type: string
                description: Repeatable filter by market_id. Absent → all markets accessible to broker.
              lang:
                type: array
                items:
                  type: string
                  enum:
                    - en
                    - pt
                    - id
                description: Repeatable filter by language code. Filters localized text fields in payloads. If omitted, all available locales are included in each payload.
components:
  messages:
    StreamFrameOrderUpdate:
      name: StreamFrameOrderUpdate
      title: Order Update Frame
      summary: Order state change notification
      contentType: application/json
      payload:
        $ref: '#/components/schemas/StreamFrameOrderUpdate'
    StreamFrameOrderReject:
      name: StreamFrameOrderReject
      title: Order Reject Frame
      summary: Notification of a rejected order command (Amend/Cancel)
      contentType: application/json
      payload:
        $ref: '#/components/schemas/StreamFrameOrderReject'
    StreamFrameHeartbeat:
      name: StreamFrameHeartbeat
      title: Heartbeat Frame
      summary: Keep-alive heartbeat (does not advance seq)
      contentType: application/json
      payload:
        $ref: '#/components/schemas/StreamFrameHeartbeat'
    StreamFrameBacklogEnd:
      name: StreamFrameBacklogEnd
      title: Backlog End Marker
      summary: Signals end of historical replay; live streaming begins
      contentType: application/json
      payload:
        $ref: '#/components/schemas/StreamFrameBacklogEnd'
    StreamFrameError:
      name: StreamFrameError
      title: Error Frame
      summary: Error notification before close
      contentType: application/json
      payload:
        $ref: '#/components/schemas/StreamFrameError'
    StreamFramePayout:
      name: StreamFramePayout
      title: Payout Notification Frame
      summary: Notification of a settlement payout
      contentType: application/json
      payload:
        $ref: '#/components/schemas/StreamFramePayout'
    StreamFrameAccountingUpdate:
      name: StreamFrameAccountingUpdate
      title: Accounting Update Frame
      summary: Unified atomic update of cash and position balances for an account within one engine tick.
      contentType: application/json
      payload:
        $ref: '#/components/schemas/StreamFrameAccountingUpdate'
    StreamFrameAccountingPayout:
      name: StreamFrameAccountingPayout
      title: Accounting Payout Frame
      summary: Final cash and position adjustment from market settlement or void
      contentType: application/json
      payload:
        $ref: '#/components/schemas/StreamFrameAccountingPayout'
    MDBookSnapshot:
      name: MDBookSnapshot
      title: Order Book Snapshot
      summary: Full order book snapshot for a market
      contentType: application/json
      payload:
        $ref: '#/components/schemas/MDBookSnapshot'
    MDBookDiff:
      name: MDBookDiff
      title: Order Book Diff
      summary: Incremental order book update (delta)
      contentType: application/json
      payload:
        $ref: '#/components/schemas/MDBookDiff'
    MDTrade:
      name: MDTrade
      title: Trade Event
      summary: Trade execution notification
      contentType: application/json
      payload:
        $ref: '#/components/schemas/MDTrade'
    MDHeartbeat:
      name: MDHeartbeat
      title: Market Data Heartbeat
      summary: Keep-alive heartbeat (does not advance md_seq)
      contentType: application/json
      payload:
        $ref: '#/components/schemas/MDHeartbeat'
    MDError:
      name: MDError
      title: Market Data Error
      summary: Market data stream error notification
      contentType: application/json
      payload:
        $ref: '#/components/schemas/MDError'
    MDClientSubscribe:
      name: MDClientSubscribe
      title: Subscribe Request
      summary: Client request to subscribe to market(s)
      contentType: application/json
      payload:
        $ref: '#/components/schemas/MDClientSubscribe'
    MDClientUnsubscribe:
      name: MDClientUnsubscribe
      title: Unsubscribe Request
      summary: Client request to unsubscribe from market(s)
      contentType: application/json
      payload:
        $ref: '#/components/schemas/MDClientUnsubscribe'
    StreamFrameEventUpdate:
      name: StreamFrameEventUpdate
      title: Event/Market Lifecycle Update Frame
      summary: Event or market lifecycle state change notification
      contentType: application/json
      payload:
        $ref: '#/components/schemas/StreamFrameEventUpdate'
  schemas:
    OrderView:
      type: object
      description: |
        Current state of an order.
      required:
        - order_id
        - client_order_id
        - market_id
        - broker_id
        - account_id
        - side
        - type
        - tif
        - token
        - price
        - qty
        - volume
        - filled_qty
        - filled_volume
        - status
        - last_update_wall_time_ns
        - last_update_engine_seq
      properties:
        order_id:
          type: string
          format: uuid
        client_order_id:
          type: string
        market_id:
          type: string
        broker_id:
          type: string
        account_id:
          type: string
        side:
          $ref: '#/components/schemas/OrderSide'
        type:
          $ref: '#/components/schemas/OrderType'
        tif:
          $ref: '#/components/schemas/OrderTIF'
        token:
          $ref: '#/components/schemas/Token'
        price:
          $ref: '#/components/schemas/Decimal'
        qty:
          $ref: '#/components/schemas/Decimal'
        volume:
          $ref: '#/components/schemas/Decimal'
        filled_qty:
          $ref: '#/components/schemas/Decimal'
        filled_volume:
          $ref: '#/components/schemas/Decimal'
        filled_fee:
          description: Total filled fees accumulated on this order (in quote currency units).
          allOf:
            - $ref: '#/components/schemas/Decimal'
        status:
          $ref: '#/components/schemas/OrderStatus'
        status_reason:
          $ref: '#/components/schemas/OrderStatusReason'
        last_update_wall_time_ns:
          type: integer
          format: int64
        last_update_engine_seq:
          type: integer
          format: int64
          description: Monotonic engine sequence number of the last applied change
        last_cmd_id_hex:
          type: string
          description: 32 lowercase hex
    PayoutView:
      type: object
      required:
        - market_id
        - account_id
        - qty_held_at_settlement
        - settlement_price
        - payout_amount
        - settled_at_wall_time_ns
      properties:
        market_id:
          type: string
        account_id:
          type: string
        qty_held_at_settlement:
          description: Net position at settlement. Positive (+) values indicate Long (YES) contracts held. Negative (-) values indicate Short (NO) contracts held.
          allOf:
            - $ref: '#/components/schemas/Decimal'
        settlement_price:
          allOf:
            - $ref: '#/components/schemas/Decimal'
        payout_amount:
          allOf:
            - $ref: '#/components/schemas/Decimal'
        settled_at_wall_time_ns:
          type: integer
          format: int64
    StreamFrameOrderUpdate:
      type: object
      required:
        - type
        - seq
        - ts_ns
        - payload
      properties:
        type:
          type: string
          enum:
            - order_update
        seq:
          type: integer
          format: int64
          minimum: 0
        ts_ns:
          type: integer
          format: int64
        payload:
          $ref: '#/components/schemas/OrderView'
    OrderRejectPayload:
      type: object
      required:
        - command_type
        - reason
        - correlation_id
      properties:
        command_type:
          type: string
          enum:
            - AMEND
            - CANCEL
        order_id:
          type: string
          format: uuid
        client_order_id:
          type: string
        reason:
          type: string
        correlation_id:
          type: string
          format: uuid
          description: Correlation ID from the rejected command
    StreamFrameOrderReject:
      type: object
      required:
        - type
        - seq
        - ts_ns
        - payload
      properties:
        type:
          type: string
          enum:
            - order_reject
        seq:
          type: integer
          format: int64
          minimum: 0
        ts_ns:
          type: integer
          format: int64
        payload:
          $ref: '#/components/schemas/OrderRejectPayload'
    StreamFramePayout:
      type: object
      required:
        - type
        - seq
        - ts_ns
        - payload
      properties:
        type:
          type: string
          enum:
            - payout
        seq:
          type: integer
          format: int64
          minimum: 0
        ts_ns:
          type: integer
          format: int64
        payload:
          $ref: '#/components/schemas/PayoutView'
    AccountingPositionDelta:
      type: object
      required:
        - yes_qty
        - no_qty
        - yes_reserved
        - no_reserved
      properties:
        yes_qty:
          $ref: '#/components/schemas/Decimal'
        no_qty:
          $ref: '#/components/schemas/Decimal'
        yes_reserved:
          $ref: '#/components/schemas/Decimal'
        no_reserved:
          $ref: '#/components/schemas/Decimal'
    StreamFrameAccountingUpdate:
      type: object
      required:
        - type
        - seq
        - ts_ns
        - payload
      properties:
        type:
          type: string
          enum:
            - accounting_update
        seq:
          type: integer
          format: int64
        ts_ns:
          type: integer
          format: int64
        payload:
          type: object
          required:
            - account_id
            - market_id
            - ref_id
            - client_ref_id
            - deltas
          properties:
            account_id:
              type: string
            market_id:
              type: string
            ref_id:
              type: string
              description: System reference (order_id or cmd_id)
            client_ref_id:
              type: string
              description: Client reference (client_order_id or client_merge_id)
            deltas:
              type: object
              required:
                - cash
                - position_delta
                - margin_release
              properties:
                cash:
                  $ref: '#/components/schemas/Decimal'
                margin_release:
                  $ref: '#/components/schemas/Decimal'
                position_delta:
                  $ref: '#/components/schemas/AccountingPositionDelta'
            merge:
              type: object
              required:
                - merge_id
                - merged_qty
                - pnl_realized
              properties:
                merge_id:
                  type: string
                  format: uuid
                merged_qty:
                  $ref: '#/components/schemas/Decimal'
                pnl_realized:
                  $ref: '#/components/schemas/Decimal'
    StreamFrameAccountingPayout:
      type: object
      required:
        - type
        - seq
        - ts_ns
        - payload
      properties:
        type:
          type: string
          enum:
            - accounting_payout
        seq:
          type: integer
          format: int64
        ts_ns:
          type: integer
          format: int64
        payload:
          type: object
          required:
            - account_id
            - market_id
            - payout_id
            - reason
            - cash_delta
            - position_delta
          properties:
            account_id:
              type: string
            market_id:
              type: string
            payout_id:
              type: string
              format: uuid
            reason:
              type: string
              enum:
                - SETTLEMENT
                - VOID
            cash_delta:
              $ref: '#/components/schemas/Decimal'
            position_delta:
              $ref: '#/components/schemas/AccountingPositionDelta'
    StreamFrameHeartbeat:
      type: object
      required:
        - type
        - ts_ns
        - payload
      properties:
        type:
          type: string
          enum:
            - heartbeat
        seq:
          type: integer
          format: int64
          minimum: 0
          description: Repeats last delivered data seq; heartbeat does not advance global seq
        ts_ns:
          type: integer
          format: int64
        payload:
          type: object
          required:
            - interval_sec
          properties:
            interval_sec:
              type: integer
              minimum: 1
    StreamFrameBacklogEnd:
      type: object
      required:
        - type
        - seq
        - ts_ns
        - payload
      properties:
        type:
          type: string
          enum:
            - backlog_end
        seq:
          type: integer
          format: int64
          minimum: 0
        ts_ns:
          type: integer
          format: int64
        payload:
          type: object
          required:
            - last_seq
          properties:
            last_seq:
              type: integer
              format: int64
              minimum: 0
    StreamFrameError:
      type: object
      required:
        - type
        - seq
        - ts_ns
        - payload
      properties:
        type:
          type: string
          enum:
            - error
        seq:
          type: integer
          format: int64
          minimum: 0
        ts_ns:
          type: integer
          format: int64
        payload:
          type: object
          required:
            - code
            - message
          properties:
            code:
              type: string
            message:
              type: string
            details:
              type: object
              additionalProperties: true
    MDTrade:
      type: object
      required:
        - type
        - market_id
        - md_seq
        - ts_ns
        - trade_id
        - price
        - qty
        - taker_side
      properties:
        type:
          type: string
          enum:
            - trade
        market_id:
          type: string
        md_seq:
          type: integer
          format: int64
          minimum: 0
        ts_ns:
          type: integer
          format: int64
        trade_id:
          type: string
        price:
          $ref: '#/components/schemas/Decimal'
        qty:
          $ref: '#/components/schemas/Decimal'
        taker_side:
          $ref: '#/components/schemas/OrderSide'
          description: Side of the taker order that initiated the trade
    StreamFrameEventUpdate:
      type: object
      required:
        - type
        - seq
        - ts_ns
        - event_type
        - payload
      properties:
        type:
          type: string
          enum:
            - event_update
        seq:
          type: integer
          format: int64
          minimum: 0
          description: Monotonic sequence for lifecycle events
        ts_ns:
          type: integer
          format: int64
          description: Timestamp in nanoseconds
        event_id:
          type: string
          nullable: true
          description: Event ID (if applicable)
        market_id:
          type: string
          nullable: true
          description: Market ID (if applicable)
        event_type:
          $ref: '#/components/schemas/LifecycleEventType'
        payload:
          oneOf:
            - $ref: '#/components/schemas/EventCreatedPayload'
            - $ref: '#/components/schemas/EventStatusChangedPayload'
            - $ref: '#/components/schemas/EventSettledPayload'
            - $ref: '#/components/schemas/EventVoidedPayload'
            - $ref: '#/components/schemas/MarketCreatedPayload'
            - $ref: '#/components/schemas/MarketStatusChangedPayload'
            - $ref: '#/components/schemas/MarketDetailsChangedPayload'
            - $ref: '#/components/schemas/MarketSettledPayload'
            - $ref: '#/components/schemas/MarketVoidedPayload'
    OrderSide:
      type: string
      enum:
        - BUY
        - SELL
    OrderType:
      type: string
      enum:
        - LIMIT
        - MARKET
    OrderTIF:
      type: string
      enum:
        - GTC
        - IOC
        - FOK
        - DAY
    Token:
      type: string
      enum:
        - 'YES'
        - 'NO'
    Decimal:
      type: object
      required:
        - units
        - nanos
      properties:
        units:
          type: integer
          format: int64
        nanos:
          type: integer
          format: int32
          minimum: -999999999
          maximum: 999999999
    OrderStatus:
      type: string
      enum:
        - NEW
        - ACTIVE
        - PARTIALLY_FILLED
        - FILLED
        - CANCELLED
        - EXPIRED
        - REJECTED
    OrderStatusReason:
      type: string
      description: Machine-readable reason code for the current status
      enum:
        - USER_REQUEST
        - MARKET_CLOSED
        - MARKET_SETTLED
        - MARKET_VOIDED
        - TIF_EXPIRATION
        - FOK_FAILED
        - IOC_PARTIAL
        - SELF_TRADE_PREVENTION
        - INSUFFICIENT_FUNDS
        - SYSTEM_ERROR
        - ORDER_ALREADY_FILLED
        - ORDER_ALREADY_CANCELLED
        - ORDER_NOT_FOUND
        - QTY_INVALID
        - QTY_BELOW_EXECUTED
        - NO_MATCHING_LIQUIDITY
        - DUPLICATE_CLIENT_ORDER_ID
        - EXCEEDS_MAX_ORDER_QTY
        - EXCEEDS_MAX_ORDER_NOTIONAL
        - EXCEEDS_MAX_POSITION
    MDBookSnapshot:
      type: object
      required:
        - type
        - market_id
        - md_seq
        - ts_ns
        - bids
        - asks
      properties:
        type:
          type: string
          enum:
            - book_snapshot
        market_id:
          type: string
        md_seq:
          type: integer
          format: int64
          minimum: 0
        ts_ns:
          type: integer
          format: int64
        bids:
          type: array
          items:
            type: object
            required:
              - price
              - qty
            properties:
              price:
                $ref: '#/components/schemas/Decimal'
              qty:
                $ref: '#/components/schemas/Decimal'
        asks:
          type: array
          items:
            type: object
            required:
              - price
              - qty
            properties:
              price:
                $ref: '#/components/schemas/Decimal'
              qty:
                $ref: '#/components/schemas/Decimal'
    MDBookDiff:
      type: object
      required:
        - type
        - market_id
        - md_seq
        - ts_ns
        - bids
        - asks
      properties:
        type:
          type: string
          enum:
            - book_diff
        market_id:
          type: string
        md_seq:
          type: integer
          format: int64
          minimum: 0
        ts_ns:
          type: integer
          format: int64
        bids:
          description: Price level deltas (upsert or delete if qty=0)
          type: array
          items:
            type: object
            required:
              - price
              - qty
            properties:
              price:
                $ref: '#/components/schemas/Decimal'
              qty:
                $ref: '#/components/schemas/Decimal'
        asks:
          description: Price level deltas (upsert or delete if qty=0)
          type: array
          items:
            type: object
            required:
              - price
              - qty
            properties:
              price:
                $ref: '#/components/schemas/Decimal'
              qty:
                $ref: '#/components/schemas/Decimal'
    MDHeartbeat:
      type: object
      required:
        - type
        - ts_ns
      properties:
        type:
          type: string
          enum:
            - heartbeat
        ts_ns:
          type: integer
          format: int64
        market_id:
          type: string
          nullable: true
          description: Optional; may be omitted for aggregated heartbeat across all subscribed markets
        md_seq:
          type: integer
          format: int64
          minimum: 0
          nullable: true
          description: Repeats last data md_seq for the specific market if market_id present; absent for aggregated heartbeat
    MDError:
      type: object
      required:
        - type
        - ts_ns
        - payload
      properties:
        type:
          type: string
          enum:
            - error
        ts_ns:
          type: integer
          format: int64
        payload:
          type: object
          required:
            - code
            - message
          properties:
            code:
              type: string
            message:
              type: string
            details:
              type: object
              additionalProperties: true
    MDClientSubscribe:
      description: Client → Server subscription request (WebSocket text JSON)
      type: object
      required:
        - type
        - markets
      properties:
        type:
          type: string
          enum:
            - subscribe
        markets:
          type: array
          minItems: 1
          items:
            type: string
        force_snapshot:
          type: boolean
          default: false
          description: If true, forces fresh snapshot even if already subscribed
    MDClientUnsubscribe:
      description: Client → Server unsubscribe request
      type: object
      required:
        - type
        - markets
      properties:
        type:
          type: string
          enum:
            - unsubscribe
        markets:
          type: array
          minItems: 1
          items:
            type: string
    LifecycleEventType:
      type: string
      enum:
        - event.created
        - event.status_changed
        - event.settled
        - event.voided
        - market.created
        - market.status_changed
        - market.details_changed
        - market.settled
        - market.voided
      description: Lifecycle event type
    LocalizedText:
      type: object
      description: |
        Localized text content. When lang filter is applied, only requested locales are included.
        When lang filter is omitted, all available locales (en, pt, id) are included.
      properties:
        en:
          type: string
        pt:
          type: string
        id:
          type: string
    EventSeries:
      type: string
      enum:
        - ''
        - 1min
        - 5min
        - 15min
        - 1h
        - 4h
        - 1d
      description: |
        Event series meta tag. Empty = standalone event. Non-empty values indicate
        the event belongs to a recurring series with the given interval.
    EventStatus:
      type: string
      enum:
        - open
        - closed
        - settled
        - voided
      description: |
        - open: event is open, trading on markets is open or will be open (market statuses: unopened, pre-open, active, or suspended)
        - closed: event is closed, trading stopped, markets awaiting resolution and settlement
        - settled: all markets resolved and payouts completed
        - voided: event cancelled, all markets voided, contracts refunded
    SettlementSource:
      type: object
      required:
        - name
        - url
      properties:
        name:
          type: string
        url:
          type: string
        underlying:
          type: object
          required:
            - base_currency
            - quote_currency
          properties:
            base_currency:
              type: string
            quote_currency:
              type: string
    EventCreatedPayload:
      type: object
      required:
        - event_id
        - title
        - rules_primary
        - category
        - series
        - settlement_sources
        - status
        - created_at
        - opens_at
        - closes_at
        - markets
      properties:
        event_id:
          type: string
        title:
          $ref: '#/components/schemas/LocalizedText'
        series_title:
          allOf:
            - $ref: '#/components/schemas/LocalizedText'
          nullable: true
          description: Localized title of the recurring series this event belongs to. Null when the event's template defines no series title. Absent in payloads emitted before this field was introduced.
        rules_primary:
          $ref: '#/components/schemas/LocalizedText'
        category:
          type: string
          enum:
            - crypto
            - forex
        image_url:
          type: string
        series:
          $ref: '#/components/schemas/EventSeries'
        status:
          $ref: '#/components/schemas/EventStatus'
        void_reason:
          type: string
          nullable: true
        created_at:
          type: string
          format: date-time
        opens_at:
          type: string
          format: date-time
        closes_at:
          type: string
          format: date-time
        settlement_sources:
          type: array
          items:
            $ref: '#/components/schemas/SettlementSource'
        markets:
          type: array
          items:
            type: string
          description: Array of market_ids belonging to this event
    EventStatusChangedPayload:
      type: object
      required:
        - event_id
        - old_status
        - new_status
      properties:
        event_id:
          type: string
        old_status:
          $ref: '#/components/schemas/EventStatus'
        new_status:
          $ref: '#/components/schemas/EventStatus'
    EventSettledPayload:
      type: object
      required:
        - event_id
        - resolved_at
      properties:
        event_id:
          type: string
        expiration_value:
          type: string
          nullable: true
          description: 'Final asset value as string. Not necessarily a price, and not always numeric: it carries whatever value the settlement source publishes for the outcome. `null` if the resolution source publishes no closing value; the outcome is then carried by `resolved_market_id`, or by the per-market `result` when that is null too.'
        resolved_market_id:
          type: string
          nullable: true
          description: 'Market ID of the winning market. `null` when no market won, and also when more than one won: this field carries a single winner and cannot express several. Read the per-market `result` to see the outcome of every market.'
        resolved_at:
          type: string
          format: date-time
          description: Actual resolution timestamp
    EventVoidedPayload:
      type: object
      required:
        - event_id
        - void_reason
      properties:
        event_id:
          type: string
        void_reason:
          type: string
          description: Reason for voiding the event
    MarketStatus:
      type: string
      enum:
        - unopened
        - pre-open
        - active
        - suspended
        - closed
        - settled
        - voided
      description: |
        - unopened: created, not initialized in CLOB
        - pre-open: loaded in CLOB (MM can already place liquidity)
        - suspended: market open, but new orders are temporarily declined
        - active: market open for order acceptance
        - closed: closed, awaiting resolution; trading stopped, orders no longer accepted
        - settled: market resolved, payouts completed
        - voided: market cancelled, contracts refunded
    MarketResult:
      type: string
      enum:
        - 'yes'
        - 'no'
        - ''
      description: |
        Written when the event resolves, so it appears before the market reaches settled.
        - yes: YES is the winner
        - no: NO is the winner
        - empty: no result yet, or the market was voided
    MarketCreatedPayload:
      type: object
      required:
        - market_id
        - event_id
        - market_index
        - title
        - created_at
        - opens_at
        - closes_at
        - expected_expiration_time
        - settlement_timer_seconds
        - status
        - status_revision
        - result
        - can_close_early
        - strike_type
      properties:
        market_id:
          type: string
          description: Market ID
        event_id:
          type: string
        market_index:
          type: integer
          description: Index of the market within the event
        title:
          $ref: '#/components/schemas/LocalizedText'
        yes_sub_title:
          allOf:
            - $ref: '#/components/schemas/LocalizedText'
          nullable: true
        no_sub_title:
          allOf:
            - $ref: '#/components/schemas/LocalizedText'
          nullable: true
        lower_bound:
          allOf:
            - $ref: '#/components/schemas/Decimal'
          nullable: true
          description: Lower boundary for strike range (NULL for first market)
        upper_bound:
          allOf:
            - $ref: '#/components/schemas/Decimal'
          nullable: true
          description: Upper boundary for strike range (NULL for last market)
        lower_inclusive:
          type: boolean
          description: Whether lower_bound is inclusive in the range
        upper_inclusive:
          type: boolean
          description: Whether upper_bound is inclusive in the range
        status:
          $ref: '#/components/schemas/MarketStatus'
        status_revision:
          type: integer
          format: int64
          description: Monotonic revision of the market status from Event Service
        result:
          $ref: '#/components/schemas/MarketResult'
        created_at:
          type: string
          format: date-time
        opens_at:
          type: string
          format: date-time
        closes_at:
          type: string
          format: date-time
        expected_expiration_time:
          type: string
          format: date-time
        settlement_timer_seconds:
          type: integer
          format: int32
        can_close_early:
          type: boolean
        early_close_condition:
          type: string
          nullable: true
        strike_type:
          type: string
          enum:
            - greater
            - greater_or_equal
            - less
            - less_or_equal
            - between
            - custom
        floor_strike:
          allOf:
            - $ref: '#/components/schemas/Decimal'
          nullable: true
          description: Minimum expiration value that leads to a YES settlement
        cap_strike:
          allOf:
            - $ref: '#/components/schemas/Decimal'
          nullable: true
          description: Maximum expiration value that leads to a YES settlement
        custom_strike:
          type: string
          nullable: true
          description: Expiration value that leads to a YES settlement when strike_type=custom
    MarketStatusChangedPayload:
      type: object
      required:
        - market_id
        - event_id
        - old_status
        - new_status
        - status_revision
      properties:
        status_revision:
          type: integer
          format: int64
          description: Monotonic revision of the market status from Event Service
        market_id:
          type: string
        event_id:
          type: string
        old_status:
          $ref: '#/components/schemas/MarketStatus'
        new_status:
          $ref: '#/components/schemas/MarketStatus'
    MarketDetailsChangedPayload:
      type: object
      required:
        - market_id
        - event_id
        - title
        - status_revision
      properties:
        market_id:
          type: string
        event_id:
          type: string
        title:
          $ref: '#/components/schemas/LocalizedText'
        yes_sub_title:
          allOf:
            - $ref: '#/components/schemas/LocalizedText'
          nullable: true
        no_sub_title:
          allOf:
            - $ref: '#/components/schemas/LocalizedText'
          nullable: true
        floor_strike:
          allOf:
            - $ref: '#/components/schemas/Decimal'
          nullable: true
          description: Minimum expiration value that leads to a YES settlement
        status_revision:
          type: integer
          format: int64
          description: Monotonic revision of the market status from Event Service
    MarketSettledPayload:
      type: object
      required:
        - market_id
        - event_id
        - result
        - settlement_price
        - status_revision
      properties:
        market_id:
          type: string
        event_id:
          type: string
        result:
          type: string
          enum:
            - 'yes'
            - 'no'
          description: Winner (YES or NO)
        settlement_price:
          $ref: '#/components/schemas/Decimal'
          description: 1.0 for YES, 0.0 for NO
        expiration_value:
          type: string
          nullable: true
          description: 'Final asset value as string. Not necessarily a price, and not always numeric: it carries whatever value the settlement source publishes for the outcome. `null` if the resolution source publishes no closing value, in which case the outcome is carried by `result`.'
        status_revision:
          type: integer
          format: int64
          description: Monotonic revision of the market status from Event Service
    MarketVoidedPayload:
      type: object
      required:
        - market_id
        - event_id
        - void_reason
        - status_revision
      properties:
        market_id:
          type: string
        event_id:
          type: string
        void_reason:
          type: string
          description: Reason for voiding the market
        status_revision:
          type: integer
          format: int64
          description: Monotonic revision of the market status from Event Service
