openapi: 3.1.0
info:
  title: Exchange API — REST lane
  version: 1.0.0
  summary: Client-facing REST surface for the binary prediction-market exchange.
  description: |
    The client-visible REST contract of the API lane.

    ## Conventions
    - **Versioning:** every path is under `/v1/`. Unknown *request* fields are
      rejected (`400 unknown_field`); clients MUST ignore unknown *response*
      fields (additive changes are non-breaking).
    - **Numbers:** prices are integer **ticks** (1–99, cents); quantities are
      integer shares; money is integer cents. u64-domain values (`order_id`,
      `client_order_id`, `seq`, cents amounts, timestamps) render as decimal
      **strings**; u32-and-below render as JSON numbers. Floats never appear.
    - **Timestamps:** `ts`/`*_ts` fields are nanoseconds since the Unix epoch as
      decimal strings — the sequencer's stamp, untranslated.
    - **`as_of_seq`:** every read response carries the stream sequence number
      its answer is current as of. Two reads with the same `as_of_seq` describe
      one consistent instant.
    - **Pagination:** list endpoints take `limit` (default 100, max 1000) and an
      opaque `cursor`; responses echo `cursor` for the next page (empty string
      when exhausted).

    ## Authentication
    Every request — there is no anonymous surface — carries three headers:
    `DX-ACCESS-KEY`, `DX-ACCESS-TIMESTAMP` (Unix ms), and `DX-ACCESS-SIGNATURE`
    (base64 HMAC-SHA256 over the canonical string
    `timestamp + "\n" + METHOD + "\n" + path?query + "\n" + body`, ±5000 ms
    replay window). Keys carry scopes `read` (GETs), `trade` (order mutations),
    and `withdraw` (`POST /v1/withdrawals`); the required scope per operation
    is given in `x-required-scope`. Rate limits are per-key token buckets
    (read 100/s +200 burst; write 50/s +100 burst) answered with `429
    rate_limited` whose body carries `details.retry_after_ms` (decimal-string
    milliseconds — no `Retry-After` header in v1).

    ## Execution outcomes are not errors
    Post-only cross, FOK infeasible, IOC remainder, STP and max-cost-infeasible
    arrive as a **successful** placement response with `status: "canceled"` and
    a `reason` — they are the order's history, not a protocol failure. Only the
    reject taxonomy maps to non-2xx.
  x-other-lanes:
    websocket:
      endpoint: wss://{host}/v1/ws
      note: >-
        AsyncAPI territory — subscribe/unsubscribe command envelope, public
        channels (orderbook_delta, trades, oracle, rounds) and the private
        `user` channel with snapshot+resume. `ticker` is deferred (wscode 4).
        Not modelled in this OpenAPI document.
    mcp:
      endpoint: POST /v1/mcp
      note: >-
        JSON-RPC 2.0 agent lane exposing the same reads/writes as MCP tools.
        Described by its own tool schema, not OpenAPI.
externalDocs:
  description: Normative API surface specification (REST + WebSocket).
  url: https://docs.omnibook.xyz/
servers:
  - url: https://api.omnibook.xyz
    description: >-
      Production. Every request is authenticated — there is no anonymous access,
      including market data. See Authentication.
tags:
  - name: Exchange
    description: Exchange-wide control state and retention caps.
  - name: Account
    description: The authenticated key's own scopes and rate limits.
  - name: Markets
    description: Market metadata, orderbooks, public tape, oracle and rounds.
  - name: Portfolio (read)
    description: Key-scoped balance, positions, fills and orders.
  - name: Orders (write)
    description: Order placement, cancel, decrease, batch, cancel-batch and cancel-all.
  - name: Funding
    description: Deposit address and withdrawals.
  - name: Admin
    description: >
      Control-plane operator actions. Served on a SEPARATE listener from every
      path above — see the operation's own `servers` override; the top-level
      `servers` block does not reach it.
security:
  - DxAccessKey: []
    DxAccessTimestamp: []
    DxAccessSignature: []
