openapi: 3.0.0
info:
  title: Broker REST API (v1)
  version: 1.0.0
  description: |
    Broker-facing JSON REST API. All URIs are prefixed with /v1.
    Notes:
      - Every state changing request (POST / PATCH / DELETE) REQUIRES Idempotency-Key header (stable across retries).
servers:
  - url: https://api.example.com/v1
    description: Production
  - url: https://sandbox.api.example.com/v1
    description: Sandbox
security:
  - apiKeyAuth: []
tags:
  - name: Orders
  - name: Positions
  - name: Payouts
  - name: Events
  - name: Markets
paths:
  /trading/accounts/{account_id}/orders:
    post:
      tags:
        - Orders
      summary: Create new order
      operationId: createOrder
      parameters:
        - $ref: '#/components/parameters/AccountId'
        - $ref: '#/components/parameters/AccessKey'
        - $ref: '#/components/parameters/AccessSignature'
        - $ref: '#/components/parameters/AccessTimestamp'
        - $ref: '#/components/parameters/IdempotencyKey'
        - $ref: '#/components/parameters/CorrelationId'
      requestBody:
        required: true
        content:
          application/json:
            schema:
              $ref: '#/components/schemas/NewOrderRequest'
            examples:
              limit:
                value:
                  market_id: btcusdt
                  side: BUY
                  type: LIMIT
                  tif: GTC
                  price:
                    units: 25000
                    nanos: 0
                  qty:
                    units: 1
                    nanos: 0
                  client_order_id: cli-ord-12345
      responses:
        '201':
          description: Order creation command accepted
          content:
            application/json:
              schema:
                $ref: '#/components/schemas/CommandAck'
        '202':
          description: Idempotent replay (command already accepted)
          content:
            application/json:
              schema:
                $ref: '#/components/schemas/CommandAck'
        '409':
          $ref: '#/components/responses/IdempotencyMismatch'
        '422':
          $ref: '#/components/responses/ValidationError'
        '429':
          $ref: '#/components/responses/RateLimited'
        '503':
          $ref: '#/components/responses/ServiceUnavailable'
      security:
        - apiKeyAuth: []
    get:
      tags:
        - Orders
      summary: List orders for account (filtered & paginated)
      operationId: listOrders
      parameters:
        - $ref: '#/components/parameters/AccountId'
        - $ref: '#/components/parameters/AccessKey'
        - $ref: '#/components/parameters/AccessSignature'
        - $ref: '#/components/parameters/AccessTimestamp'
        - in: query
          name: market_id
          required: true
          schema:
            type: string
        - in: query
          name: event_id
          required: false
          schema:
            type: string
        - in: query
          name: status
          required: false
          schema:
            type: string
            enum:
              - NEW
              - ACTIVE
              - PARTIALLY_FILLED
              - FILLED
              - CANCELLED
              - EXPIRED
        - in: query
          name: limit
          schema:
            type: integer
            minimum: 1
            maximum: 100
            default: 50
        - in: query
          name: cursor
          schema:
            type: string
            description: Opaque pagination cursor
      responses:
        '200':
          description: Page of orders
          content:
            application/json:
              schema:
                $ref: '#/components/schemas/OrderList'
        '400':
          $ref: '#/components/responses/ValidationError'
        '503':
          $ref: '#/components/responses/ServiceUnavailable'
      security:
        - apiKeyAuth: []
    patch:
      tags:
        - Orders
      summary: Amend (modify) order
      description: |
        Modify order parameters (Price/Qty).
          - **Targeting:** Requires `market_id` and either `order_id` OR `client_order_id`.
          - **Stable Identity:** `client_order_id` is immutable and used only for targeting.
          - **Priority Logic:**
              * price change OR qty increase → priority reset (order repositioned)
              * qty decrease (and unchanged price) → priority preserved
          - **Idempotency:** Guaranteed by `Idempotency-Key`.
      operationId: amendOrder
      parameters:
        - $ref: '#/components/parameters/AccountId'
        - $ref: '#/components/parameters/AccessKey'
        - $ref: '#/components/parameters/AccessSignature'
        - $ref: '#/components/parameters/AccessTimestamp'
        - $ref: '#/components/parameters/IdempotencyKey'
        - $ref: '#/components/parameters/CorrelationId'
      requestBody:
        required: true
        content:
          application/json:
            schema:
              $ref: '#/components/schemas/AmendOrderRequest'
      responses:
        '200':
          description: Amend command accepted
          content:
            application/json:
              schema:
                $ref: '#/components/schemas/CommandAck'
        '202':
          description: Idempotent replay
          content:
            application/json:
              schema:
                $ref: '#/components/schemas/CommandAck'
        '404':
          $ref: '#/components/responses/NotFound'
        '409':
          $ref: '#/components/responses/IdempotencyMismatch'
        '422':
          $ref: '#/components/responses/ValidationError'
        '503':
          $ref: '#/components/responses/ServiceUnavailable'
      security:
        - apiKeyAuth: []
    delete:
      tags:
        - Orders
      summary: Cancel order (by order_id or client_order_id)
      description: Requires market_id in body; if both order_id and client_order_id are provided, order_id takes precedence.
      operationId: cancelOrderKey
      parameters:
        - $ref: '#/components/parameters/AccountId'
        - $ref: '#/components/parameters/AccessKey'
        - $ref: '#/components/parameters/AccessSignature'
        - $ref: '#/components/parameters/AccessTimestamp'
        - $ref: '#/components/parameters/IdempotencyKey'
        - $ref: '#/components/parameters/CorrelationId'
      requestBody:
        required: true
        content:
          application/json:
            schema:
              $ref: '#/components/schemas/CancelOrderRequest'
      responses:
        '201':
          description: Cancellation command accepted
          content:
            application/json:
              schema:
                $ref: '#/components/schemas/CommandAck'
        '202':
          description: Idempotent replay (already accepted)
          content:
            application/json:
              schema:
                $ref: '#/components/schemas/CommandAck'
        '404':
          $ref: '#/components/responses/NotFound'
        '409':
          $ref: '#/components/responses/IdempotencyMismatch'
        '422':
          $ref: '#/components/responses/ValidationError'
        '503':
          $ref: '#/components/responses/ServiceUnavailable'
      security:
        - apiKeyAuth: []
  /trading/accounts/{account_id}/orders/{order_id}:
    get:
      tags:
        - Orders
      summary: Get order by order_id
      operationId: getOrder
      parameters:
        - $ref: '#/components/parameters/AccountId'
        - $ref: '#/components/parameters/OrderId'
        - $ref: '#/components/parameters/AccessKey'
        - $ref: '#/components/parameters/AccessSignature'
        - $ref: '#/components/parameters/AccessTimestamp'
      responses:
        '200':
          description: Order view
          content:
            application/json:
              schema:
                $ref: '#/components/schemas/OrderView'
        '404':
          $ref: '#/components/responses/NotFound'
        '503':
          $ref: '#/components/responses/ServiceUnavailable'
      security:
        - apiKeyAuth: []
  /trading/accounts/{account_id}/positions:
    get:
      tags:
        - Positions
      summary: List positions
      operationId: listPositions
      parameters:
        - $ref: '#/components/parameters/AccountId'
        - $ref: '#/components/parameters/AccessKey'
        - $ref: '#/components/parameters/AccessSignature'
        - $ref: '#/components/parameters/AccessTimestamp'
        - in: query
          name: market_id
          required: false
          schema:
            type: string
        - in: query
          name: event_id
          required: false
          schema:
            type: string
        - in: query
          name: status
          description: Filter by position status. OPEN (default) returns only non-zero qty; ALL returns settled/closed positions as well.
          required: false
          schema:
            type: string
            enum:
              - OPEN
              - ALL
            default: OPEN
        - in: query
          name: presentation
          description: |
            Presentation mode for positions.
            - `dual`: returns separate yes_qty and no_qty.
            - `net`: returns a single net quantity (yes_qty - no_qty).
          required: false
          schema:
            type: string
            enum:
              - dual
              - net
            default: net
        - in: query
          name: cursor
          required: false
          schema:
            type: string
        - in: query
          name: limit
          required: false
          schema:
            type: integer
            minimum: 1
            maximum: 100
            default: 50
      responses:
        '200':
          description: Position list
          content:
            application/json:
              schema:
                $ref: '#/components/schemas/PositionList'
        '400':
          $ref: '#/components/responses/ValidationError'
        '503':
          $ref: '#/components/responses/ServiceUnavailable'
  /trading/accounts/{account_id}/merge:
    post:
      tags:
        - Positions
      summary: Manual merge YES/NO positions
      description: |
        Annihilate equal amounts of YES and NO tokens to release capital.
        The `merge_id` is deterministically generated from the request fingerprint (`cmd_id`).
      operationId: mergePositions
      parameters:
        - $ref: '#/components/parameters/AccountId'
        - $ref: '#/components/parameters/AccessKey'
        - $ref: '#/components/parameters/AccessSignature'
        - $ref: '#/components/parameters/AccessTimestamp'
        - $ref: '#/components/parameters/IdempotencyKey'
      requestBody:
        required: true
        content:
          application/json:
            schema:
              type: object
              required:
                - market_id
                - qty
                - client_ref_id
              properties:
                market_id:
                  type: string
                qty:
                  $ref: '#/components/schemas/Decimal'
                client_ref_id:
                  type: string
                  maxLength: 64
                  pattern: ^[A-Za-z0-9._~-]{1,64}$
                  description: Client-supplied identifier for correlation (client_merge_id).
      responses:
        '202':
          description: Merge command accepted
          content:
            application/json:
              schema:
                type: object
                required:
                  - ref_id
                  - client_ref_id
                properties:
                  ref_id:
                    type: string
                    description: System fingerprint of the request (cmd_id). Use for correlation with Accounting Stream.
                  client_ref_id:
                    type: string
                    description: The client-supplied reference ID for correlation.
        '409':
          $ref: '#/components/responses/IdempotencyMismatch'
        '422':
          $ref: '#/components/responses/ValidationError'
        '503':
          $ref: '#/components/responses/ServiceUnavailable'
      security:
        - apiKeyAuth: []
  /trading/accounts/{account_id}/payouts:
    get:
      tags:
        - Payouts
      summary: List settlement payouts for account
      operationId: listPayouts
      parameters:
        - $ref: '#/components/parameters/AccountId'
        - $ref: '#/components/parameters/AccessKey'
        - $ref: '#/components/parameters/AccessSignature'
        - $ref: '#/components/parameters/AccessTimestamp'
        - in: query
          name: market_id
          required: false
          schema:
            type: string
            description: Filter payouts by specific market ID
        - in: query
          name: event_id
          required: false
          schema:
            type: string
            description: Filter payouts by specific event ID
        - in: query
          name: cursor
          required: false
          schema:
            type: string
        - in: query
          name: limit
          required: false
          schema:
            type: integer
            minimum: 1
            maximum: 100
            default: 50
      responses:
        '200':
          description: Payout list
          content:
            application/json:
              schema:
                $ref: '#/components/schemas/PayoutList'
        '400':
          $ref: '#/components/responses/ValidationError'
        '503':
          $ref: '#/components/responses/ServiceUnavailable'
  /events:
    get:
      tags:
        - Events
      summary: List events
      operationId: listEvents
      parameters:
        - $ref: '#/components/parameters/AccessKey'
        - $ref: '#/components/parameters/AccessSignature'
        - $ref: '#/components/parameters/AccessTimestamp'
        - in: query
          name: limit
          required: false
          schema:
            type: integer
            minimum: 1
            maximum: 100
            default: 50
        - in: query
          name: cursor
          required: false
          schema:
            type: string
            description: Opaque pagination cursor
        - in: query
          name: status
          required: false
          schema:
            $ref: '#/components/schemas/EventStatus'
        - $ref: '#/components/parameters/Lang'
      responses:
        '200':
          description: Page of events
          content:
            application/json:
              schema:
                $ref: '#/components/schemas/EventList'
        '400':
          $ref: '#/components/responses/ValidationError'
        '404':
          $ref: '#/components/responses/NotFound'
        '503':
          $ref: '#/components/responses/ServiceUnavailable'
      security:
        - apiKeyAuth: []
  /events/{event_id}:
    get:
      tags:
        - Events
      summary: Get event by ID
      operationId: getEvent
      parameters:
        - $ref: '#/components/parameters/EventId'
        - $ref: '#/components/parameters/AccessKey'
        - $ref: '#/components/parameters/AccessSignature'
        - $ref: '#/components/parameters/AccessTimestamp'
        - $ref: '#/components/parameters/Lang'
      responses:
        '200':
          description: Event details
          content:
            application/json:
              schema:
                $ref: '#/components/schemas/Event'
        '400':
          $ref: '#/components/responses/ValidationError'
        '404':
          $ref: '#/components/responses/NotFound'
        '503':
          $ref: '#/components/responses/ServiceUnavailable'
      security:
        - apiKeyAuth: []
  /markets:
    get:
      tags:
        - Markets
      summary: List markets
      operationId: listMarkets
      description: |
        List markets with optional filtering and pagination.

        Timestamp filters are mutually exclusive. Only ONE set of timestamp filters can be used per request:
        - `min_created_ts`, `max_created_ts` – filter by creation time;
        - `min_close_ts`, `max_close_ts` – filter by close time;
        - `min_settled_ts`, `max_settled_ts` – filter by settlement time.
        Using multiple timestamp filter sets (e.g., both created and closed) will result in 400 Bad Request
      parameters:
        - $ref: '#/components/parameters/AccessKey'
        - $ref: '#/components/parameters/AccessSignature'
        - $ref: '#/components/parameters/AccessTimestamp'
        - in: query
          name: limit
          required: false
          schema:
            type: integer
            minimum: 1
            maximum: 100
            default: 50
        - in: query
          name: cursor
          required: false
          schema:
            type: string
            description: Opaque pagination cursor
        - in: query
          name: event_id
          required: false
          schema:
            type: string
        - in: query
          name: min_created_ts
          required: false
          description: Minimum creation timestamp (Unix milliseconds).
          schema:
            type: integer
            format: int64
        - in: query
          name: max_created_ts
          required: false
          description: Maximum creation timestamp (Unix milliseconds).
          schema:
            type: integer
            format: int64
        - in: query
          name: min_close_ts
          required: false
          description: Minimum close timestamp (Unix milliseconds).
          schema:
            type: integer
            format: int64
        - in: query
          name: max_close_ts
          required: false
          description: Maximum close timestamp (Unix milliseconds).
          schema:
            type: integer
            format: int64
        - in: query
          name: min_settled_ts
          required: false
          description: Minimum settlement timestamp (Unix milliseconds).
          schema:
            type: integer
            format: int64
        - in: query
          name: max_settled_ts
          required: false
          description: Maximum settlement timestamp (Unix milliseconds).
          schema:
            type: integer
            format: int64
        - in: query
          name: status
          required: false
          schema:
            type: string
            enum:
              - unopened
              - pre-open
              - active
              - suspended
              - closed
              - settled
        - in: query
          name: market_ids
          required: false
          schema:
            type: string
            description: Comma-separated list of market IDs
        - $ref: '#/components/parameters/Lang'
      responses:
        '200':
          description: Page of markets
          content:
            application/json:
              schema:
                $ref: '#/components/schemas/MarketList'
        '400':
          $ref: '#/components/responses/ValidationError'
        '404':
          $ref: '#/components/responses/NotFound'
        '503':
          $ref: '#/components/responses/ServiceUnavailable'
      security:
        - apiKeyAuth: []
  /markets/{market_id}:
    get:
      tags:
        - Markets
      summary: Get market by ID
      operationId: getMarket
      parameters:
        - $ref: '#/components/parameters/MarketId'
        - $ref: '#/components/parameters/AccessKey'
        - $ref: '#/components/parameters/AccessSignature'
        - $ref: '#/components/parameters/AccessTimestamp'
        - $ref: '#/components/parameters/Lang'
      responses:
        '200':
          description: Market details
          content:
            application/json:
              schema:
                $ref: '#/components/schemas/Market'
        '400':
          $ref: '#/components/responses/ValidationError'
        '404':
          $ref: '#/components/responses/NotFound'
        '503':
          $ref: '#/components/responses/ServiceUnavailable'
      security:
        - apiKeyAuth: []
