> ## Documentation Index
> Fetch the complete documentation index at: https://docs.arcus.xyz/llms.txt
> Use this file to discover all available pages before exploring further.

# Place order

> Place a single order.

Place a single order. Requires `address` query parameter matching the master Ethereum address for `X-API-Key` (same as `POST /createApiKey` response).

### Response behavior

**Asynchronous.** The endpoint returns either `202 Accepted` (the common case — the signed request was forwarded to the matching engine and the body does not carry terminal state) or `200 OK` (the gateway already had definitive state for the order by the time it responded; `status` reflects that state). In both cases the body echoes the accepted `orderId` / `clientId` so subscribers can correlate WebSocket events with the request. To observe the full order lifecycle (`OPEN`, `FILLED`, `CANCELED`, `REJECTED`, fills, rejection reasons, etc.) clients **must** subscribe to the `orders` WebSocket channel (and `userFills` for trade-level events).


## OpenAPI

````yaml /api-reference/openapi.yml post /v1/placeOrder
openapi: 3.1.0
info:
  title: Arcus API
  version: 1.0.0
  description: >
    REST API for the Arcus exchange. Provides endpoints for order management,
    market data, and account onboarding.


    ## Coming soon (not implemented yet)

    The following features are declared in this spec (or implied by it) but are
    **not yet live** on the gateway or matching engine. Schemas and endpoints
    may be present so clients can wire against the final shape, but today the
    gateway returns `501 Not Implemented` or the feature is simply absent. Do
    not depend on these in production until they are announced as live.


    - **Modify order** (`POST /modifyOrder`) — reduce-only modifications will
    preserve queue priority; any change that moves the price level or increases
    size will be handled as an atomic cancel + replace (queue priority is lost).
    Currently returns `501 Not Implemented`.

    - **Batch modify order** (`POST /batchModifyOrders`) — same semantics as
    `modifyOrder`, applied atomically per item. Currently returns `501 Not
    Implemented`.

    - **Next predicted funding rate** — the predicted funding rate for the
    upcoming interval is not yet exposed on `GET /markets` or the markets
    WebSocket channel. Only the current realized rate is available today.

    - **Funding rate calculations** — the authoritative calculation methodology
    (premium sampling window, interest-rate component, clamping, payment
    schedule) is still being finalized and is not documented here yet.

    - **Advanced order types** — trailing stop, stop-limit, OCO, and other
    conditional / bracket order variants beyond plain `LIMIT` and `MARKET` are
    not yet supported.


    ## Authentication

    Order management and credential-creating endpoints require **three** request
    headers:


    - `X-API-Key` — hex-encoded Ed25519 public key (64 chars). The public key
    *is* the API key. Obtain/register one via `POST /createApiKey`.

    - `X-Timestamp` — Unix time in **nanoseconds** as a decimal string (e.g.
    `"1713825891591000000"`). Millisecond or second epochs are rejected with
    `401 Unauthorized`. Must be within ±30,000 ms (`MaxTimestampDriftMs`, the
    drift window stays configured in milliseconds) of server wall-clock;
    otherwise the request is rejected with `401 Unauthorized`.

    - `X-Signature` — lowercase hex-encoded Ed25519 signature (128 chars).


    **Single-order endpoints** (`placeOrder`, `cancelOrder`, `modifyOrder`) use
    the **ordersign typed canonical payload** as the signing message — a
    compact, key-sorted JSON object with engine-native integer values. The
    payload is NOT the raw HTTP body; it is built from the parsed request
    fields:

      ```
      placeOrder:   {"ad":"0x…","ai":N,[,"c":"…"],"ct":N,"g":N,"m":N,"op":1,"p":N,"q":N,"r":0|1,"s":N,"t":N,"v":1}
      cancelOrder:  {"ad":"0x…","ai":N,[,"c":"…"],"ct":N,[,"id":"…"],"m":N,"op":2,"v":1}
      modifyOrder:  {"ad":"0x…","ai":N,[,"c":"…"],"ct":N,[,"id":"…"],"m":N,"op":3,"p":N,"q":N,"v":1}
      ```

    `ct` in the payload must equal `X-Timestamp`. Integer values `p`, `q` etc.
    are engine-native ticks/quantums, not human-readable decimals. Keys in
    brackets are conditional (omitted when empty). `op` is the operation enum:
    `1`=place, `2`=cancel, `3`=modify, `4`=placeUntriggered (TPSL). See the
    `ordersign` package for full field definitions and reference signing code.


    **Batch endpoints do not use this header** — see below.

    - **Batch endpoints are signed per order — no `X-Signature`.** `POST
    /batchPlaceOrders` and `POST /batchCancelOrders` require `X-API-Key` +
    `X-Timestamp` only; the gateway does **not** verify an envelope
    `X-Signature` for these routes. Instead, each element of the `orders` /
    `cancels` array embeds its own `"signature"` field — a 128-hex Ed25519
    signature over the **ordersign typed canonical payload** for that operation:

        - Plain `orders` elements (no `tpsl_type`) use `op=1`.
        - TPSL / conditional `orders` elements (with `tpsl_type`) use `op=4`
          (`OpPlaceUntriggered`) — same field set as `op=1` but a different `op` value,
          preventing replay of a TPSL signature as a plain placeOrder.
        - `cancels` elements use `op=2`.

    The `ct` field inside every element's payload must equal the shared
    `X-Timestamp` header value. The whole batch consumes a single replay slot.


    Read-only endpoints (`GET /openOrders`, `GET /fills`, etc.) require only
    `X-API-Key`. In relaxed local development the gateway may run with
    signatures disabled; the OpenAPI `security` blocks list `signedRequest` (the
    three-header AND) or `{}` (empty) to reflect that.


    ## Account-scoped requests

    Public account info reads such as fills, positions, orders, open orders, and
    account snapshots require `address` as a **query parameter**. Signed order
    management requests also require `address`, and it must match the master
    Ethereum address returned for that API key from `POST /createApiKey`.
    WebSocket `placeOrder` / `cancel` / `batchPlaceOrders` / `batchCancelOrders`
    / `modifyOrder` use the same value in the JSON payload field `address`.


    ## Order execution is asynchronous

    `POST /placeOrder`, `POST /cancelOrder`, `POST /batchPlaceOrders`, and `POST
    /batchCancelOrders` are asynchronous. They return either:


    - **`202 Accepted`** — the common case. The signed request was validated by
    the gateway and forwarded to the matching engine; the body does **not**
    carry the order's terminal state.

    - **`200 OK`** — returned only when the gateway already has definitive state
    for the request by the time it responds (the body's `status` reflects that
    state, e.g. `OPEN` / `FILLED` / `CANCELED` / `REJECTED`). Clients should
    treat this as best-effort enrichment and not rely on it.


    In both cases the body echoes `orderId` / `clientId` so callers can
    correlate WebSocket events with the request. To observe the full order
    lifecycle (`OPEN`, `FILLED`, `CANCELED`, `REJECTED`, fills, rejection
    reasons, etc.) clients **must** subscribe to the `orders` WebSocket channel
    (and `userFills` for trade-level events). Treat both the `202` and `200`
    HTTP bodies as request acknowledgements; all definitive state lives on the
    WebSocket.


    `POST /modifyOrder` and `POST /batchModifyOrders` are **not implemented
    yet** — the schemas are declared so consumers can wire against the final
    shape, but the gateway returns `501 Not Implemented` today. When live, they
    will follow the same async contract.


    ## Order rejection reasons

    When the matching engine (go-core) rejects an order, the resulting
    `AccountUpdate` event (delivered over the WebSocket `account` channel with
    `type: REJECTED`) carries a `rejectionReason` enum. The same value may also
    appear on HTTP `OrderResponse` / `CancelOrderResponse` bodies that resolved
    to definitive state before returning (the `200` path described above). The
    complete set of reasons go-core can emit today is:


    | Value                        |
    Meaning                                                                 |

    |------------------------------|-------------------------------------------------------------------------|

    | `POST_ONLY_WOULD_CROSS`      | Post-only order was rejected because it
    would have crossed the book and taken liquidity. |

    | `SELF_TRADE`                 | Order would have matched against another
    resting order belonging to the same account; self-trade prevention rejected
    it. |

    | `UNDERCOLLATERALIZED`        | Account has insufficient free collateral /
    margin to support the order. |

    | `COULD_NOT_FILL`             | Generic "could not fill" outcome. Legacy
    reason — prefer the IOC/FOK-specific values below for time-in-force-driven
    cancels. |

    | `IOC_CANCELED`               | An `IMMEDIATE_OR_CANCEL` order produced
    zero fills against the book and was canceled. |

    | `FOK_FAILED`                 | A `FILL_OR_KILL` order could not be filled
    in full at submission and was canceled in its entirety. |

    | `REDUCE_ONLY_WOULD_INCREASE` | A `reduceOnly` order was rejected because
    executing it would have opened or increased the account's position rather
    than reducing it. |

    | `TOO_MANY_CLIENT_IDS`        | Account already has 10,000 live `clientId`s
    (the per-account maximum). Cancel an existing order or wait for one to reach
    a terminal state to free a slot. |

    | `DUPLICATE_CLIENT_ID`        | The `clientId` already maps to a live order
    on this account. Cancel the prior order (or use atomic modify via
    `placeAndCancel`) before reusing the same `clientId`. |


    See the `RejectionReason` schema for the canonical enum.


    ## Rate limiting

    Rate-limited endpoints return `429 Too Many Requests` carrying two
    retry-after signals:


    - `Retry-After` HTTP header (integer seconds, RFC 7231 compliant).

    - `retryAfterMs` field in the JSON body — precise milliseconds.


    The body also carries a typed `reason` indicating which layer rejected
    (`ip`, `account_empty`, `account_partial`). See `RateLimitedError` for the
    schema and `TooManyRequests` for the behavioral contract.


    Partner market-makers may be added to the bypass allowlist via the
    `RATELIMIT_BYPASS_ADDRESSES` server-side configuration (per environment).
    Allowlisted addresses skip rate-limit charging entirely and never receive a
    429 from this layer.