paths:
  /v1/exchange/status:
    get:
      tags:
        - Exchange
      operationId: getExchangeStatus
      summary: Exchange active/paused state
      description: |
        v1 pins `exchange_active: true` — no exchange-pause control message
        exists yet. The field is not a working kill switch.
      x-required-scope: read
      responses:
        '200':
          description: Current exchange control state.
          content:
            application/json:
              schema:
                $ref: '#/components/schemas/ExchangeStatus'
              example:
                exchange_active: true
                as_of_seq: '182390'
        '401':
          $ref: '#/components/responses/Unauthorized'
        '403':
          $ref: '#/components/responses/MissingScope'
        '429':
          $ref: '#/components/responses/RateLimited'
  /v1/exchange/schedule:
    get:
      tags:
        - Exchange
      operationId: getExchangeSchedule
      summary: Maintenance windows (static reference data)
      x-required-scope: read
      responses:
        '200':
          description: Scheduled maintenance windows.
          content:
            application/json:
              schema:
                $ref: '#/components/schemas/ExchangeSchedule'
        '401':
          $ref: '#/components/responses/Unauthorized'
        '403':
          $ref: '#/components/responses/MissingScope'
        '429':
          $ref: '#/components/responses/RateLimited'
  /v1/exchange/retention:
    get:
      tags:
        - Exchange
      operationId: getRetention
      summary: Live-listing retention caps
      description: >
        Publishes the venue's live-listing retention caps. Live listings
        silently truncate at these caps; deeper

        history is deferred to the Postgres mirror. Resting orders are never

        evicted — only terminal rows are.
      x-required-scope: read
      responses:
        '200':
          description: The venue retention caps.
          content:
            application/json:
              schema:
                $ref: '#/components/schemas/RetentionLimits'
              example:
                tape_cap: 256
                terminal_cap: 256
                reject_cap: 256
                fill_cap: 256
        '401':
          $ref: '#/components/responses/Unauthorized'
        '403':
          $ref: '#/components/responses/MissingScope'
        '429':
          $ref: '#/components/responses/RateLimited'
  /v1/account/limits:
    get:
      tags:
        - Account
      operationId: getAccountLimits
      summary: The authenticated key's own scopes and rate limits
      description: |
        Reports the calling key's scopes, effective read/write token buckets,
        batch cap, and per-request response budget. Answered from
        the key record + gateway config, so it carries no `as_of_seq`.
      x-required-scope: read
      responses:
        '200':
          description: The key's scopes and limits.
          content:
            application/json:
              schema:
                $ref: '#/components/schemas/AccountLimits'
              example:
                scopes:
                  - read
                  - trade
                read:
                  sustained_rate_per_s: 100
                  burst: 200
                write:
                  sustained_rate_per_s: 50
                  burst: 100
                batch_cap: 20
                response_budget_ms: 2000
        '400':
          $ref: '#/components/responses/BadRequest'
        '401':
          $ref: '#/components/responses/Unauthorized'
        '403':
          $ref: '#/components/responses/MissingScope'
        '429':
          $ref: '#/components/responses/RateLimited'
  /v1/markets:
    get:
      tags:
        - Markets
      operationId: listMarkets
      summary: List markets
      x-required-scope: read
      parameters:
        - name: status
          in: query
          description: Comma-separated list of market statuses to filter by.
          required: false
          schema:
            type: string
            example: trading,proposed
        - $ref: '#/components/parameters/Cursor'
        - $ref: '#/components/parameters/Limit'
      responses:
        '200':
          description: A page of market objects.
          content:
            application/json:
              schema:
                $ref: '#/components/schemas/MarketsPage'
        '400':
          $ref: '#/components/responses/BadRequest'
        '401':
          $ref: '#/components/responses/Unauthorized'
        '403':
          $ref: '#/components/responses/MissingScope'
        '429':
          $ref: '#/components/responses/RateLimited'
  /v1/markets/{market_id}:
    get:
      tags:
        - Markets
      operationId: getMarket
      summary: Get a single market
      x-required-scope: read
      parameters:
        - $ref: '#/components/parameters/MarketIdPath'
      responses:
        '200':
          description: The market object.
          content:
            application/json:
              schema:
                allOf:
                  - $ref: '#/components/schemas/Market'
                  - $ref: '#/components/schemas/AsOfSeq'
        '401':
          $ref: '#/components/responses/Unauthorized'
        '403':
          $ref: '#/components/responses/MissingScope'
        '404':
          $ref: '#/components/responses/NotFound'
        '429':
          $ref: '#/components/responses/RateLimited'
  /v1/markets/{market_id}/orderbook:
    get:
      tags:
        - Markets
      operationId: getOrderbook
      summary: L2 orderbook (bids-only, both sides)
      description: |
        Bids only, both sides. `yes` = YES bids at YES-space
        ticks; `no` = NO bids at NO-space ticks; each best-first. A NO bid at
        `t` *is* the YES ask at `100 − t` — asks are derivable, never sent.
        Level entries are `[tick, aggregate_qty]`; per-order data is never
        published.
      x-required-scope: read
      parameters:
        - $ref: '#/components/parameters/MarketIdPath'
        - name: depth
          in: query
          description: Levels per side.
          required: false
          schema:
            type: integer
            default: 10
            minimum: 1
            maximum: 99
      responses:
        '200':
          description: The L2 book.
          content:
            application/json:
              schema:
                $ref: '#/components/schemas/Orderbook'
              example:
                market_id: 1
                'yes':
                  - - 47
                    - 1200
                  - - 46
                    - 800
                'no':
                  - - 52
                    - 950
                  - - 51
                    - 400
                as_of_seq: '182390'
        '401':
          $ref: '#/components/responses/Unauthorized'
        '403':
          $ref: '#/components/responses/MissingScope'
        '404':
          $ref: '#/components/responses/NotFound'
        '429':
          $ref: '#/components/responses/RateLimited'
  /v1/markets/{market_id}/trades:
    get:
      tags:
        - Markets
      operationId: getTrades
      summary: Public trade tape (newest-first)
      x-required-scope: read
      parameters:
        - $ref: '#/components/parameters/MarketIdPath'
        - $ref: '#/components/parameters/Cursor'
        - $ref: '#/components/parameters/Limit'
      responses:
        '200':
          description: A page of public tape entries.
          content:
            application/json:
              schema:
                $ref: '#/components/schemas/TradesPage'
        '400':
          $ref: '#/components/responses/BadRequest'
        '401':
          $ref: '#/components/responses/Unauthorized'
        '403':
          $ref: '#/components/responses/MissingScope'
        '404':
          $ref: '#/components/responses/NotFound'
        '429':
          $ref: '#/components/responses/RateLimited'
  /v1/oracle/price:
    get:
      tags:
        - Markets
      operationId: getOraclePrice
      summary: Live oracle reference price
      description: |
        The live oracle reference price @1e8. When no valid
        tick exists yet, the zero sentinel applies (`price: "0"`,
        `fresh_source_count: 0`, `seq: "0"`).
      x-required-scope: read
      responses:
        '200':
          description: Current oracle price.
          content:
            application/json:
              schema:
                $ref: '#/components/schemas/OraclePrice'
              example:
                price: '10000000000000'
                fresh_source_count: 3
                seq: '42'
        '401':
          $ref: '#/components/responses/Unauthorized'
        '403':
          $ref: '#/components/responses/MissingScope'
        '429':
          $ref: '#/components/responses/RateLimited'
  /v1/rounds:
    get:
      tags:
        - Markets
      operationId: listRounds
      summary: Recent rounds (most-recent-first)
      description: |
        Recent rounds, paginated descending by `round_number`.
        Optional `category` scopes to one series.
      x-required-scope: read
      parameters:
        - name: category
          in: query
          description: Optional series/category filter (u16).
          required: false
          schema:
            type: integer
            minimum: 0
            maximum: 65535
        - $ref: '#/components/parameters/Cursor'
        - $ref: '#/components/parameters/Limit'
      responses:
        '200':
          description: A page of round objects.
          content:
            application/json:
              schema:
                $ref: '#/components/schemas/RoundsPage'
        '400':
          $ref: '#/components/responses/BadRequest'
        '401':
          $ref: '#/components/responses/Unauthorized'
        '403':
          $ref: '#/components/responses/MissingScope'
        '429':
          $ref: '#/components/responses/RateLimited'
  /v1/rounds/{market_id}:
    get:
      tags:
        - Markets
      operationId: getRound
      summary: A market's current round
      description: |
        One market's current round in any status. Never-opened
        or evicted-past-ring ⇒ 404 `round_not_found`.
      x-required-scope: read
      parameters:
        - $ref: '#/components/parameters/MarketIdPath'
      responses:
        '200':
          description: The round object plus `as_of_seq`.
          content:
            application/json:
              schema:
                allOf:
                  - $ref: '#/components/schemas/Round'
                  - $ref: '#/components/schemas/AsOfSeq'
        '401':
          $ref: '#/components/responses/Unauthorized'
        '403':
          $ref: '#/components/responses/MissingScope'
        '404':
          description: '`round_not_found` — unknown market, never opened, or evicted.'
          content:
            application/json:
              schema:
                $ref: '#/components/schemas/ErrorEnvelope'
              example:
                error:
                  code: round_not_found
                  num: null
                  origin: gateway
                  message: round_not_found
                  details: null
        '429':
          $ref: '#/components/responses/RateLimited'
  /v1/portfolio/balance:
    get:
      tags:
        - Portfolio (read)
      operationId: getBalance
      summary: Cash balance (available / reserved)
      x-required-scope: read
      responses:
        '200':
          description: Cash balance in cents.
          content:
            application/json:
              schema:
                $ref: '#/components/schemas/Balance'
              example:
                available: '1250000'
                reserved: '300000'
                as_of_seq: '182390'
        '401':
          $ref: '#/components/responses/Unauthorized'
        '403':
          $ref: '#/components/responses/MissingScope'
        '429':
          $ref: '#/components/responses/RateLimited'
  /v1/portfolio/positions:
    get:
      tags:
        - Portfolio (read)
      operationId: getPositions
      summary: Positions per (market, outcome)
      x-required-scope: read
      parameters:
        - $ref: '#/components/parameters/MarketIdQuery'
        - $ref: '#/components/parameters/Cursor'
        - $ref: '#/components/parameters/Limit'
      responses:
        '200':
          description: A page of positions.
          content:
            application/json:
              schema:
                $ref: '#/components/schemas/PositionsPage'
        '401':
          $ref: '#/components/responses/Unauthorized'
        '403':
          $ref: '#/components/responses/MissingScope'
        '429':
          $ref: '#/components/responses/RateLimited'
  /v1/portfolio/fills:
    get:
      tags:
        - Portfolio (read)
      operationId: getFills
      summary: The caller's fills (either role), newest-first
      x-required-scope: read
      parameters:
        - $ref: '#/components/parameters/MarketIdQuery'
        - name: order_id
          in: query
          required: false
          schema:
            $ref: '#/components/schemas/U64String'
        - $ref: '#/components/parameters/Cursor'
        - $ref: '#/components/parameters/Limit'
      responses:
        '200':
          description: A page of fills.
          content:
            application/json:
              schema:
                $ref: '#/components/schemas/FillsPage'
        '401':
          $ref: '#/components/responses/Unauthorized'
        '403':
          $ref: '#/components/responses/MissingScope'
        '429':
          $ref: '#/components/responses/RateLimited'
  /v1/portfolio/orders:
    get:
      tags:
        - Portfolio (read)
      operationId: listOrders
      summary: List the caller's orders
      x-required-scope: read
      parameters:
        - $ref: '#/components/parameters/MarketIdQuery'
        - name: status
          in: query
          description: Comma-separated list of order statuses to filter by.
          required: false
          schema:
            type: string
            example: resting,partially_filled
        - name: client_order_id
          in: query
          required: false
          schema:
            $ref: '#/components/schemas/U64String'
        - $ref: '#/components/parameters/Cursor'
        - $ref: '#/components/parameters/Limit'
      responses:
        '200':
          description: A page of order objects.
          content:
            application/json:
              schema:
                $ref: '#/components/schemas/OrdersPage'
        '400':
          $ref: '#/components/responses/BadRequest'
        '401':
          $ref: '#/components/responses/Unauthorized'
        '403':
          $ref: '#/components/responses/MissingScope'
        '429':
          $ref: '#/components/responses/RateLimited'
    post:
      tags:
        - Orders (write)
      operationId: placeOrder
      summary: Place an order
      description: >
        Closed request schema — an unknown field is `400 unknown_field`. The

        `201` reports the **actual sequenced outcome** (the gateway holds the

        request through ack-by-observation), not a synthesized "accepted".

        Execution outcomes (post-only cross, FOK/IOC, STP, max-cost) return

        `201` with `status: "canceled"` and a `reason` — they are not errors.
        Reservation and fee accounting are absent here; they

        arrive on the WS `user` channel or via the balance read.
      x-required-scope: trade
      requestBody:
        required: true
        content:
          application/json:
            schema:
              $ref: '#/components/schemas/PlaceOrderRequest'
      responses:
        '201':
          description: The sequenced placement outcome.
          content:
            application/json:
              schema:
                $ref: '#/components/schemas/PlaceOrderResponse'
              example:
                order_id: '16777291'
                client_order_id: '42'
                status: partially_filled
                seq: '182391'
                filled_qty: 100
                resting_qty: 400
                fills:
                  - tick_yes: 47
                    qty: 100
                    path: direct
        '400':
          $ref: '#/components/responses/BadRequest'
        '401':
          $ref: '#/components/responses/Unauthorized'
        '403':
          $ref: '#/components/responses/MissingScope'
        '409':
          $ref: '#/components/responses/Conflict'
        '422':
          $ref: '#/components/responses/Unprocessable'
        '429':
          $ref: '#/components/responses/RateLimited'
        '503':
          $ref: '#/components/responses/Shed'
        '504':
          $ref: '#/components/responses/SequencingTimeout'
    delete:
      tags:
        - Orders (write)
      operationId: cancelAllOrders
      summary: Cancel all orders
      description: |
        Cancel all of the caller's resting orders (optionally scoped by
        `market_id` / `group_id`). Maps to MASS_CANCEL. The
        sequenced command is the acknowledgment — there is no summary event
        and no count. The resulting CANCELED events stream on the WS `user`
        channel.

        The request body MUST be empty: a non-empty body is rejected 400
        `unexpected_body` before the query is decoded, so a client that
        meant `DELETE /v1/portfolio/orders/batch` and dropped the last path
        segment fails closed instead of retiring its whole book.
      x-required-scope: trade
      parameters:
        - name: market_id
          in: query
          description: Restrict to one market; 0 or absent = all markets.
          required: false
          schema:
            type: integer
        - name: group_id
          in: query
          description: Restrict to one order group; 0 or absent = all groups.
          required: false
          schema:
            type: integer
      responses:
        '202':
          description: Cancel-all sequenced.
          content:
            application/json:
              schema:
                $ref: '#/components/schemas/SeqAck'
              example:
                seq: '182400'
        '400':
          $ref: '#/components/responses/BadRequest'
        '401':
          $ref: '#/components/responses/Unauthorized'
        '403':
          $ref: '#/components/responses/MissingScope'
        '429':
          $ref: '#/components/responses/RateLimited'
        '503':
          $ref: '#/components/responses/Shed'
        '504':
          $ref: '#/components/responses/SequencingTimeout'
  /v1/portfolio/orders/batch:
    post:
      tags:
        - Orders (write)
      operationId: batchPlaceOrders
      summary: Place up to 20 orders
      description: |
        Fanned into independent commands. The response is
        per-item and index-aligned: each entry is either a placement response
        or an error object. Partial success is normal, not an error. Costs N
        write tokens.
      x-required-scope: trade
      requestBody:
        required: true
        content:
          application/json:
            schema:
              $ref: '#/components/schemas/BatchRequest'
      responses:
        '201':
          description: Per-item, index-aligned results.
          content:
            application/json:
              schema:
                $ref: '#/components/schemas/BatchResponse'
        '400':
          $ref: '#/components/responses/BadRequest'
        '401':
          $ref: '#/components/responses/Unauthorized'
        '403':
          $ref: '#/components/responses/MissingScope'
        '429':
          $ref: '#/components/responses/RateLimited'
        '503':
          $ref: '#/components/responses/Shed'
    delete:
      tags:
        - Orders (write)
      operationId: cancelOrdersBatch
      summary: Cancel up to 20 orders by id
      description: |
        Fanned into independent CANCEL commands. The response
        is per-item and index-aligned: each entry is either a cancel result
        or an error object. Unknown / foreign / already-terminal ids settle
        as `unknown_order`. Partial success is normal, not an error. Costs N
        write tokens.

        The body is required. Signing covers it, so a client or
        intermediary that drops a DELETE body gets 401, never a partial
        cancel.
      x-required-scope: trade
      requestBody:
        required: true
        content:
          application/json:
            schema:
              $ref: '#/components/schemas/CancelBatchRequest'
            example:
              order_ids:
                - '16777291'
                - '16777292'
      responses:
        '200':
          description: Per-item, index-aligned results.
          content:
            application/json:
              schema:
                $ref: '#/components/schemas/CancelBatchResponse'
        '400':
          $ref: '#/components/responses/BadRequest'
        '401':
          $ref: '#/components/responses/Unauthorized'
        '403':
          $ref: '#/components/responses/MissingScope'
        '429':
          $ref: '#/components/responses/RateLimited'
        '503':
          $ref: '#/components/responses/Shed'
  /v1/portfolio/orders/{order_id}:
    get:
      tags:
        - Portfolio (read)
      operationId: getOrder
      summary: Get one order
      x-required-scope: read
      parameters:
        - $ref: '#/components/parameters/OrderIdPath'
      responses:
        '200':
          description: The order object.
          content:
            application/json:
              schema:
                allOf:
                  - $ref: '#/components/schemas/Order'
                  - $ref: '#/components/schemas/AsOfSeq'
        '401':
          $ref: '#/components/responses/Unauthorized'
        '403':
          $ref: '#/components/responses/MissingScope'
        '404':
          $ref: '#/components/responses/NotFound'
        '429':
          $ref: '#/components/responses/RateLimited'
    delete:
      tags:
        - Orders (write)
      operationId: cancelOrder
      summary: Cancel an order
      description: |
        `404 unknown_order` covers unknown, foreign, already-terminal and the
        cancel-lost-the-race case — deliberately indistinguishable, resolved by
        `GET /v1/portfolio/orders/{order_id}`.
      x-required-scope: trade
      parameters:
        - $ref: '#/components/parameters/OrderIdPath'
      responses:
        '200':
          description: Order canceled.
          content:
            application/json:
              schema:
                $ref: '#/components/schemas/CancelResult'
              example:
                status: canceled
                canceled_qty: 300
                seq: '182401'
        '401':
          $ref: '#/components/responses/Unauthorized'
        '403':
          $ref: '#/components/responses/MissingScope'
        '404':
          $ref: '#/components/responses/NotFound'
        '429':
          $ref: '#/components/responses/RateLimited'
        '504':
          $ref: '#/components/responses/SequencingTimeout'
  /v1/portfolio/orders/{order_id}/decrease:
    post:
      tags:
        - Orders (write)
      operationId: decreaseOrder
      summary: Decrease an order's quantity (absolute target)
      description: |
        `new_qty` is an **absolute target**, not a delta — idempotent resends.
        Decrease-in-place keeps time priority; there is no amend endpoint
        (increase or reprice = cancel + new).
      x-required-scope: trade
      parameters:
        - $ref: '#/components/parameters/OrderIdPath'
      requestBody:
        required: true
        content:
          application/json:
            schema:
              $ref: '#/components/schemas/DecreaseRequest'
      responses:
        '200':
          description: Order decreased.
          content:
            application/json:
              schema:
                $ref: '#/components/schemas/DecreaseResult'
              example:
                status: decreased
                new_qty: 300
                seq: '182402'
        '400':
          $ref: '#/components/responses/BadRequest'
        '401':
          $ref: '#/components/responses/Unauthorized'
        '403':
          $ref: '#/components/responses/MissingScope'
        '404':
          $ref: '#/components/responses/NotFound'
        '429':
          $ref: '#/components/responses/RateLimited'
        '504':
          $ref: '#/components/responses/SequencingTimeout'
  /v1/portfolio/orders/{order_id}/queue_position:
    get:
      tags:
        - Portfolio (read)
      operationId: getQueuePosition
      summary: Shares resting ahead of an order (price-time priority)
      description: |
        Shares resting ahead of the caller's order at its canonical YES-space
        price level, by time priority. Exact for the resting
        orders it reports on (the read model never evicts a resting order); a
        lower bound on wait (counts only already-resting size). `404
        unknown_order` covers unknown / foreign / already-terminal.
      x-required-scope: read
      parameters:
        - $ref: '#/components/parameters/OrderIdPath'
      responses:
        '200':
          description: Shares ahead under price-time priority.
          content:
            application/json:
              schema:
                $ref: '#/components/schemas/QueuePosition'
              example:
                shares_ahead: '1200'
                as_of_seq: '182390'
        '401':
          $ref: '#/components/responses/Unauthorized'
        '403':
          $ref: '#/components/responses/MissingScope'
        '404':
          $ref: '#/components/responses/NotFound'
        '429':
          $ref: '#/components/responses/RateLimited'
  /v1/portfolio/deadman:
    post:
      tags:
        - Orders (write)
      operationId: armDeadman
      summary: Arm / refresh the dead-man switch
      description: >
        Arm (or heartbeat-refresh) the caller's **gateway** dead-man switch.
        Sets the deadline to `now + timeout_ms`;

        calling again refreshes it. When a tick reaches an armed deadline the

        gateway fires one user-wide MASS_CANCEL and disarms (fire-once). This is

        the gateway fairy dead-man, NOT Kalshi order-group fill-velocity
        limiting

        (which stays deferred). Closed schema — an unknown field is `400

        unknown_field`; a `timeout_ms` outside `1000..=60000` is `400

        invalid_timeout`.
      x-required-scope: trade
      requestBody:
        required: true
        content:
          application/json:
            schema:
              $ref: '#/components/schemas/DeadmanArmRequest'
      responses:
        '200':
          description: Dead-man armed / refreshed.
          content:
            application/json:
              schema:
                $ref: '#/components/schemas/DeadmanArmResponse'
              example:
                timeout_ms: 5000
                armed: true
        '400':
          $ref: '#/components/responses/BadRequest'
        '401':
          $ref: '#/components/responses/Unauthorized'
        '403':
          $ref: '#/components/responses/MissingScope'
        '429':
          $ref: '#/components/responses/RateLimited'
    delete:
      tags:
        - Orders (write)
      operationId: disarmDeadman
      summary: Disarm the dead-man switch
      description: |
        Disarm the caller's dead-man switch. Bodyless — a
        non-empty body is `400 unexpected_body`. Idempotent: disarming when not
        armed still returns `armed: false`.
      x-required-scope: trade
      responses:
        '200':
          description: Dead-man disarmed.
          content:
            application/json:
              schema:
                $ref: '#/components/schemas/DeadmanDisarmResponse'
              example:
                armed: false
        '400':
          $ref: '#/components/responses/BadRequest'
        '401':
          $ref: '#/components/responses/Unauthorized'
        '403':
          $ref: '#/components/responses/MissingScope'
        '429':
          $ref: '#/components/responses/RateLimited'
  /v1/deposit-address:
    get:
      tags:
        - Funding
      operationId: getDepositAddress
      summary: The user's deposit address
      x-required-scope: read
      responses:
        '200':
          description: Deposit address, `0x` + 40 hex.
          content:
            application/json:
              schema:
                $ref: '#/components/schemas/DepositAddress'
              example:
                address: '0x5aaeb6053f3e94c9b9a09f33669435e7ef1beaed'
        '401':
          $ref: '#/components/responses/Unauthorized'
        '403':
          $ref: '#/components/responses/MissingScope'
        '429':
          $ref: '#/components/responses/RateLimited'
        '504':
          $ref: '#/components/responses/SequencingTimeout'
  /v1/withdrawals:
    post:
      tags:
        - Funding
      operationId: postWithdrawal
      summary: Request a withdrawal
      description: |
        Closed schema. `client_withdrawal_id` is the per-user
        idempotency key and must be **strictly increasing**: a fresh id
        (`> hwm`) is forwarded; an equal id (`== hwm`) is treated as an
        idempotent retry and returns the current phase; a stale id (`< hwm`)
        returns `409 stale_client_withdrawal_id`. `201` = freshly accepted;
        `200` = a phase report for a retry/known id. The gateway validates
        `dest` structurally only and forwards its casing verbatim for the
        funding port's EIP-55 check.
      x-required-scope: withdraw
      requestBody:
        required: true
        content:
          application/json:
            schema:
              $ref: '#/components/schemas/WithdrawalRequest'
      responses:
        '200':
          description: Current phase of a known/retried withdrawal id.
          content:
            application/json:
              schema:
                $ref: '#/components/schemas/WithdrawalStatus'
              example:
                wid: '9001'
                status: reserved
        '201':
          description: Freshly accepted withdrawal.
          content:
            application/json:
              schema:
                $ref: '#/components/schemas/WithdrawalStatus'
              example:
                wid: '9001'
                status: requested
        '400':
          $ref: '#/components/responses/BadRequest'
        '401':
          $ref: '#/components/responses/Unauthorized'
        '403':
          $ref: '#/components/responses/MissingScope'
        '404':
          $ref: '#/components/responses/NotFound'
        '409':
          $ref: '#/components/responses/Conflict'
        '422':
          $ref: '#/components/responses/Unprocessable'
        '429':
          $ref: '#/components/responses/RateLimited'
        '503':
          $ref: '#/components/responses/FundingCapacity'
        '504':
          $ref: '#/components/responses/SequencingTimeout'
  /v1/withdrawals/{client_withdrawal_id}:
    get:
      tags:
        - Funding
      operationId: getWithdrawal
      summary: Withdrawal status by client id
      x-required-scope: read
      parameters:
        - name: client_withdrawal_id
          in: path
          required: true
          schema:
            $ref: '#/components/schemas/U64String'
      responses:
        '200':
          description: Current phase of the withdrawal.
          content:
            application/json:
              schema:
                $ref: '#/components/schemas/WithdrawalStatus'
              example:
                wid: '9001'
                status: confirmed
        '401':
          $ref: '#/components/responses/Unauthorized'
        '403':
          $ref: '#/components/responses/MissingScope'
        '404':
          $ref: '#/components/responses/NotFound'
        '429':
          $ref: '#/components/responses/RateLimited'
        '504':
          $ref: '#/components/responses/SequencingTimeout'