components:
  securitySchemes:
    apiKeyAuth:
      type: apiKey
      in: header
      name: PRE-ACCESS-KEY
      description: |
        Keyed BLAKE2b-256 authentication. Required headers:
        - PRE-ACCESS-KEY: broker access key identifier (unique API key)
        - PRE-ACCESS-SIGNATURE: Keyed BLAKE2b-256 hex (lowercase, 64 chars)
        - PRE-ACCESS-TIMESTAMP: RFC3339 timestamp with milliseconds (e.g., 2025-10-30T06:00:34.075Z)

        Signature is computed over (each line ends with \n):
        1. upper(request_method) + \n
        2. request_uri (path + query) + \n
        3. timestamp (from PRE-ACCESS-TIMESTAMP) + \n
        4. idempotency_key (from Idempotency-Key header) + \n
        5. broker_id + \n
        6. access_key (from PRE-ACCESS-KEY) + \n
        7. body_digest (BLAKE2b-256 hex lowercase of request body bytes, 64 chars)

        Keyed BLAKE2b-256 is computed using secret_key associated with the access_key.
  responses:
    NotFound:
      description: Resource not found
      content:
        application/json:
          schema:
            $ref: '#/components/schemas/Error'
    ValidationError:
      description: Validation / semantic error
      content:
        application/json:
          schema:
            $ref: '#/components/schemas/Error'
    IdempotencyMismatch:
      description: Idempotency-Key reused with different payload
      content:
        application/json:
          schema:
            $ref: '#/components/schemas/Error'
    RateLimited:
      description: Rate limit exceeded
      headers:
        Retry-After:
          schema:
            type: integer
          description: Seconds until new window
      content:
        application/json:
          schema:
            $ref: '#/components/schemas/Error'
    ServiceUnavailable:
      description: Backpressure / temporary unavailability
      content:
        application/json:
          schema:
            $ref: '#/components/schemas/Error'
  schemas:
    CommandAck:
      type: object
      description: Confirmation of command acceptance. Contains at least one order identifier.
      anyOf:
        - required:
            - order_id
        - required:
            - client_order_id
      properties:
        order_id:
          type: string
          format: uuid
          description: Internal Order ID (if known or generated).
        client_order_id:
          type: string
          description: Client-supplied identifier (if provided).
    NewOrderRequest:
      type: object
      description: |
        Create a new order.

        **Trading Logic (Binary Markets):**
        The system uses a Single Order Book (YES contract).
        - `token=YES`: Standard behavior. `BUY` -> Long YES, `SELL` -> Short YES.
        - `token=NO`: Inverted behavior. `BUY NO` is executed as `SELL YES` (Short). `SELL NO` is executed as `BUY YES` (Close Short).
        - **Positions:** Net position is stored as a signed value. Positive = YES held, Negative = NO held.

        **Constraints:**
          - **LIMIT:** `price` and `qty` are REQUIRED.
          - **MARKET BUY:** `volume` is REQUIRED. `price` and `qty` are FORBIDDEN.
          - **MARKET SELL:** `qty` is REQUIRED. `price` and `volume` are FORBIDDEN.
        Violation of these constraints leads to 422 ValidationError.
        client_order_id: MUST be unique per broker_id across all active (non-REJECTED terminal) orders; length 1..64; charset recommended [A-Za-z0-9._~-].
      required:
        - market_id
        - side
        - type
        - tif
        - client_order_id
        - token
      properties:
        market_id:
          type: string
        side:
          $ref: '#/components/schemas/OrderSide'
        type:
          $ref: '#/components/schemas/OrderType'
        tif:
          $ref: '#/components/schemas/OrderTIF'
        token:
          $ref: '#/components/schemas/Token'
        price:
          description: Required for LIMIT orders. Forbidden for MARKET orders.
          allOf:
            - $ref: '#/components/schemas/Decimal'
        qty:
          description: Required for LIMIT and MARKET SELL orders. Forbidden for MARKET BUY.
          allOf:
            - $ref: '#/components/schemas/Decimal'
        volume:
          description: Required for MARKET BUY orders (total budget). Forbidden for MARKET SELL.
          allOf:
            - $ref: '#/components/schemas/Decimal'
        client_order_id:
          type: string
          maxLength: 64
          pattern: ^[A-Za-z0-9._~-]{1,64}$
          description: |
            Client-supplied identifier. Must be unique per broker_id.
            Reuse of the same ID may lead to unpredictable behavior when using client_order_id for lookups or modifications (e.g., affecting only the latest or an arbitrary order).
        auto_merge:
          type: boolean
          default: false
          description: Automatically merge YES and NO tokens for this account if they accumulate after this trade.
    AmendOrderRequest:
      type: object
      required:
        - market_id
      description: Must provide either order_id OR client_order_id to identify the order.
      properties:
        market_id:
          type: string
          description: Required for routing
        order_id:
          type: string
          format: uuid
          description: Target order ID (preferred)
        client_order_id:
          type: string
          description: Target client order ID (fallback)
        price:
          allOf:
            - $ref: '#/components/schemas/Decimal'
        qty:
          allOf:
            - $ref: '#/components/schemas/Decimal'
    CancelOrderRequest:
      type: object
      additionalProperties: false
      description: Provide market_id and either order_id or client_order_id. If both are provided, order_id takes precedence.
      required:
        - market_id
      oneOf:
        - required:
            - order_id
        - required:
            - client_order_id
      properties:
        market_id:
          type: string
        order_id:
          type: string
          format: uuid
          description: If both order_id and client_order_id are provided, order_id takes precedence
        client_order_id:
          type: string
          maxLength: 64
          pattern: ^[A-Za-z0-9._~-]{1,64}$
          description: Alternative client-supplied identifier (unique per broker_id)
    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
    OrderList:
      type: object
      required:
        - items
      properties:
        items:
          type: array
          items:
            $ref: '#/components/schemas/OrderView'
        next_cursor:
          type: string
          nullable: true
    DualPosition:
      type: object
      required:
        - account_id
        - market_id
        - yes_qty
        - no_qty
        - yes_reserved
        - no_reserved
        - realized_pnl
        - fees_paid
        - last_update_wall_time_ns
      properties:
        account_id:
          type: string
        market_id:
          type: string
        yes_qty:
          description: Balance in YES contracts
          allOf:
            - $ref: '#/components/schemas/Decimal'
        no_qty:
          description: Balance in NO contracts
          allOf:
            - $ref: '#/components/schemas/Decimal'
        yes_reserved:
          description: YES contracts reserved in active limit orders
          allOf:
            - $ref: '#/components/schemas/Decimal'
        no_reserved:
          description: NO contracts reserved in active limit orders
          allOf:
            - $ref: '#/components/schemas/Decimal'
        realized_pnl:
          description: Realized profit/loss from closed trades, merges AND settlement payouts
          allOf:
            - $ref: '#/components/schemas/Decimal'
        fees_paid:
          description: Total fees paid on this market
          allOf:
            - $ref: '#/components/schemas/Decimal'
        last_update_wall_time_ns:
          type: integer
          format: int64
        last_update_engine_seq:
          type: integer
          format: int64
    NetPosition:
      type: object
      required:
        - account_id
        - market_id
        - qty
        - yes_reserved
        - no_reserved
        - realized_pnl
        - fees_paid
        - last_update_wall_time_ns
      properties:
        account_id:
          type: string
        market_id:
          type: string
        qty:
          description: Net position (yes_qty - no_qty).
          allOf:
            - $ref: '#/components/schemas/Decimal'
        yes_reserved:
          description: YES contracts reserved in active limit orders
          allOf:
            - $ref: '#/components/schemas/Decimal'
        no_reserved:
          description: NO contracts reserved in active limit orders
          allOf:
            - $ref: '#/components/schemas/Decimal'
        realized_pnl:
          description: Realized profit/loss from closed trades, merges AND settlement payouts
          allOf:
            - $ref: '#/components/schemas/Decimal'
        fees_paid:
          description: Total fees paid on this market
          allOf:
            - $ref: '#/components/schemas/Decimal'
        last_update_wall_time_ns:
          type: integer
          format: int64
        last_update_engine_seq:
          type: integer
          format: int64
    PositionList:
      oneOf:
        - $ref: '#/components/schemas/DualPositionList'
        - $ref: '#/components/schemas/NetPositionList'
    DualPositionList:
      type: object
      required:
        - items
      properties:
        items:
          type: array
          items:
            $ref: '#/components/schemas/DualPosition'
        next_cursor:
          type: string
          nullable: true
    NetPositionList:
      type: object
      required:
        - items
      properties:
        items:
          type: array
          items:
            $ref: '#/components/schemas/NetPosition'
        next_cursor:
          type: string
          nullable: true
    Payout:
      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
          description: Identifier of the settled market
        account_id:
          type: string
          description: Account identifier
        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
          description: UTC timestamp (nanoseconds) when the settlement was recorded
      examples:
        yes_win_long_position:
          summary: 'Example: YES wins, user had LONG position'
          value:
            market_id: TRUMP-WIN-2024
            account_id: broker-client-1
            qty_held_at_settlement:
              units: 10
              nanos: 0
            settlement_price:
              units: 1
              nanos: 0
            payout_amount:
              units: 10
              nanos: 0
            settled_at_wall_time_ns: 1701388800000000000
        no_win_long_position:
          summary: 'Example: NO wins, user had LONG position'
          value:
            market_id: TRUMP-WIN-2024
            account_id: broker-client-1
            qty_held_at_settlement:
              units: 10
              nanos: 0
            settlement_price:
              units: 0
              nanos: 0
            payout_amount:
              units: 0
              nanos: 0
            settled_at_wall_time_ns: 1701388800000000000
        yes_win_short_position:
          summary: 'Example: YES wins, user had SHORT position (bought NO)'
          value:
            market_id: TRUMP-WIN-2024
            account_id: broker-client-2
            qty_held_at_settlement:
              units: -5
              nanos: 0
            settlement_price:
              units: 1
              nanos: 0
            payout_amount:
              units: 0
              nanos: 0
            settled_at_wall_time_ns: 1701388800000000000
        no_win_short_position:
          summary: 'Example: NO wins, user had SHORT position (bought NO)'
          value:
            market_id: TRUMP-WIN-2024
            account_id: broker-client-2
            qty_held_at_settlement:
              units: -5
              nanos: 0
            settlement_price:
              units: 0
              nanos: 0
            payout_amount:
              units: 5
              nanos: 0
            settled_at_wall_time_ns: 1701388800000000000
    PayoutList:
      type: object
      required:
        - items
      properties:
        items:
          type: array
          items:
            $ref: '#/components/schemas/Payout'
        next_cursor:
          type: string
          nullable: true
    Event:
      type: object
      required:
        - event_id
        - title
        - series_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.
        rules_primary:
          $ref: '#/components/schemas/LocalizedText'
        category:
          type: string
        image_url:
          type: string
        series:
          $ref: '#/components/schemas/EventSeries'
        status:
          $ref: '#/components/schemas/EventStatus'
        void_reason:
          type: string
        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:
            $ref: '#/components/schemas/Market'
    EventList:
      type: object
      required:
        - events
      properties:
        events:
          type: array
          items:
            $ref: '#/components/schemas/Event'
        cursor:
          type: string
          nullable: true
    Market:
      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
        event_id:
          type: string
        market_index:
          type: integer
          description: Index of the market within the event (e.g., 0-5 for 6 markets)
        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_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
          description: The amount of time after determination that the market settles
        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
          description: Determines how the market strike is defined and evaluated
        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
        expiration_value:
          type: string
          nullable: true
          description: 'The asset value that was used for determination and settlement. Not necessarily a price, and not always numeric: it carries whatever value the settlement source publishes for the outcome.'
        settlement_price:
          allOf:
            - $ref: '#/components/schemas/Decimal'
          nullable: true
          description: Final settlement price of the YES contract
    MarketList:
      type: object
      required:
        - markets
      properties:
        markets:
          type: array
          items:
            $ref: '#/components/schemas/Market'
        cursor:
          type: string
          nullable: true
    Error:
      type: object
      required:
        - error_code
        - message
      properties:
        error_code:
          type: string
        message:
          type: string
        details:
          type: object
          additionalProperties: true
    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
    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
    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.
    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
    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
  parameters:
    AccountId:
      name: account_id
      in: path
      required: true
      schema:
        type: string
        maxLength: 128
        pattern: ^[A-Za-z0-9._~-]{1,128}$
      description: Broker-supplied account identifier; length 1..128; charset [A-Za-z0-9._~-].
    AccessKey:
      name: PRE-ACCESS-KEY
      in: header
      required: true
      schema:
        type: string
        pattern: ^[A-Za-z0-9._~-]{1,64}$
      description: Broker access key identifier.
    AccessSignature:
      name: PRE-ACCESS-SIGNATURE
      in: header
      required: true
      schema:
        type: string
        pattern: ^[0-9a-f]{64}$
      description: Keyed BLAKE2b-256 signature (hex lowercase, 64 characters).
    AccessTimestamp:
      name: PRE-ACCESS-TIMESTAMP
      in: header
      required: true
      schema:
        type: string
        format: date-time
      description: RFC3339 timestamp with milliseconds (e.g., 2025-10-30T06:00:34.075Z). Must be within clock_skew_tolerance (default 5m) of server time.
    IdempotencyKey:
      name: Idempotency-Key
      in: header
      required: true
      schema:
        type: string
        maxLength: 64
        pattern: ^[A-Za-z0-9._~-]{1,64}$
      description: |
        Stable idempotency token reused on retries. Reuse with identical payload => 202. Reuse with different payload => 409 idempotency_key_mismatch.
    CorrelationId:
      name: Correlation-Id
      in: header
      required: false
      schema:
        type: string
        pattern: ^[0-9a-f]{8}-[0-9a-f]{4}-7[0-9a-f]{3}-[89ab][0-9a-f]{3}-[0-9a-f]{12}$
      description: Optional UUIDv7 correlation identifier (lowercase). Must match RFC4122 UUIDv7 (version nibble '7', variant 8|9|a|b). If present but invalid → 422 invalid_correlation_id (error_code).
    OrderId:
      name: order_id
      in: path
      required: true
      schema:
        type: string
        format: uuid
    Lang:
      name: lang
      in: query
      required: false
      schema:
        type: array
        items:
          type: string
          enum:
            - en
            - pt
            - id
        description: Language codes to filter localized content (repeatable). If omitted, all available locales are returned.
      style: form
      explode: true
    EventId:
      name: event_id
      in: path
      required: true
      schema:
        type: string
      description: Unique identifier for the event
    MarketId:
      name: market_id
      in: path
      required: true
      schema:
        type: string
      description: Unique identifier for the market