servers:
  - url: https://api.testnet.arcus.xyz
    description: Testnet
  - url: https://api.arcus.xyz
    description: Mainnet
security: []
tags:
  - name: Onboarding
    description: Account and API key management.
  - name: Public
    description: >-
      Unauthenticated endpoints — market data, health checks, and account-scoped
      info reads.
  - name: Exchange
    description: >
      Signed order management endpoints (all are HTTP `POST`). Every request
      must carry the full header triple `X-API-Key` + `X-Timestamp` +
      `X-Signature` where `X-Signature` is an Ed25519 signature over the
      operation-specific signing message: `placeOrder`, `cancelOrder`, and
      `modifyOrder` sign the ordersign typed canonical payload (the JSON object
      itself, no prefix), while `cancelAllOrders` and `setLeverage` use the
      legacy `X-Timestamp + ACTION + canonicalJSON(body)` message (ACTION = the
      final camelCase path segment; the HTTP method is not signed).
      `X-Timestamp` is a Unix nanosecond epoch (decimal string) and must be
      within ±30,000 ms of server wall-clock. See the top-level
      **Authentication** section for full details. These endpoints also require
      query parameter `address` matching the key's master Ethereum address.
  - name: Referral
    description: >
      Affiliate / referral program endpoints. Mutating endpoints (`POST`)
      require the full Ed25519 signature triple and the body `address` must
      match the API key's master Ethereum address. Read endpoints (`GET`) are
      public and scoped by `?address=`.
  - name: Meta
    description: >
      Product / UX-level APIs served by the `api-meta` service. These live
      behind the same host as the rest of the API but are routed (via the ALB
      path rule on `/v1/api-meta/*`) to a separate deployment with its own
      ScyllaDB keyspace and its own deployment lifecycle. They share the same
      `X-API-Key` + Ed25519 signature scheme as Exchange endpoints; the signed
      `REQUEST_PATH` includes the `/v1/api-meta/...` prefix verbatim.
  - name: UserPreferences
    description: >
      Per-master-wallet preference store (favorited markets, dashboard layouts,
      theme, language, …). Reads are public and scoped by `?address=`; writes
      (PATCH / DELETE) require an Ed25519 signature and are bound to the address
      registered to `X-API-Key`. See `UserPreferenceKey` for the known keys and
      `UserPreferenceValue` for the type system.
  - name: MarketMetadata
    description: >
      Curated reference data for markets (company profile, branding, headline
      financials, …) refreshed from the upstream ticker feed. Reads are public;
      freshness is bounded by the per-row `ingestedAt` returned in the response
      envelope.
  - name: Notifications
    description: >
      Per-master-wallet inbox of "unseen events" surfaced from the matching
      engine: terminal resting-order fills (maker side, fully filled),
      liquidations, and TP/SL triggers. Reads are public and scoped by
      `?address=`; the `:markSeen` write requires an Ed25519 signature and is
      bound to the address registered to `X-API-Key`. Notifications expire after
      30 days.