components:
  securitySchemes:
    DxAccessKey:
      type: apiKey
      in: header
      name: DX-ACCESS-KEY
      description: The public API key (alphanumeric access_key string).
    DxAccessTimestamp:
      type: apiKey
      in: header
      name: DX-ACCESS-TIMESTAMP
      description: Request timestamp, Unix milliseconds (decimal). ±5000 ms replay window.
    DxAccessSignature:
      type: apiKey
      in: header
      name: DX-ACCESS-SIGNATURE
      description: >
        base64(HMAC-SHA256(secret, "timestamp\nMETHOD\npath?query\nbody")). For
        the WS handshake the body is empty and the path is `/v1/ws`.
  parameters:
    Cursor:
      name: cursor
      in: query
      description: Opaque pagination cursor from a prior response; omit for the first page.
      required: false
      schema:
        type: string
    Limit:
      name: limit
      in: query
      description: Page size.
      required: false
      schema:
        type: integer
        default: 100
        minimum: 1
        maximum: 1000
    MarketIdPath:
      name: market_id
      in: path
      required: true
      schema:
        type: integer
    MarketIdQuery:
      name: market_id
      in: query
      required: false
      schema:
        type: integer
    OrderIdPath:
      name: order_id
      in: path
      required: true
      schema:
        $ref: '#/components/schemas/U64String'
  responses:
    Unauthorized:
      description: Missing or invalid signature (`unauthorized`, origin gateway).
      content:
        application/json:
          schema:
            $ref: '#/components/schemas/ErrorEnvelope'
          example:
            error:
              code: unauthorized
              num: null
              origin: gateway
              message: unauthorized
              details: null
    MissingScope:
      description: >-
        Key lacks the required scope (`missing_scope`), or a risk reject
        (`per_market_limit`, `user_suspended`, `reduce_only_violation`, …).
      content:
        application/json:
          schema:
            $ref: '#/components/schemas/ErrorEnvelope'
          example:
            error:
              code: missing_scope
              num: null
              origin: gateway
              message: missing_scope
              details: null
    RateLimited:
      description: |
        Token bucket empty (`rate_limited`). Retry after
        `error.details.retry_after_ms` milliseconds (decimal string). v1 does
        not emit a `Retry-After` HTTP header.
      content:
        application/json:
          schema:
            $ref: '#/components/schemas/ErrorEnvelope'
          example:
            error:
              code: rate_limited
              num: null
              origin: gateway
              message: rate limited
              details:
                retry_after_ms: '10'
    BadRequest:
      description: >
        Edge validation fault (origin gateway: `unknown_field`,
        `malformed_json`, `schema_violation`, `bad_client_withdrawal_id`,
        `bad_amount`, `bad_destination`) or a core/port validity reject
        (`bad_price_tick`, `bad_qty`, `invalid_decrease`,
        `client_order_id_required`).
      content:
        application/json:
          schema:
            $ref: '#/components/schemas/ErrorEnvelope'
          example:
            error:
              code: unknown_field
              num: null
              origin: gateway
              message: unknown_field
              details: null
    NotFound:
      description: >-
        `market_not_found`, `unknown_order`, `unknown_withdrawal`, or
        `not_found`.
      content:
        application/json:
          schema:
            $ref: '#/components/schemas/ErrorEnvelope'
          example:
            error:
              code: unknown_order
              num: 260
              origin: core
              message: unknown_order
              details: null
    Conflict:
      description: >
        `duplicate_client_order_id`, `stale_client_withdrawal_id`, or a
        market-state reject (`market_closed`, `market_inactive`,
        `exchange_paused`, `trading_paused`, `market_frozen`).
      content:
        application/json:
          schema:
            $ref: '#/components/schemas/ErrorEnvelope'
          example:
            error:
              code: duplicate_client_order_id
              num: 259
              origin: core
              message: duplicate_client_order_id
              details: null
    Unprocessable:
      description: >-
        Funding reject (`insufficient_balance`, `sell_exceeds_position`,
        `max_cost_exceeded`).
      content:
        application/json:
          schema:
            $ref: '#/components/schemas/ErrorEnvelope'
          example:
            error:
              code: insufficient_balance
              num: 257
              origin: core
              message: insufficient_balance
              details: null
    Shed:
      description: Backpressure — no session capacity (`shed`).
      content:
        application/json:
          schema:
            $ref: '#/components/schemas/ErrorEnvelope'
          example:
            error:
              code: shed
              num: null
              origin: gateway
              message: shed
              details: null
    FundingCapacity:
      description: >-
        Funding port at capacity (`funding_capacity`) or order-book full
        (`book_full`).
      content:
        application/json:
          schema:
            $ref: '#/components/schemas/ErrorEnvelope'
          example:
            error:
              code: funding_capacity
              num: null
              origin: port
              message: funding_capacity
              details: null
    SequencingTimeout:
      description: >
        The sequenced outcome did not arrive within the response budget
        (`sequencing_timeout`, origin gateway). For a placement, retry the
        identical request then `GET /v1/portfolio/orders?client_order_id=` — at
        most one order results.
      content:
        application/json:
          schema:
            $ref: '#/components/schemas/ErrorEnvelope'
          example:
            error:
              code: sequencing_timeout
              num: null
              origin: gateway
              message: sequencing_timeout
              details: null
    AdminBadRequest:
      description: >
        Malformed request (`bad_json`, `unknown_field`, `missing_field`,
        `bad_value`) or an authorization-independent reject (`invalid_action`,
        `missing_checker`, `unexpected_checker`, `same_signer`).
      content:
        application/json:
          schema:
            $ref: '#/components/schemas/AdminError'
          example:
            error: unknown_field
    AdminUnauthorized:
      description: '`unknown_operator` (unknown `operator_id`) or `bad_signature`.'
      content:
        application/json:
          schema:
            $ref: '#/components/schemas/AdminError'
          example:
            error: unknown_operator
    AdminForbidden:
      description: '`missing_role` — a signer lacks the role the action requires.'
      content:
        application/json:
          schema:
            $ref: '#/components/schemas/AdminError'
          example:
            error: missing_role
    AdminConflict:
      description: >
        `bad_nonce` — a signer's nonce is zero or not strictly greater than its
        last accepted nonce.
      content:
        application/json:
          schema:
            $ref: '#/components/schemas/AdminError'
          example:
            error: bad_nonce
  schemas:
    U64String:
      type: string
      description: A u64-domain value rendered as a decimal string.
      pattern: ^[0-9]+$
      example: '182390'
    Tick:
      type: integer
      description: Price tick in cents, in the outcome's own space.
      minimum: 1
      maximum: 99
    Side:
      type: string
      enum:
        - buy
        - sell
    Outcome:
      type: string
      enum:
        - 'yes'
        - 'no'
    FillPath:
      type: string
      description: The FILL path flag.
      enum:
        - direct
        - mint
        - merge
    AsOfSeq:
      type: object
      properties:
        as_of_seq:
          allOf:
            - $ref: '#/components/schemas/U64String'
            - description: Stream sequence this answer is current as of.
      required:
        - as_of_seq
    ErrorEnvelope:
      type: object
      description: The uniform error body.
      properties:
        error:
          type: object
          properties:
            code:
              type: string
              description: lowercase snake_case reject/gateway code.
            num:
              type:
                - integer
                - 'null'
              description: >-
                The u16 reject code; non-null iff origin is `core` or `port`
                drawing from the shared registry (funding-port rejects carry a
                null num).
            origin:
              type: string
              enum:
                - core
                - port
                - gateway
            message:
              type: string
            details:
              type:
                - object
                - 'null'
          required:
            - code
            - num
            - origin
            - message
            - details
      required:
        - error
    ExchangeStatus:
      type: object
      properties:
        exchange_active:
          type: boolean
        as_of_seq:
          $ref: '#/components/schemas/U64String'
      required:
        - exchange_active
        - as_of_seq
    ExchangeSchedule:
      type: object
      properties:
        schedule:
          type: array
          items:
            type: object
            properties:
              kind:
                type: string
                enum:
                  - maintenance
              start_ts:
                $ref: '#/components/schemas/U64String'
              end_ts:
                $ref: '#/components/schemas/U64String'
            required:
              - kind
              - start_ts
              - end_ts
      required:
        - schedule
    RetentionLimits:
      type: object
      description: Venue live-listing retention caps.
      properties:
        tape_cap:
          type: integer
          description: Public trade prints kept per market.
        terminal_cap:
          type: integer
          description: Terminal order rows kept per user.
        reject_cap:
          type: integer
          description: Rejected-order rows kept per user.
        fill_cap:
          type: integer
          description: Own-fill rows kept per user.
      required:
        - tape_cap
        - terminal_cap
        - reject_cap
        - fill_cap
    RateBucket:
      type: object
      description: One token bucket.
      properties:
        sustained_rate_per_s:
          type: integer
        burst:
          type: integer
      required:
        - sustained_rate_per_s
        - burst
    AccountLimits:
      type: object
      description: The authenticated key's scopes and limits.
      properties:
        scopes:
          type: array
          items:
            type: string
            enum:
              - read
              - trade
              - withdraw
        read:
          $ref: '#/components/schemas/RateBucket'
        write:
          $ref: '#/components/schemas/RateBucket'
        batch_cap:
          type: integer
        response_budget_ms:
          type: integer
      required:
        - scopes
        - read
        - write
        - batch_cap
        - response_budget_ms
    QueuePosition:
      type: object
      description: Shares resting ahead under price-time priority.
      properties:
        shares_ahead:
          allOf:
            - $ref: '#/components/schemas/U64String'
          description: Sum of remaining size ahead at this order's YES-space level.
        as_of_seq:
          $ref: '#/components/schemas/U64String'
      required:
        - shares_ahead
        - as_of_seq
    DeadmanArmRequest:
      type: object
      additionalProperties: false
      description: Arm / refresh the dead-man switch.
      properties:
        timeout_ms:
          type: integer
          minimum: 1000
          maximum: 60000
          description: >-
            Heartbeat budget in milliseconds. On the first tick at or after now
            + timeout_ms the gateway fires one user-wide MASS_CANCEL and disarms
            (fire-once). Re-arm to refresh the deadline.
      required:
        - timeout_ms
    DeadmanArmResponse:
      type: object
      description: The armed dead-man state.
      properties:
        timeout_ms:
          type: integer
          minimum: 1000
          maximum: 60000
        armed:
          type: boolean
          enum:
            - true
      required:
        - timeout_ms
        - armed
    DeadmanDisarmResponse:
      type: object
      description: The disarmed dead-man state.
      properties:
        armed:
          type: boolean
          enum:
            - false
      required:
        - armed
    Market:
      type: object
      description: A market object.
      properties:
        market_id:
          type: integer
        status:
          type: string
          enum:
            - created
            - trading
            - frozen
            - proposed
            - resolved
            - archived
        title:
          type:
            - string
            - 'null'
          description: projection metadata (non-normative)
        best_yes_bid:
          type:
            - integer
            - 'null'
        best_yes_ask:
          type:
            - integer
            - 'null'
        display_tick:
          type:
            - integer
            - 'null'
        last_tick_yes:
          type:
            - integer
            - 'null'
        volume:
          allOf:
            - $ref: '#/components/schemas/U64String'
          description: cumulative shares traded
        open_interest:
          allOf:
            - $ref: '#/components/schemas/U64String'
          description: outstanding pairs (= pool / $1)
        position_limit:
          type: integer
          description: 0 = unlimited
        fee_schedule_id:
          type: integer
        close_ts:
          type:
            - string
            - 'null'
          description: projection metadata
      required:
        - market_id
        - status
        - title
        - best_yes_bid
        - best_yes_ask
        - display_tick
        - last_tick_yes
        - volume
        - open_interest
        - position_limit
        - fee_schedule_id
        - close_ts
    MarketsPage:
      type: object
      properties:
        markets:
          type: array
          items:
            $ref: '#/components/schemas/Market'
        cursor:
          type: string
        as_of_seq:
          $ref: '#/components/schemas/U64String'
      required:
        - markets
        - cursor
        - as_of_seq
    OrderbookLevel:
      type: array
      description: '[tick, aggregate_qty] — qty is a JSON number (per golden vector).'
      prefixItems:
        - $ref: '#/components/schemas/Tick'
        - type: integer
          description: aggregate shares at the level
      minItems: 2
      maxItems: 2
    Orderbook:
      type: object
      description: Bids only, both sides.
      properties:
        market_id:
          type: integer
        'yes':
          type: array
          items:
            $ref: '#/components/schemas/OrderbookLevel'
          description: YES bids at YES-space ticks
          best-first: null
        'no':
          type: array
          items:
            $ref: '#/components/schemas/OrderbookLevel'
          description: NO bids at NO-space ticks
          best-first: null
        as_of_seq:
          $ref: '#/components/schemas/U64String'
      required:
        - market_id
        - 'yes'
        - 'no'
        - as_of_seq
    Trade:
      type: object
      description: A public tape entry.
      properties:
        seq:
          $ref: '#/components/schemas/U64String'
        ts:
          $ref: '#/components/schemas/U64String'
        market_id:
          type: integer
        tick_yes:
          $ref: '#/components/schemas/Tick'
        qty:
          type: integer
        path:
          $ref: '#/components/schemas/FillPath'
        taker_side:
          $ref: '#/components/schemas/Side'
        taker_outcome:
          $ref: '#/components/schemas/Outcome'
      required:
        - seq
        - ts
        - market_id
        - tick_yes
        - qty
        - path
        - taker_side
        - taker_outcome
    TradesPage:
      type: object
      properties:
        trades:
          type: array
          items:
            $ref: '#/components/schemas/Trade'
        cursor:
          type: string
        as_of_seq:
          $ref: '#/components/schemas/U64String'
      required:
        - trades
        - cursor
        - as_of_seq
    OraclePrice:
      type: object
      description: Live oracle reference price.
      properties:
        price:
          allOf:
            - $ref: '#/components/schemas/U64String'
          description: Reference price @1e8.
        fresh_source_count:
          type: integer
          description: Count of fresh CEX perp sources in the median (u8).
        seq:
          allOf:
            - $ref: '#/components/schemas/U64String'
          description: The producing tick's stream seq.
      required:
        - price
        - fresh_source_count
        - seq
    Round:
      type: object
      description: A round object.
      properties:
        market_id:
          type: integer
        status:
          type: string
          enum:
            - trading
            - frozen
            - proposed
            - settled
            - void
        strike:
          allOf:
            - $ref: '#/components/schemas/U64String'
          description: Open price @1e8.
        settle_price:
          allOf:
            - $ref: '#/components/schemas/U64String'
          description: Freeze price @1e8.
        winner:
          oneOf:
            - $ref: '#/components/schemas/Outcome'
            - type: 'null'
          description: Outcome once decided; null while trading/frozen.
        yes_payout:
          type: integer
          description: u8 cents-per-$1 for YES holders
        fresh_source_count_at_freeze:
          type: integer
          description: u8 count at freeze
        gen:
          type: integer
        open_ts:
          $ref: '#/components/schemas/U64String'
        freeze_ts:
          $ref: '#/components/schemas/U64String'
        resolve_ts:
          $ref: '#/components/schemas/U64String'
        round_number:
          $ref: '#/components/schemas/U64String'
        generation:
          type: integer
          description: market slot generation (order-binding epoch)
        category:
          type: integer
          description: series/category id (0 = unclassified)
        twap_window_secs:
          type: integer
          description: per-series settle TWAP window
      required:
        - market_id
        - status
        - strike
        - settle_price
        - winner
        - yes_payout
        - fresh_source_count_at_freeze
        - gen
        - open_ts
        - freeze_ts
        - resolve_ts
        - round_number
        - generation
        - category
        - twap_window_secs
    RoundsPage:
      type: object
      properties:
        rounds:
          type: array
          items:
            $ref: '#/components/schemas/Round'
        cursor:
          type: string
        as_of_seq:
          $ref: '#/components/schemas/U64String'
      required:
        - rounds
        - cursor
        - as_of_seq
    Balance:
      type: object
      properties:
        available:
          allOf:
            - $ref: '#/components/schemas/U64String'
          description: cents
        reserved:
          allOf:
            - $ref: '#/components/schemas/U64String'
          description: open-buy reservations + pending withdrawals
          cents: null
        as_of_seq:
          $ref: '#/components/schemas/U64String'
      required:
        - available
        - reserved
        - as_of_seq
    Position:
      type: object
      properties:
        market_id:
          type: integer
        outcome:
          $ref: '#/components/schemas/Outcome'
        free:
          type: integer
          description: free shares (REST renders as JSON number)
        reserved_for_sale:
          type: integer
          description: shares reserved against resting sells
      required:
        - market_id
        - outcome
        - free
        - reserved_for_sale
    PositionsPage:
      type: object
      properties:
        positions:
          type: array
          items:
            $ref: '#/components/schemas/Position'
        cursor:
          type: string
        as_of_seq:
          $ref: '#/components/schemas/U64String'
      required:
        - positions
        - cursor
        - as_of_seq
    Fill:
      type: object
      description: A fill from the caller's own order.
      properties:
        seq:
          $ref: '#/components/schemas/U64String'
        ts:
          $ref: '#/components/schemas/U64String'
        market_id:
          type: integer
        order_id:
          $ref: '#/components/schemas/U64String'
        role:
          type: string
          enum:
            - maker
            - taker
        side:
          $ref: '#/components/schemas/Side'
        outcome:
          $ref: '#/components/schemas/Outcome'
        qty:
          type: integer
        tick_yes:
          $ref: '#/components/schemas/Tick'
        path:
          $ref: '#/components/schemas/FillPath'
        fee:
          allOf:
            - $ref: '#/components/schemas/U64String'
          description: cents
      required:
        - seq
        - ts
        - market_id
        - order_id
        - role
        - side
        - outcome
        - qty
        - tick_yes
        - path
        - fee
    FillsPage:
      type: object
      properties:
        fills:
          type: array
          items:
            $ref: '#/components/schemas/Fill'
        cursor:
          type: string
        as_of_seq:
          $ref: '#/components/schemas/U64String'
      required:
        - fills
        - cursor
        - as_of_seq
    Order:
      type: object
      description: An order object — request echo plus lifecycle fields.
      properties:
        order_id:
          oneOf:
            - $ref: '#/components/schemas/U64String'
            - type: 'null'
          description: null until the order reaches the stream
        client_order_id:
          $ref: '#/components/schemas/U64String'
        market_id:
          type: integer
        side:
          $ref: '#/components/schemas/Side'
        outcome:
          $ref: '#/components/schemas/Outcome'
        tick:
          $ref: '#/components/schemas/Tick'
        qty:
          type: integer
        tif:
          type: string
          enum:
            - gtc
            - gtt
            - ioc
            - fok
        expiry_ts:
          allOf:
            - $ref: '#/components/schemas/U64String'
          description: ns; "0" when not gtt
        max_cost:
          allOf:
            - $ref: '#/components/schemas/U64String'
          description: cents; "0" when unset
        group_id:
          type: integer
        flags:
          type: integer
          description: >-
            bitfield: bit0 post_only, bit1 reduce_only, bit2 cancel_on_pause,
            bit3 stp_maker
        status:
          type: string
          enum:
            - resting
            - partially_filled
            - executed
            - canceled
            - expired
            - rejected
        reason:
          type:
            - string
            - 'null'
          description: CANCELED/REJECTED reason name when terminal
        filled_qty:
          type: integer
        remaining_qty:
          type: integer
        created_seq:
          $ref: '#/components/schemas/U64String'
        created_ts:
          $ref: '#/components/schemas/U64String'
        last_update_seq:
          $ref: '#/components/schemas/U64String'
      required:
        - order_id
        - client_order_id
        - market_id
        - side
        - outcome
        - tick
        - qty
        - tif
        - expiry_ts
        - max_cost
        - group_id
        - flags
        - status
        - reason
        - filled_qty
        - remaining_qty
        - created_seq
        - created_ts
        - last_update_seq
    OrdersPage:
      type: object
      properties:
        orders:
          type: array
          items:
            $ref: '#/components/schemas/Order'
        cursor:
          type: string
        as_of_seq:
          $ref: '#/components/schemas/U64String'
      required:
        - orders
        - cursor
        - as_of_seq
    PlaceOrderRequest:
      type: object
      description: Closed schema — unknown field ⇒ 400.
      additionalProperties: false
      properties:
        client_order_id:
          allOf:
            - $ref: '#/components/schemas/U64String'
          description: idempotency key
        market_id:
          type: integer
        side:
          $ref: '#/components/schemas/Side'
        outcome:
          $ref: '#/components/schemas/Outcome'
        type:
          type: string
          enum:
            - limit
            - market
        tick:
          allOf:
            - $ref: '#/components/schemas/Tick'
          description: limit only
        worst_tick:
          allOf:
            - $ref: '#/components/schemas/Tick'
          description: >-
            market only, optional; marketable-limit bound (default 99 buy / 1
            sell)
        qty:
          type: integer
          minimum: 1
        tif:
          type: string
          enum:
            - gtc
            - gtt
            - ioc
            - fok
          description: limit only; market ⇒ forced ioc
        expiry_ts:
          allOf:
            - $ref: '#/components/schemas/U64String'
          description: ns; gtt only
        max_cost:
          allOf:
            - $ref: '#/components/schemas/U64String'
          description: cents; required for market buys
          else optional: null
        group_id:
          type: integer
          default: 0
        post_only:
          type: boolean
          default: false
        reduce_only:
          type: boolean
          default: false
        cancel_on_pause:
          type: boolean
          default: false
        stp_maker:
          type: boolean
          default: false
      required:
        - client_order_id
        - market_id
        - side
        - outcome
        - type
        - qty
    FillBrief:
      type: object
      description: One FILL event in stream order.
      properties:
        tick_yes:
          $ref: '#/components/schemas/Tick'
        qty:
          type: integer
        path:
          $ref: '#/components/schemas/FillPath'
      required:
        - tick_yes
        - qty
        - path
    PlaceOrderResponse:
      type: object
      description: The sequenced placement outcome.
      properties:
        order_id:
          $ref: '#/components/schemas/U64String'
        client_order_id:
          $ref: '#/components/schemas/U64String'
        status:
          type: string
          enum:
            - resting
            - partially_filled
            - executed
            - canceled
        reason:
          type:
            - string
            - 'null'
          description: present when status is canceled
        seq:
          allOf:
            - $ref: '#/components/schemas/U64String'
          description: the NEW_ORDER's stamp
        filled_qty:
          type: integer
        resting_qty:
          type: integer
        fills:
          type: array
          items:
            $ref: '#/components/schemas/FillBrief'
      required:
        - order_id
        - client_order_id
        - status
        - seq
        - filled_qty
        - resting_qty
        - fills
    BatchRequest:
      type: object
      additionalProperties: false
      properties:
        orders:
          type: array
          minItems: 1
          maxItems: 20
          items:
            $ref: '#/components/schemas/PlaceOrderRequest'
      required:
        - orders
    BatchResponse:
      type: array
      description: Per-item, index-aligned with the request.
      items:
        oneOf:
          - $ref: '#/components/schemas/PlaceOrderResponse'
          - $ref: '#/components/schemas/ErrorEnvelope'
    DecreaseRequest:
      type: object
      additionalProperties: false
      properties:
        new_qty:
          type: integer
          minimum: 0
          description: absolute target
      required:
        - new_qty
    DecreaseResult:
      type: object
      properties:
        status:
          type: string
          enum:
            - decreased
        new_qty:
          type: integer
        seq:
          $ref: '#/components/schemas/U64String'
      required:
        - status
        - new_qty
        - seq
    CancelResult:
      type: object
      properties:
        status:
          type: string
          enum:
            - canceled
        canceled_qty:
          type: integer
        seq:
          $ref: '#/components/schemas/U64String'
      required:
        - status
        - canceled_qty
        - seq
    CancelBatchRequest:
      type: object
      additionalProperties: false
      properties:
        order_ids:
          type: array
          maxItems: 20
          items:
            $ref: '#/components/schemas/U64String'
      required:
        - order_ids
    CancelBatchResponse:
      type: object
      description: Aggregate cancel-batch body.
      properties:
        results:
          type: array
          description: Per-item, index-aligned with the request.
          items:
            oneOf:
              - $ref: '#/components/schemas/CancelResult'
              - $ref: '#/components/schemas/ErrorEnvelope'
      required:
        - results
    SeqAck:
      type: object
      description: A bare sequenced-command acknowledgment.
      properties:
        seq:
          $ref: '#/components/schemas/U64String'
      required:
        - seq
    DepositAddress:
      type: object
      properties:
        address:
          type: string
          description: '`0x` + 40 hex.'
          pattern: ^0x[0-9a-fA-F]{40}$
      required:
        - address
    WithdrawalRequest:
      type: object
      description: Closed schema.
      additionalProperties: false
      properties:
        client_withdrawal_id:
          allOf:
            - $ref: '#/components/schemas/U64String'
          description: nonzero, strictly increasing per user (idempotency key)
        amount:
          allOf:
            - $ref: '#/components/schemas/U64String'
          description: nonzero cents
        dest:
          type: string
          description: >-
            exactly 42 chars: lowercase `0x` + 40 hex (either case); mixed-case
            triggers the funding port's EIP-55 checksum verify
          pattern: ^0x[0-9a-fA-F]{40}$
      required:
        - client_withdrawal_id
        - amount
        - dest
    WithdrawalStatus:
      type: object
      properties:
        wid:
          $ref: '#/components/schemas/U64String'
        status:
          type: string
          enum:
            - requested
            - reserved
            - confirmed
            - failed
            - alarm
      required:
        - wid
        - status
    AdminSigner:
      type: object
      description: One operator's signature over the request.
      additionalProperties: false
      properties:
        operator_id:
          type: integer
        nonce:
          allOf:
            - $ref: '#/components/schemas/U64String'
          description: Strictly greater than this signer's last accepted nonce.
        signature:
          type: string
          description: 64 hex chars — the 32 raw HMAC-SHA256 bytes.
          pattern: ^[0-9a-fA-F]{64}$
      required:
        - operator_id
        - nonce
        - signature
    AdminMarketControlRequest:
      type: object
      description: Two `MARKETS` signers.
      additionalProperties: false
      properties:
        action:
          type: string
          enum:
            - market_control
        market_id:
          type: integer
        market_action:
          type: string
          enum:
            - create
            - open_trading
            - freeze
            - resolve
            - archive
        primary:
          $ref: '#/components/schemas/AdminSigner'
        checker:
          $ref: '#/components/schemas/AdminSigner'
      required:
        - action
        - market_id
        - market_action
        - primary
        - checker
    AdminMarketRefdataRequest:
      type: object
      description: Two `MARKETS` signers.
      additionalProperties: false
      properties:
        action:
          type: string
          enum:
            - market_refdata
        market_id:
          type: integer
        position_limit:
          type: integer
          description: 0 = unlimited
        fee_schedule_id:
          type: integer
        review_window_secs:
          type: integer
        primary:
          $ref: '#/components/schemas/AdminSigner'
        checker:
          $ref: '#/components/schemas/AdminSigner'
      required:
        - action
        - market_id
        - position_limit
        - fee_schedule_id
        - review_window_secs
        - primary
        - checker
    AdminFeeScheduleRequest:
      type: object
      description: Two `MARKETS` signers.
      additionalProperties: false
      properties:
        action:
          type: string
          enum:
            - fee_schedule
        schedule_id:
          type: integer
          description: nonzero
        maker_rate:
          type: integer
        taker_rate:
          type: integer
        primary:
          $ref: '#/components/schemas/AdminSigner'
        checker:
          $ref: '#/components/schemas/AdminSigner'
      required:
        - action
        - schedule_id
        - maker_rate
        - taker_rate
        - primary
        - checker
    AdminUserControlRequest:
      type: object
      description: Two `RISK` signers.
      additionalProperties: false
      properties:
        action:
          type: string
          enum:
            - user_control
        user_id:
          type: integer
        user_action:
          type: string
          enum:
            - suspend
            - reinstate
        primary:
          $ref: '#/components/schemas/AdminSigner'
        checker:
          $ref: '#/components/schemas/AdminSigner'
      required:
        - action
        - user_id
        - user_action
        - primary
        - checker
    AdminTradingPauseRequest:
      type: object
      description: >
        One `EMERGENCY` signer for `pause` (`checker` MUST be absent); two
        `RISK` signers for `resume` (`checker` MUST be present) — : an absent
        `checker` where one is required is `missing_checker`, a present one
        where none is expected is `unexpected_checker`.
      additionalProperties: false
      properties:
        action:
          type: string
          enum:
            - trading_pause
        market_id:
          type: integer
        pause_action:
          type: string
          enum:
            - pause
            - resume
        primary:
          $ref: '#/components/schemas/AdminSigner'
        checker:
          $ref: '#/components/schemas/AdminSigner'
      required:
        - action
        - market_id
        - pause_action
        - primary
    AdminResolutionRequest:
      type: object
      description: >
        One `RESOLUTION` signer; `checker` MUST be absent — the resolution peer
        itself enforces the separate proposer/checker rule and review window.
      additionalProperties: false
      properties:
        action:
          type: string
          enum:
            - resolution_approve
            - resolution_contest
        market_id:
          type: integer
        proposal_gen:
          type: integer
        primary:
          $ref: '#/components/schemas/AdminSigner'
        checker:
          $ref: '#/components/schemas/AdminSigner'
      required:
        - action
        - market_id
        - proposal_gen
        - primary
    AdminRequest:
      description: Closed schema — unknown field ⇒ 400.
      oneOf:
        - $ref: '#/components/schemas/AdminMarketControlRequest'
        - $ref: '#/components/schemas/AdminMarketRefdataRequest'
        - $ref: '#/components/schemas/AdminFeeScheduleRequest'
        - $ref: '#/components/schemas/AdminUserControlRequest'
        - $ref: '#/components/schemas/AdminTradingPauseRequest'
        - $ref: '#/components/schemas/AdminResolutionRequest'
      discriminator:
        propertyName: action
        mapping:
          market_control: '#/components/schemas/AdminMarketControlRequest'
          market_refdata: '#/components/schemas/AdminMarketRefdataRequest'
          fee_schedule: '#/components/schemas/AdminFeeScheduleRequest'
          user_control: '#/components/schemas/AdminUserControlRequest'
          trading_pause: '#/components/schemas/AdminTradingPauseRequest'
          resolution_approve: '#/components/schemas/AdminResolutionRequest'
          resolution_contest: '#/components/schemas/AdminResolutionRequest'
    AdminAccepted:
      type: object
      description: >
        Authorization accepted. `202`, not `200` — the action exists only once
        the queued frame returns on the sequenced stream, which this body does
        not report.
      properties:
        status:
          type: string
          enum:
            - accepted
        primary_id:
          type: integer
        checker_id:
          type: integer
          description: absent when the action needed no checker
      required:
        - status
        - primary_id
    AdminError:
      type: object
      description: >
        The admin lane's error body — a bare code string, NOT the trading lane's
        `ErrorEnvelope` above.
      properties:
        error:
          type: string
      required:
        - error