paths:
  /v1/placeOrder:
    post:
      tags:
        - Exchange
      summary: Place order
      description: Place a single order.
      operationId: placeOrder
      parameters:
        - $ref: '#/components/parameters/AddressQuery'
      requestBody:
        required: true
        content:
          application/json:
            schema:
              $ref: '#/components/schemas/OrderRequest'
      responses:
        '200':
          description: >
            Order processed and the gateway already has definitive state for it.
            `status` reflects that state (`OPEN` / `FILLED` / `CANCELED` /
            `REJECTED`). Treat this as best-effort enrichment of the `202` path;
            the `orders` WebSocket channel is still the source of truth for the
            full lifecycle.
          content:
            application/json:
              schema:
                $ref: '#/components/schemas/OrderResponse'
        '202':
          description: >
            Order accepted by the gateway and forwarded to the matching engine.
            The body contains the accepted `orderId` / `clientId` only — the
            definitive order state is delivered via the `orders` WebSocket
            channel, which clients must subscribe to.
          content:
            application/json:
              schema:
                $ref: '#/components/schemas/OrderResponse'
        '400':
          description: >
            Validation error, missing/invalid `address`, or a structured order
            rejection. Structured rejections include `errorSource` and
            `errorType` alongside `error`:


            | `errorType`       | Cause |

            |-------------------|-------|

            | `InvalidRequest`  | A field failed validation (bad address,
            unknown market, invalid enum, etc.). TPSL-specific causes:
            `stopPrice` is required when `tpslType` is set; `tpslType` must be
            `STOP_LOSS` or `TAKE_PROFIT`; `reduceOnly` must be `true` for TPSL
            orders; trigger price must not be on the wrong side of the current
            oracle price (a trigger that would fire instantly is rejected).
            Order notional (`quantity` × `price`) must be at least $5 USD;
            reduce-only orders (including TPSLs) are exempt. |

            | `Tick`            | Price or size is not aligned to the market's
            tick / step size. |

            | `OracleDeviation` | A LIMIT order's price deviates from the
            current oracle price by more than the configured threshold. |

            | `MarketPriceSlippageToleranceTooHigh` | A MARKET order's
            protective slippage-bound price deviates from the current mark price
            by more than 10%. For TPSL MARKET legs the reference is the
            `stopPrice` (trigger price). Adjust `price` to within 10% and
            resubmit. |

            | `ReduceOnly`      | A reduce-only order would open or increase a
            position rather than reduce it. |
          content:
            application/json:
              schema:
                $ref: '#/components/schemas/error'
        '401':
          description: Missing or invalid API key.
          content:
            application/json:
              schema:
                $ref: '#/components/schemas/error'
        '403':
          description: '`address` does not match the API key''s account.'
          content:
            application/json:
              schema:
                $ref: '#/components/schemas/error'
        '429':
          $ref: '#/components/responses/TooManyRequests'
        '500':
          description: Internal server error.
          content:
            application/json:
              schema:
                $ref: '#/components/schemas/error'
        '503':
          description: Order placement temporarily disabled.
          content:
            application/json:
              schema:
                $ref: '#/components/schemas/error'
      security:
        - apiKey: []
          timestamp: []
          signature: []
        - {}
components:
  parameters:
    AddressQuery:
      name: address
      in: query
      required: true
      schema:
        $ref: '#/components/schemas/EthereumAddressHex'
      description: >
        Master Ethereum address for this API key (must match `address` from
        `POST /createApiKey` for the same key). Required on REST for
        account-scoped reads and for place/cancel. Invalid hex → 400; mismatch
        with key → 403.
  schemas:
    OrderRequest:
      type: object
      required:
        - address
        - marketId
        - accountIndex
        - orderSide
        - orderType
        - quantity
        - price
        - timeInForce
        - timestamp
      properties:
        address:
          $ref: '#/components/schemas/EthereumAddressHex'
          description: >
            Master Ethereum address for this API key (must match `POST
            /createApiKey` for the same key). May be sent here and/or as the
            `address` query parameter; if both are set, they must match.
        marketId:
          $ref: '#/components/schemas/MarketId'
        accountIndex:
          $ref: '#/components/schemas/AccountIndex'
        orderSide:
          $ref: '#/components/schemas/OrderSide'
        orderType:
          $ref: '#/components/schemas/OrderType'
        quantity:
          type: string
          minLength: 1
          pattern: ^(0|0\.[0-9]*[1-9][0-9]*|[1-9][0-9]*\.?[0-9]*)$
          description: >
            Order size in human-readable base-asset units (e.g. "0.1" for 0.1
            BTC). Must be a positive value divisible by the market's step size
            (see GET /markets). Set to `"0"` for `positionTpsl` grouping — the
            engine resizes to the full open position at trigger time.
          examples:
            - '0.1'
        price:
          type: string
          minLength: 1
          pattern: ^(0|0\.[0-9]*[1-9][0-9]*|[1-9][0-9]*\.?[0-9]*)$
          description: >
            Order price in human-readable USD (e.g. `"50000.5"`). Must be a
            positive decimal divisible by the market's tick size (see `GET
            /markets`).


            - **LIMIT** — the resting limit price.

            - **MARKET** — required protective slippage bound (see `orderType`).
            Must be within 10% of the current **mark price**; deviations beyond
            that are rejected with `MarketPriceSlippageToleranceTooHigh`.

            - **TPSL MARKET** — also required. Must be within 10% of `stopPrice`
            (trigger price), since at trigger time the mark price ≈ stopPrice.
          examples:
            - '50000.5'
        timeInForce:
          $ref: '#/components/schemas/TimeInForce'
        goodTilTime:
          type: string
          pattern: ^[0-9]+$
          description: >
            Good-till-time (GTT): the order's expiration as an epoch-MICROSECOND
            timestamp (string) — the API's user-facing timestamp resolution,
            matching `createdAt`/`updatedAt`. REQUIRED for resting
            time-in-forces (`GTT`, `ALO`) and must be at least one month ahead
            of the current system timestamp; a nearer value is rejected. It is
            ignored for `IOC` and `FOK`, which never rest. The engine cancels
            the order (cancel reason `Expired`) the first time it would fill at
            or after this instant.
          examples:
            - '4102444800000000'
        minSize:
          type: string
          description: Minimum fill size in human-readable base-asset units.
          nullable: true
        stopPrice:
          type: string
          minLength: 1
          description: >-
            Trigger price in human-readable USD. Required when `tpslType` is
            set; otherwise omit this field.
        tpslType:
          $ref: '#/components/schemas/TpslType'
          description: >-
            Set to `STOP_LOSS` or `TAKE_PROFIT` to mark this as a trigger order.
            Requires `stopPrice`; for `LIMIT` execution, `price` is also
            required.
        isPositionTPSL:
          type: boolean
          description: >
            When true, mark this TPSL as a position-level close: at trigger time
            the engine resizes the order to the user's full open position in
            `marketId` (and forces `reduceOnly`). If the position is zero, the
            TPSL is canceled instead of executed. Requires `tpslType`. At most
            one position-level TPSL of each trigger class (TP, SL) may be active
            per account+market — a second placement is rejected with
            `POSITION_TPSL_ALREADY_EXISTS`. Defaults to false (sized leg /
            partialTpsl behavior).
          default: false
          nullable: true
        parentOrderId:
          type: string
          description: >
            Entry order id this TPSL leg is bound to (entryTpsl bundles). Set on
            each child TPSL placed alongside an entry order so the engine
            cascade-cancels every leg pointing at the entry when the entry is
            canceled. Requires `tpslType`. Omit for standalone TPSLs
            (partialTpsl, positionTpsl) and for non-TPSL orders.
          nullable: true
        clientId:
          type: string
          maxLength: 36
          pattern: ^[A-Za-z0-9_-]+$
          description: >
            Optional client-assigned identifier for this order.


            **Charset** — only ASCII letters, digits, hyphens, and underscores
            (`[A-Za-z0-9_-]`) are accepted. This restricted set ensures JSON
            serialization is byte-identical across all SDK languages (Go,
            TypeScript, Python), which is required for cross-language signature
            compatibility. Strings containing any other character (spaces, `@`,
            Unicode, etc.) are rejected with HTTP 400 `InvalidRequest`.


            **Length** — 1–36 characters.


            **Uniqueness** — must be unique among the account's currently live
            orders. Reusing a `clientId` while the prior order is still open
            returns `DUPLICATE_CLIENT_ID`. The id is freed once the prior order
            reaches a terminal state (FILLED, CANCELED, REJECTED). An account
            may hold at most 10,000 live `clientId`s (`TOO_MANY_CLIENT_IDS`).


            When set, the order can later be canceled or modified by `clientId`
            instead of `orderId`.
          nullable: true
        clientTime:
          type: string
          description: Client-side timestamp (epoch ms as string).
          nullable: true
        fillMode:
          type: string
          description: Fill mode.
          nullable: true
        reduceOnly:
          type: boolean
          description: >
            If true, the order can only reduce an existing position. When
            `reduceOnly` is `true`, `timeInForce` must be `IOC` or `FOK`.
          default: false
          nullable: true
        signature:
          type: string
          description: >
            Per-order Ed25519 signature (128 hex). REQUIRED when this order is
            an element of a `POST /batchPlaceOrders` `orders` array; omitted for
            single `POST /placeOrder` (which uses `X-Signature` in the HTTP
            header instead).


            Covers the **ordersign typed canonical payload** for this order:


            - Plain orders (no `tpsl_type`): `op=1`
            `{"ad":"0x…","ai":N,[,"c":"…"],"ct":N,"g":N,"m":N,"op":1,"p":N,"q":N,"r":0|1,"s":N,"t":N,"v":1}`

            - TPSL / conditional orders (with `tpsl_type`): `op=4`
            (`OpPlaceUntriggered`) Same field set as `op=1` but `"op":4`. Using
            a distinct op prevents cross-replay between TPSL and plain
            placeOrder signatures.


            In both cases `ct` must equal the shared `X-Timestamp` header value.
        timestamp:
          type: integer
          format: int64
          minimum: 1
          maximum: 9223372036854776000
          description: Signature timestamp (epoch ms). Must be > 0.
      dependentRequired:
        tpslType:
          - stopPrice
        isPositionTPSL:
          - tpslType
        parentOrderId:
          - tpslType
      allOf:
        - if:
            properties:
              timeInForce:
                enum:
                  - GTT
                  - ALO
            required:
              - timeInForce
          then:
            required:
              - goodTilTime
        - if:
            properties:
              orderType:
                const: MARKET
            required:
              - orderType
          then:
            properties:
              timeInForce:
                enum:
                  - IOC
        - if:
            properties:
              orderType:
                const: LIMIT
              tpslType:
                type: string
                minLength: 1
            required:
              - orderType
              - tpslType
          then:
            properties:
              timeInForce:
                enum:
                  - GTT
        - if:
            properties:
              tpslType:
                type: string
                minLength: 1
            required:
              - tpslType
          then:
            properties:
              reduceOnly:
                const: true
            required:
              - reduceOnly
    OrderResponse:
      type: object
      required:
        - address
        - accountIndex
        - orderId
        - marketId
        - marketDisplayName
        - side
        - quantity
        - price
        - status
        - createdAt
      description: >
        Place-order response. The HTTP call is asynchronous and may return
        either:


        - `202 Accepted` — common case. `status` is `ACK` and the body carries
        no terminal state; the `orders` WebSocket channel delivers the order's
        lifecycle.

        - `200 OK` — the gateway already had definitive state for the order by
        the time it responded. `status` reflects that state (`OPEN` / `FILLED` /
        `CANCELED` / `REJECTED`) and enrichment fields (`updatedAt`,
        `remainingSize`, `filledSize`, `rejectionReason`) may be populated.
        Clients should treat this as best-effort and not rely on it.


        In both cases the body echoes the accepted `orderId` and `clientId` for
        correlation. To observe the full lifecycle (fills, rejection reasons,
        etc.) clients **must** subscribe to the `orders` WebSocket channel (and
        `userFills` for trade-level events).
      properties:
        address:
          type: string
          description: Master Ethereum address of the account that placed the order.
          examples:
            - '0x1234567890abcdef1234567890abcdef12345678'
        accountIndex:
          type: integer
          minimum: 0
          maximum: 9
          description: Account index (subaccount) that placed the order.
        orderId:
          type: string
          description: Server-generated order ID (hex string).
          examples:
            - a1b2c3d4e5f67890
        clientId:
          type: string
          description: Echo of the clientId, if provided.
        marketId:
          $ref: '#/components/schemas/MarketId'
        marketDisplayName:
          $ref: '#/components/schemas/MarketDisplayName'
        side:
          $ref: '#/components/schemas/OrderSide'
          description: Order side.
        type:
          $ref: '#/components/schemas/OrderType'
          description: Order type (e.g. LIMIT, MARKET).
        timeInForce:
          $ref: '#/components/schemas/TimeInForce'
          description: Echo of the time-in-force from the request.
        goodTilTime:
          type: string
          description: >
            Expiration timestamp in epoch microseconds (as string), echoed from
            the request. Present for GTD/ALO orders that carry a good-till-time
            expiry; absent for GTC/IOC/FOK orders (which never expire). Matches
            the `goodTilTime` field on the `Order` shape.
        quantity:
          type: string
          description: Order size in human-readable base-asset units.
        price:
          type: string
          description: Order price in human-readable USD.
        reduceOnly:
          type: boolean
          description: Echo of the reduce-only flag from the request.
        status:
          $ref: '#/components/schemas/order-status'
          description: >
            `ACK` on the `202` path. On the `200` path, the definitive go-core
            state (`OPEN` / `FILLED` / `CANCELED` / `REJECTED`). Subscribe to
            the `orders` WebSocket channel for the full lifecycle.
          examples:
            - ACK
        createdAt:
          type: integer
          format: int64
          description: Creation timestamp (epoch microseconds).
        updatedAt:
          type: integer
          format: int64
          description: >
            Epoch microseconds of the definitive go-core state when the gateway
            returned it on the `200` path. Omitted on the `202` path —
            definitive timestamps are delivered via the `orders` WebSocket
            channel.
        remainingSize:
          type: string
          description: >
            Unfilled size at `updatedAt`, in human-readable base-asset units.
            Populated only on the `200` path; otherwise see the `orders`
            WebSocket channel.
        filledSize:
          type: string
          description: >
            Cumulative filled size at `updatedAt`, in human-readable base-asset
            units. Populated only on the `200` path; otherwise see the `orders`
            WebSocket channel.
        rejectionReason:
          $ref: '#/components/schemas/RejectionReason'
          description: >
            Machine-readable reason when `status` is `REJECTED` on the `200`
            path. Otherwise rejection reasons are delivered via the `orders`
            WebSocket channel.
        error:
          type: string
          description: Present when status is ERROR (e.g. batch item validation failure).
        rateLimit:
          $ref: '#/components/schemas/AccountRateLimit'
          description: >
            Per-subaccount order-pool rate-limit snapshot after this placement.
            Omitted when rate limiting is not configured.
    error:
      type: object
      required:
        - error
      properties:
        error:
          type: string
          description: Human-readable error message.
          examples:
            - Invalid request body
        code:
          type: string
          description: >
            Machine-readable error code for generic (non-order) rejections.
            Present on geo-restriction blocks; clients should key
            product-availability UI off this rather than the human `error`
            string.


            | Value            | Meaning |

            |------------------|---------|

            | `GEO_RESTRICTED` | The action is not available from the caller's
            country/region for this product (perps and/or spot). Reads remain
            available; only state-changing actions are blocked. |
          enum:
            - GEO_RESTRICTED
        errorSource:
          type: string
          description: >
            Scopes the failure to a specific operation. Present on structured
            order errors (place / cancel / modify flows); absent on generic HTTP
            errors.
          enum:
            - Order
            - Cancel
        errorType:
          type: string
          description: >
            Machine-readable reason for the API-layer rejection. Present
            alongside `errorSource` on structured order errors; absent on
            generic HTTP errors.


            | Value             | Meaning |

            |-------------------|---------|

            | `Tick`            | Price or size does not align to the market's
            tick size / step size. |

            | `InvalidRequest`  | A request field failed validation (bad
            address, unknown market, invalid enum, etc.). TPSL-specific causes:
            `stopPrice` required when `tpslType` is set; `tpslType` must be
            `STOP_LOSS` or `TAKE_PROFIT`; `reduceOnly` must be `true` for TPSL
            orders; trigger price must not sit on the wrong side of the current
            oracle (would fire immediately); batch grouping/shape constraints
            violated (wrong order count, missing or duplicate `tpslType` legs).
            |

            | `OracleDeviation` | The order's price deviates from the current
            oracle price by more than the allowed threshold. Adjust the price
            closer to the oracle and resubmit. |

            | `ReduceOnly`      | A reduce-only order was rejected because
            filling it would open or increase the account's position rather than
            reduce it. |

            | `Unavailable`     | The requested operation (place / cancel) is
            temporarily disabled. |

            | `Unauthorized`    | API key or trading-identity check failed. |

            | `Forbidden`       | The authenticated key is not permitted to
            perform the requested operation. |

            | `NotImplemented`  | The requested capability is not yet available
            on this path (e.g. TPSL over WebSocket). |

            | `Transmission`    | The gateway accepted and validated the request
            but could not deliver it to the matching engine. Retry. |

            | `Internal`        | Unexpected gateway-side failure unrelated to
            transmission. |
          enum:
            - Tick
            - InvalidRequest
            - OracleDeviation
            - ReduceOnly
            - Unavailable
            - Unauthorized
            - Forbidden
            - NotImplemented
            - Transmission
            - Internal
        rejectionReason:
          type: string
          description: >
            Machine-readable engine-level rejection code. Present on
            `OrderResponse` / `BatchOrderItemResponse` when `status` is
            `REJECTED` on the synchronous `200` path (i.e. the gateway already
            had a definitive engine reject before responding). Absent on `202`
            responses and on HTTP error bodies — in those cases rejection
            reasons are delivered asynchronously via the `orders` WebSocket
            channel.


            Mirror of the canonical `RejectionReason` schema in `openapi.yaml` —
            keep the two in lockstep (and the hardcoded list in
            `sdks/python/scripts/post_process_models.py`).


            | Value                              | Meaning |

            |------------------------------------|---------|

            | `POST_ONLY_WOULD_CROSS`            | Post-only order would have
            crossed the book and taken liquidity. |

            | `SELF_TRADE`                       | Order would match against the
            account's own resting order; blocked by self-trade prevention. |

            | `UNDERCOLLATERALIZED`              | Account has insufficient free
            collateral / margin to support the order. |

            | `COULD_NOT_FILL`                   | Generic "could not fill"
            (legacy; prefer `IOC_CANCELED` / `FOK_FAILED`). |

            | `IOC_CANCELED`                     | IOC order produced zero fills
            and was canceled. |

            | `FOK_FAILED`                       | FOK order could not be fully
            filled at submission. |

            | `REDUCE_ONLY_WOULD_INCREASE`       | reduce-only order would have
            opened or increased the position. |

            | `TOO_MANY_CLIENT_IDS`              | Account already has 10,000
            live `clientId`s (the per-account maximum). Cancel or let existing
            orders reach a terminal state to free slots before placing new ones
            with distinct `clientId`s. |

            | `DUPLICATE_CLIENT_ID`              | Account already has an open
            order with the same `clientId`. |

            | `POSITION_TPSL_ALREADY_EXISTS`     | A position-level TPSL of the
            same trigger class (TP or SL) already exists for this
            account+market. At most one TP and one SL per market. |

            | `ENTRY_TPSL_CANNOT_BE_POSITION_TPSL` | A TPSL bound to an entry
            order (`parentOrderId` set) cannot also be a position-level TPSL;
            entry-linked TPSLs are sized partial legs. |

            | `OPEN_ORDER_CAP_EXCEEDED`          | Account has reached its
            per-account open-order cap; cancel a resting order or let one reach
            a terminal state before placing another. |

            | `ORDER_WILL_TAKE_LIQUIDITY_DURING_MARKET_HALT` | Outside regular
            trading hours, a crossing order would have taken liquidity past the
            off-hours trading bound while the market is halted at that band. |

            | `ORDER_NOT_FOUND`                  | `cancelOrder` referenced an
            order not on the orderbook (already filled, canceled, never placed,
            or owned by another account). |

            | `ORDER_NOT_FOUND_FOR_MODIFY`       | `modifyOrder` referenced an
            order not on the regular orderbook; TPSL and untriggered orders
            cannot be modified. |

            | `MODIFY_CHANGED_IMMUTABLE_FIELD`   | `modifyOrder` attempted to
            change an immutable field (`side`, `timeInForce`, `reduceOnly`, or
            `orderType`). |

            | `MODIFY_ZERO_SIZE`                 | `modifyOrder` specified a new
            size of zero or less. |
          enum:
            - POST_ONLY_WOULD_CROSS
            - SELF_TRADE
            - UNDERCOLLATERALIZED
            - COULD_NOT_FILL
            - IOC_CANCELED
            - FOK_FAILED
            - REDUCE_ONLY_WOULD_INCREASE
            - TOO_MANY_CLIENT_IDS
            - DUPLICATE_CLIENT_ID
            - POSITION_TPSL_ALREADY_EXISTS
            - ENTRY_TPSL_CANNOT_BE_POSITION_TPSL
            - OPEN_ORDER_CAP_EXCEEDED
            - ORDER_WILL_TAKE_LIQUIDITY_DURING_MARKET_HALT
            - ORDER_NOT_FOUND
            - ORDER_NOT_FOUND_FOR_MODIFY
            - MODIFY_CHANGED_IMMUTABLE_FIELD
            - MODIFY_ZERO_SIZE
    EthereumAddressHex:
      type: string
      pattern: ^(0x|0X)?[0-9a-fA-F]{40}$
      description: >
        20-byte EVM address as hex: optional `0x` or `0X` prefix and exactly 40
        hexadecimal digits. API responses normalize to lowercase `a`–`f` after
        `0x`.
    MarketId:
      type: integer
      minimum: 0
      maximum: 65535
      description: >-
        Perpetual market identifier (uint16). Map to display name via `GET
        /markets`. Used for orders, positions, funding, and market metadata.
    AccountIndex:
      type: integer
      minimum: 0
      maximum: 9
      description: >-
        Account index (account index, 0–9). Identifies the account for orders,
        positions, fills, and API keys.
    OrderSide:
      type: string
      enum:
        - BUY
        - SELL
    OrderType:
      type: string
      enum:
        - LIMIT
        - MARKET
      description: >
        Execution style of the order. Use `tpslType` to mark an order as
        stop-loss or take-profit.


        - `LIMIT` — rests at the specified `price` until matched or expired.

        - `MARKET` — executes immediately at best available price. A `price`
        field is **required** and serves as a protective slippage bound. The
        price must be a positive decimal within **10%** of the current **mark
        price**; deviations beyond that are rejected with
        `MarketPriceSlippageToleranceTooHigh`. TPSL MARKET legs must also supply
        a `price`, but it is validated against the `stopPrice` (trigger price)
        rather than the live mark price — the price must be within **10%** of
        `stopPrice`, since at trigger time the mark price ≈ stopPrice.
    TimeInForce:
      type: string
      enum:
        - GTT
        - IOC
        - FOK
        - ALO
      description: >-
        GTT = Good Till Time (rests until goodTilTime), IOC = Immediate or
        Cancel, FOK = Fill or Kill, ALO = Add Liquidity Only (post-only)
    TpslType:
      type: string
      enum:
        - STOP_LOSS
        - TAKE_PROFIT
      description: >-
        Trigger purpose of the order, orthogonal to `orderType` (execution
        style). Absent/omitted for plain orders.
    MarketDisplayName:
      type: string
      description: Market symbol (e.g. BTC-USD).
      examples:
        - BTC-USD
    order-status:
      type: string
      description: Current status of an order.
      enum:
        - PENDING
        - OPEN
        - PARTIALLY_FILLED
        - FILLED
        - CANCELED
        - MARGIN_CANCELED
        - REJECTED
        - UNTRIGGERED
        - TPSL_PLACED
        - TPSL_TRIGGERED
        - TPSL_CANCELED
        - LIQUIDATED
        - ADL
        - ACK
        - CANCEL_ACKNOWLEDGED
        - CANCEL_ALL_ACKNOWLEDGED
        - CANCEL_PENDING
        - ERROR
    RejectionReason:
      type: string
      description: >
        Machine-readable reason emitted by the matching engine (go-core) when an
        order is rejected. Delivered on `AccountUpdate` events with `type:
        REJECTED` over the WebSocket `account` channel, and (best-effort) on
        `OrderResponse` / `CancelOrderResponse` HTTP bodies that resolved to
        definitive state before returning.


        | Value                       |
        Meaning                                                                
        |

        |-----------------------------|-------------------------------------------------------------------------|

        | `POST_ONLY_WOULD_CROSS`     | A post-only order was rejected because
        it would have crossed the book and taken liquidity. |

        | `SELF_TRADE`                | The order would have matched against
        another resting order belonging to the same account; self-trade
        prevention rejected it. |

        | `UNDERCOLLATERALIZED`       | Account has insufficient free collateral
        / margin to support the order.  |

        | `COULD_NOT_FILL`            | Generic "could not fill" outcome. Legacy
        reason — prefer the IOC/FOK-specific values below for
        time-in-force-driven cancels. |

        | `IOC_CANCELED`              | An `IMMEDIATE_OR_CANCEL` order produced
        zero fills against the book and was canceled. |

        | `FOK_FAILED`                | A `FILL_OR_KILL` order could not be
        filled in full at submission and was canceled in its entirety. |

        | `REDUCE_ONLY_WOULD_INCREASE`| A `reduceOnly` order was rejected
        because executing it would have opened or increased the account's
        position rather than reducing it. |

        | `TOO_MANY_CLIENT_IDS`       | Account already has 10,000 live
        `clientId`s (the per-account maximum). Cancel an existing order, or wait
        for one to reach a terminal state (`FILLED`, `CANCELED`, or `REJECTED`),
        to free a slot before placing an order with a new `clientId`. |

        | `DUPLICATE_CLIENT_ID`       | A `placeOrder` was rejected because the
        account already has an open order with the same `clientId`. The clientId
        becomes reusable once the prior order reaches a terminal state
        (`FILLED`, `CANCELED`, or `REJECTED`), or you can replace the prior
        order atomically via `placeAndCancel`. |

        | `POSITION_TPSL_ALREADY_EXISTS` | A position-level TPSL of the same
        trigger class (TP or SL) already exists for this account+market; at most
        one TP and one SL position-level TPSL is allowed per market. |

        | `ENTRY_TPSL_CANNOT_BE_POSITION_TPSL` | A TPSL linked to an entry order
        (`parentOrderId` set) cannot also be a position-level TPSL. Entry-linked
        TPSLs are sized partial legs and must not carry position-level
        semantics. |

        | `ORDER_WILL_TAKE_LIQUIDITY_DURING_MARKET_HALT` | A crossing order was
        rejected because, outside regular trading hours (RTH), it would have
        taken liquidity past the off-hours trading bound while the market is
        halted at that band. Trading resumes once the bound expands or the
        market re-enters RTH. |

        | `ORDER_NOT_FOUND_FOR_MODIFY` | A `modifyOrder` targeted an order that
        is not on the regular orderbook — missing, already filled, canceled,
        owned by another account, or a `clientId` that does not resolve under
        the account. TPSL and untriggered orders cannot be modified. |

        | `MODIFY_CHANGED_IMMUTABLE_FIELD` | A `modifyOrder` attempted to change
        an immutable field (`side`, `timeInForce`, `reduceOnly`, or
        `orderType`). Only price and size may be modified; place a new order to
        change anything else. |

        | `MODIFY_ZERO_SIZE` | A `modifyOrder` specified a new size of zero or
        less. Use `cancelOrder` to remove an order rather than modifying it to
        zero. |

        | `PRICE_WILL_EXCEED_MAXIMUM_OUTSIDE_RTH_TRADING_BOUND` | The order's
        price would push the market beyond the maximum allowed off-hours trading
        bound once the current regular-trading-hours (RTH) session ends. The
        order is rejected pre-emptively to avoid a resting order that would be
        immediately unexecutable outside RTH. |

        | `MODIFY_WOULD_CROSS_OUTSIDE_RTH_TRADING_BOUNDARY` | A non-crossing
        `modifyOrder` reprice was rejected because the new price would violate
        the active off-hours trading band. Unlike
        `PRICE_WILL_EXCEED_MAXIMUM_OUTSIDE_RTH_TRADING_BOUND` (used on the
        crossing cancel-replace path, which removes the original), this reason
        means the in-place modify was rejected and the **original order is still
        resting** on the book unchanged. |
      enum:
        - POST_ONLY_WOULD_CROSS
        - SELF_TRADE
        - UNDERCOLLATERALIZED
        - COULD_NOT_FILL
        - IOC_CANCELED
        - FOK_FAILED
        - REDUCE_ONLY_WOULD_INCREASE
        - TOO_MANY_CLIENT_IDS
        - DUPLICATE_CLIENT_ID
        - POSITION_TPSL_ALREADY_EXISTS
        - ENTRY_TPSL_CANNOT_BE_POSITION_TPSL
        - ORDER_WILL_TAKE_LIQUIDITY_DURING_MARKET_HALT
        - ORDER_NOT_FOUND_FOR_MODIFY
        - MODIFY_CHANGED_IMMUTABLE_FIELD
        - MODIFY_ZERO_SIZE
        - PRICE_WILL_EXCEED_MAXIMUM_OUTSIDE_RTH_TRADING_BOUND
        - MODIFY_WOULD_CROSS_OUTSIDE_RTH_TRADING_BOUNDARY
      x-enum-descriptions:
        - Post-only order would have crossed the book and taken liquidity.
        - >-
          Order would have matched against the account's own resting order;
          blocked by self-trade prevention.
        - Insufficient free collateral / margin to support the order.
        - >-
          Generic "could not fill" outcome. Legacy — prefer IOC_CANCELED /
          FOK_FAILED for time-in-force cancels.
        - IMMEDIATE_OR_CANCEL order produced zero fills and was canceled.
        - >-
          FILL_OR_KILL order could not be fully filled at submission and was
          canceled.
        - >-
          reduceOnly order would have opened or increased a position instead of
          reducing it.
        - >-
          Account already has 10,000 live clientIds (per-account maximum);
          cancel an existing order or let one reach a terminal state to free a
          slot.
        - >-
          Account already has an open order with the same clientId; reusable
          after the prior order terminates, or replace atomically via
          placeAndCancel.
        - >-
          A position-level TPSL of the same trigger class (TP or SL) already
          exists for this account+market; at most one TP and one SL
          position-level TPSL is allowed per market.
        - >-
          A TPSL linked to an entry order (parentOrderId set) cannot also be a
          position-level TPSL; entry-linked TPSLs are sized partial legs.
        - >-
          Outside regular trading hours, a crossing order would have taken
          liquidity past the off-hours trading bound while the market is halted
          at that band; rejected until the bound expands or the market re-enters
          RTH.
        - >-
          modifyOrder targeted an order not on the regular orderbook (missing,
          filled, canceled, owned by another account, or a clientId that does
          not resolve under the account); TPSL and untriggered orders cannot be
          modified.
        - >-
          modifyOrder attempted to change an immutable field (side, timeInForce,
          reduceOnly, or orderType); only price and size may be modified.
        - >-
          modifyOrder specified a new size of zero or less; use cancelOrder
          instead of modifying to zero.
        - >-
          Order price would exceed the maximum allowed off-hours trading bound
          once the current RTH session ends; rejected pre-emptively to avoid
          stranded resting orders outside RTH.
        - >-
          Non-crossing modify reprice rejected because the new price violates
          the active off-hours trading band; unlike
          PRICE_WILL_EXCEED_MAXIMUM_OUTSIDE_RTH_TRADING_BOUND (crossing path),
          the original order is still resting on the book unchanged.
    AccountRateLimit:
      type: object
      required:
        - pool
        - remaining
      description: >
        Per-subaccount rate-limit snapshot for the pool charged by this request,
        attached to exchange write responses (placeOrder, modifyOrder,
        cancelOrder, batchPlaceOrders, batchCancelOrders, cancelAllOrders) on
        both REST and WebSocket. Reflects the account-pool state the limiter
        already computed while charging the request — no extra round-trip. The
        IP rate-limit layer is intentionally not exposed.
      properties:
        pool:
          type: string
          enum:
            - order
            - cancel
          description: >
            Which per-subaccount pool this request charged: `order` for
            place/modify/batchPlace, `cancel` for cancel/batchCancel/cancelAll.
        remaining:
          type: integer
          format: int64
          description: >
            Remaining tokens in the charged pool after this request (`floor(cap
            - consumed)`). Can be `0` or negative while the request still
            succeeds when the account is on the drip throttle (pool exhausted,
            one action allowed per drip period). A value of `-1` is a sentinel
            meaning the account layer was not enforced for this request: the
            address is on the rate-limit bypass allowlist, shadow mode is
            active, the limiter failed open on a Redis error, or only the IP
            layer is enabled.
          examples:
            - 9958
    RateLimitedError:
      description: >
        429 response body shape. Extends the generic `Error` with two
        rate-limit-specific fields:


        - `reason` — which limiter rejected the request. Stable string enum; new
        values may be added in future versions, so clients should treat unknown
        values as opaque.

        - `retryAfterMs` — precise milliseconds the client should wait before
        retrying. The HTTP `Retry-After` header carries the same information
        rounded up to whole seconds (RFC 7231); prefer this field when
        sub-second precision matters.
      type: object
      required:
        - error
      properties:
        error:
          type: string
          example: rate limited
        reason:
          type: string
          enum:
            - ip
            - account_empty
            - account_partial
            - unknown
          description: |
            Layer that rejected the request:
              * `ip` — per-IP weight bucket exhausted.
              * `account_empty` — per-subaccount pool fully exhausted; the
                drip-throttle has no token available either.
              * `account_partial` — pool has some credit but not enough
                for this batch. Split the request into smaller chunks to
                drain the remaining headroom.
              * `unknown` — defensive fallback; clients should retry per
                `retryAfterMs` and report the occurrence.
        retryAfterMs:
          type: integer
          format: int64
          minimum: 0
          description: >-
            Milliseconds to wait before retrying. Matches the precise wait the
            rate limiter computed (the `Retry-After` header rounds up to the
            next whole second).
        clientId:
          type: string
          description: >
            Echo of the request's client-supplied `clientId`, so the client can
            correlate the rejection to a specific order request. Present only on
            single-order endpoints (placeOrder, modifyOrder, cancelOrder) when
            the request carried a `clientId`. Omitted on batch endpoints (see
            `clientIds`) and when the request had none.
        clientIds:
          type: array
          items:
            type: string
          description: >
            Echo of every client-supplied `clientId` in a batch request
            (batchPlaceOrders, batchCancelOrders), positionally aligned with the
            submitted `orders` / `cancels` array. A batch is rejected
            all-or-nothing, so every listed order was rejected; the echo lets a
            client submitting multiple batches concurrently correlate the 429 to
            a specific batch. An entry is empty when that element carried no
            `clientId` (e.g. a cancel-by-orderId). Omitted on single-order
            endpoints (see `clientId`).
  responses:
    TooManyRequests:
      description: >
        Rate limit exceeded. Two signals are returned, designed to coexist with
        both naive HTTP clients and rate-limit-aware SDK clients:


        - `Retry-After` header (integer seconds, RFC 7231 compliant). Rounds up
        — clients that obey this header sleep at least as long as required. Safe
        for generic HTTP libraries to read.

        - JSON body `retryAfterMs` (precise milliseconds, matches the server's
        internal computation). Sophisticated clients should prefer this over the
        header to avoid the round-up overshoot.

        - JSON body `reason` indicates which rate-limit layer rejected: `ip`
        (per-IP weight bucket), `account_empty` (per-subaccount pool fully
        exhausted, in drip throttle), `account_partial` (pool has some credit
        but not enough for this batch — split the request smaller to drain), or
        `unknown` (defensive fallback).

        - JSON body `clientId` (single-order endpoints) or `clientIds` (batch
        endpoints) echoes the request's client-supplied order identifier(s) so
        the client can correlate the rejection to a specific order request.


        Allowlisted addresses bypass the rate-limit middleware entirely (see
        `RATELIMIT_BYPASS_ADDRESSES`); they never receive a 429 from this layer.
      headers:
        Retry-After:
          $ref: '#/components/headers/RetryAfter'
      content:
        application/json:
          schema:
            $ref: '#/components/schemas/RateLimitedError'
          example:
            error: rate limited
            reason: account_empty
            retryAfterMs: 850
            clientId: my-order-42
  headers:
    RetryAfter:
      description: Seconds the client should wait before retrying the request.
      schema:
        type: integer
        minimum: 1
        example: 1
  securitySchemes:
    apiKey:
      type: apiKey
      in: header
      name: X-API-Key
      description: >
        Hex-encoded Ed25519 public key (64 chars). The public key IS the API key
        — register it via `POST /createApiKey`. Required on every authenticated
        request, both read-only and signed.
    timestamp:
      type: apiKey
      in: header
      name: X-Timestamp
      description: >
        Unix time in **nanoseconds** as a decimal string (e.g.
        `"1713825891591000000"`). Millisecond or second epochs are rejected with
        `401 Unauthorized`. Must be within ±30,000 ms (`MaxTimestampDriftMs`,
        the drift window stays configured in milliseconds) of server wall-clock,
        or the request is rejected with `401 Unauthorized`. Required on all
        mutating / credential-creating endpoints. This same value must appear as
        the `ct` field in the ordersign typed canonical payload (single-order
        endpoints) or in each element's `ct` field (batch endpoints).
    signature:
      type: apiKey
      in: header
      name: X-Signature
      description: >
        Lowercase hex-encoded Ed25519 signature (128 chars).


        **Single-order endpoints** (`placeOrder`, `cancelOrder`, `modifyOrder`,
        and other non-batch mutating routes) sign over the **ordersign typed
        canonical payload** — a compact, key-sorted JSON object built from
        parsed request fields using engine-native integer values:


        ```

        placeOrder:  
        {"ad":"0x…","ai":N,[,"c":"…"],"ct":N,"g":N,"m":N,"op":1,"p":N,"q":N,"r":0|1,"s":N,"t":N,"v":1}

        cancelOrder: 
        {"ad":"0x…","ai":N,[,"c":"…"],"ct":N,[,"id":"…"],"m":N,"op":2,"v":1}

        modifyOrder: 
        {"ad":"0x…","ai":N,[,"c":"…"],"ct":N,[,"id":"…"],"m":N,"op":3,"p":N,"q":N,"v":1}

        ```


        `ct` must equal the `X-Timestamp` header value. Keys in brackets are
        conditional (omitted when empty). `op` values: `1`=place, `2`=cancel,
        `3`=modify. See the `ordersign` package for field definitions and
        reference signing code.


        Other signed routes (e.g. `createApiKey`, `tokens`, `userPreferences`)
        still use the legacy scheme: `signing_message = X-Timestamp + ACTION +
        canonicalJSON(body)`, where `ACTION` is the camelCase final path
        segment.


        **Batch endpoints (`batchPlaceOrders`, `batchCancelOrders`) do NOT use
        this header.** They authenticate with per-element typed ordersign
        signatures embedded in the request body (see the global auth description
        and the per-field `signature` descriptions on `OrderRequest` /
        `CancelOrderRequest`).


        Read endpoints are authenticated by `?address=` (and optionally
        `X-API-Key`) only — no signature is required. `canonicalJSON(body)` is
        the JSON body with object keys sorted lexicographically at every level
        and no whitespace; the server canonicalizes the received body before
        verifying, so only the bytes signed over must be canonical. Required on
        all mutating / credential-creating endpoints.

````