> ## 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 batch orders

> Place up to 100 orders in a single request.

Place up to 100 orders in a single request. Requires `address` query parameter matching the master Ethereum address for `X-API-Key` (same as single `POST /placeOrder`).

**Signed per order.** This endpoint does NOT use the `X-Signature` header. Each element of `orders` carries its own `signature` over the **ordersign typed canonical payload** for that order (`op` 1) — the same bytes a standalone `placeOrder` signs; the shared `X-API-Key` + `X-Timestamp` headers authenticate the batch. The whole batch consumes a single replay slot. The top-level `grouping` is not individually signed.

### Response behavior

**Asynchronous.** The endpoint returns either `202 Accepted` (the common case — the batch was forwarded to the matching engine and per-order rows do not carry terminal state) or `200 OK` (the gateway already had definitive state for the batch by the time it responded; per-order `status` reflects that state). In both cases each row echoes the accepted `orderId` / `clientId` so subscribers can correlate WebSocket events with the request. To observe each order's full lifecycle (`OPEN`, `FILLED`, `CANCELED`, `REJECTED`, fills, etc.) clients **must** subscribe to the `orders` WebSocket channel (and `userFills` for trade-level events). Per-order validation failures are still returned synchronously as `status: ERROR` with an `error` message in the corresponding `responses[]` entry; valid rows are forwarded to the matching engine (partial success).

### TPSL groupings

Set the top-level `grouping` field to submit a take-profit / stop-loss bundle instead of plain orders. The three grouping modes are:

#### partialTpsl

Place 1–2 TPSL legs sized to a user-specified quantity. Every order must set `tpslType` (`STOP_LOSS` or `TAKE_PROFIT`) and `stopPrice`. Two legs must be one of each trigger type.

```json
{
  "grouping": "partialTpsl",
  "orders": [
    { "tpslType": "TAKE_PROFIT", "stopPrice": "70000.0", "reduceOnly": true, ... },
    { "tpslType": "STOP_LOSS",   "stopPrice": "55000.0", "reduceOnly": true, ... }
  ]
}
```

#### positionTpsl

Same shape as `partialTpsl` but the engine resizes each leg to close the account's **full open position** at trigger time (`isPositionTPSL` is implicitly true). Use `quantity: "0"` on each leg; the engine replaces it at trigger. At most one TP and one SL may be active per account+market — a second placement is rejected with `POSITION_TPSL_ALREADY_EXISTS`.

```json
{
  "grouping": "positionTpsl",
  "orders": [
    { "tpslType": "TAKE_PROFIT", "stopPrice": "70000.0", "quantity": "0", "reduceOnly": true, ... },
    { "tpslType": "STOP_LOSS",   "stopPrice": "55000.0", "quantity": "0", "reduceOnly": true, ... }
  ]
}
```

#### entryTpsl

Submit an entry order together with 1–2 bound TPSL children in a single atomic bundle (2–3 orders total). The entry leg has no `tpslType`; each child leg sets `tpslType`. When the entry is canceled, all child TPSL legs are automatically cascade-canceled.

```json
{
  "grouping": "entryTpsl",
  "orders": [
    { "orderType": "LIMIT", "price": "60000.0", "quantity": "0.1", ... },
    { "tpslType": "TAKE_PROFIT", "stopPrice": "70000.0", "quantity": "0.1", "reduceOnly": true, ... },
    { "tpslType": "STOP_LOSS",   "stopPrice": "55000.0", "quantity": "0.1", "reduceOnly": true, ... }
  ]
}
```

### SBE wire encoding

Plain batches (`grouping: na`) are encoded as `PlaceOrder` messages (template 25), one per order. TPSL batches are encoded as a single `PlaceTPSL` message (template 26) carrying the full bundle.


## OpenAPI

````yaml /api-reference/openapi.yml post /v1/batchPlaceOrders
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/batchPlaceOrders:
    post:
      tags:
        - Exchange
      summary: Place batch orders
      description: Place up to 100 orders in a single request.
      operationId: placeBatchOrders
      parameters:
        - $ref: '#/components/parameters/AddressQuery'
      requestBody:
        required: true
        content:
          application/json:
            schema:
              $ref: '#/components/schemas/BatchOrdersRequest'
            examples:
              plainBatch:
                summary: Plain limit orders (no TPSL)
                value:
                  orders:
                    - address: '0xAbCdEf1234567890AbCdEf1234567890AbCdEf12'
                      marketId: 1
                      accountIndex: 0
                      orderSide: BUY
                      orderType: LIMIT
                      quantity: '0.1'
                      price: '60000.0'
                      timeInForce: GTT
                      goodTilTime: '4102444800000000'
                      timestamp: 1713825891591
                    - address: '0xAbCdEf1234567890AbCdEf1234567890AbCdEf12'
                      marketId: 1
                      accountIndex: 0
                      orderSide: SELL
                      orderType: LIMIT
                      quantity: '0.05'
                      price: '65000.0'
                      timeInForce: GTT
                      goodTilTime: '4102444800000000'
                      timestamp: 1713825891592
              partialTpslPair:
                summary: 'partialTpsl: TP+SL pair for a specific quantity'
                value:
                  grouping: partialTpsl
                  orders:
                    - address: '0xAbCdEf1234567890AbCdEf1234567890AbCdEf12'
                      marketId: 1
                      accountIndex: 0
                      orderSide: SELL
                      orderType: MARKET
                      quantity: '0.1'
                      price: '0'
                      timeInForce: IOC
                      tpslType: TAKE_PROFIT
                      stopPrice: '70000.0'
                      reduceOnly: true
                      timestamp: 1713825891591
                    - address: '0xAbCdEf1234567890AbCdEf1234567890AbCdEf12'
                      marketId: 1
                      accountIndex: 0
                      orderSide: SELL
                      orderType: MARKET
                      quantity: '0.1'
                      price: '0'
                      timeInForce: IOC
                      tpslType: STOP_LOSS
                      stopPrice: '55000.0'
                      reduceOnly: true
                      timestamp: 1713825891592
              positionTpslPair:
                summary: 'positionTpsl: TP+SL that closes the full open position'
                value:
                  grouping: positionTpsl
                  orders:
                    - address: '0xAbCdEf1234567890AbCdEf1234567890AbCdEf12'
                      marketId: 1
                      accountIndex: 0
                      orderSide: SELL
                      orderType: MARKET
                      quantity: '0'
                      price: '0'
                      timeInForce: IOC
                      tpslType: TAKE_PROFIT
                      stopPrice: '70000.0'
                      reduceOnly: true
                      timestamp: 1713825891591
                    - address: '0xAbCdEf1234567890AbCdEf1234567890AbCdEf12'
                      marketId: 1
                      accountIndex: 0
                      orderSide: SELL
                      orderType: MARKET
                      quantity: '0'
                      price: '0'
                      timeInForce: IOC
                      tpslType: STOP_LOSS
                      stopPrice: '55000.0'
                      reduceOnly: true
                      timestamp: 1713825891592
              entryTpslBundle:
                summary: 'entryTpsl: entry order + bound TP+SL children'
                value:
                  grouping: entryTpsl
                  orders:
                    - address: '0xAbCdEf1234567890AbCdEf1234567890AbCdEf12'
                      marketId: 1
                      accountIndex: 0
                      orderSide: BUY
                      orderType: LIMIT
                      quantity: '0.1'
                      price: '60000.0'
                      timeInForce: GTT
                      goodTilTime: '4102444800000000'
                      clientId: entry-001
                      timestamp: 1713825891591
                    - address: '0xAbCdEf1234567890AbCdEf1234567890AbCdEf12'
                      marketId: 1
                      accountIndex: 0
                      orderSide: SELL
                      orderType: MARKET
                      quantity: '0.1'
                      price: '0'
                      timeInForce: IOC
                      tpslType: TAKE_PROFIT
                      stopPrice: '70000.0'
                      reduceOnly: true
                      timestamp: 1713825891592
                    - address: '0xAbCdEf1234567890AbCdEf1234567890AbCdEf12'
                      marketId: 1
                      accountIndex: 0
                      orderSide: SELL
                      orderType: MARKET
                      quantity: '0.1'
                      price: '0'
                      timeInForce: IOC
                      tpslType: STOP_LOSS
                      stopPrice: '55000.0'
                      reduceOnly: true
                      timestamp: 1713825891593
      responses:
        '200':
          description: >
            Batch processed and the gateway already has definitive state for the
            rows. Per-order `status` reflects that state. Treat as best-effort
            enrichment of the `202` path; the `orders` WebSocket channel is
            still the source of truth.
          content:
            application/json:
              schema:
                $ref: '#/components/schemas/BatchOrdersResponse'
        '202':
          description: >
            Batch accepted by the gateway and forwarded to the matching engine.
            May include per-item validation errors while other items are
            forwarded. Definitive per-order state is delivered via the `orders`
            WebSocket channel, which clients must subscribe to.
          content:
            application/json:
              schema:
                $ref: '#/components/schemas/BatchOrdersResponse'
        '400':
          description: >
            Request-level validation failure (returned before any orders are
            processed). Per-order field errors are returned inline as `status:
            ERROR` rows in `responses[]` even for `200`/`202` responses.
            Structured rejections carry `errorSource` and `errorType`:


            | `errorType`       | Cause |

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

            | `InvalidRequest`  | A field failed validation (bad address,
            unknown market, invalid enum, etc.). TPSL-specific request-level
            causes: `grouping` is not a valid enum; `partialTpsl`/`positionTpsl`
            requires 1–2 orders and every order must set `tpslType` — no
            duplicate `STOP_LOSS` or `TAKE_PROFIT` legs; `entryTpsl` requires
            exactly one entry order (no `tpslType`) plus 1–2 TPSL legs with no
            duplicate trigger type. Per-row TPSL causes (surfaced in
            `responses[i].error`): `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 price. 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. |


            Note: `POSITION_TPSL_ALREADY_EXISTS` and
            `ENTRY_TPSL_CANNOT_BE_POSITION_TPSL` are engine-level rejects
            delivered asynchronously as `status: REJECTED` rows in `responses[]`
            (see `EngineRejectCode`), not as HTTP 400.
          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: []
        - {}
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:
    BatchOrdersRequest:
      type: object
      required:
        - orders
      description: >
        Request body for `POST /batchPlaceOrders`. Supports plain limit/market
        orders as well as three TPSL grouping modes controlled by the top-level
        `grouping` field.


        **Plain batch** (`grouping` omitted or `"na"`) Send up to 100 standard
        `LIMIT` or `MARKET` orders.


        **TPSL groupings** — set `grouping` to one of `partialTpsl`,
        `positionTpsl`, or `entryTpsl` to submit a trigger-order bundle. Shape
        constraints are enforced at the request level:


        - `partialTpsl` — 1 or 2 TPSL legs; every order must set `tpslType`. Two
        legs must be one `STOP_LOSS` and one `TAKE_PROFIT`. The order size is
        user-controlled.

        - `positionTpsl` — same shape as `partialTpsl` but the engine resizes
        each leg to the full open position at trigger time. `isPositionTPSL` is
        implicitly true.

        - `entryTpsl` — 2 or 3 orders: exactly one entry leg (no `tpslType`)
        plus 1–2 TPSL child legs. The TPSL children are automatically
        cascade-canceled when the entry is canceled.
      properties:
        grouping:
          $ref: '#/components/schemas/BatchOrderGrouping'
        orders:
          type: array
          description: |
            Array of orders to place. Length constraints depend on `grouping`:
            - `na` (default): 1–100 orders.
            - `partialTpsl` / `positionTpsl`: 1–2 TPSL orders.
            - `entryTpsl`: 2–3 orders (1 entry + 1–2 TPSL children).
          minItems: 1
          maxItems: 100
          items:
            $ref: '#/components/schemas/OrderRequest'
    BatchOrdersResponse:
      type: object
      required:
        - responses
      properties:
        responses:
          type: array
          items:
            $ref: '#/components/schemas/BatchOrderItemResponse'
        rateLimit:
          $ref: '#/components/schemas/AccountRateLimit'
          description: >
            Per-subaccount order-pool rate-limit snapshot after charging the
            whole batch (one snapshot for the request, not per row). 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`.
    BatchOrderGrouping:
      type: string
      enum:
        - na
        - partialTpsl
        - positionTpsl
        - entryTpsl
      description: >
        Classifies a `batchPlaceOrders` submission so the gateway can route and
        validate it correctly. Omit or set to `"na"` for a plain (non-TPSL)
        batch.


        | Value | Description |

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

        | `na` | Default. Plain limit/market orders only; no TPSL routing. |

        | `partialTpsl` | 1–2 TPSL legs sized to a specific quantity
        (user-chosen partial position close). Every order must set `tpslType`. |

        | `positionTpsl` | 1–2 TPSL legs that close the full open position at
        trigger time (`isPositionTPSL` forced true). Every order must set
        `tpslType`. At most one TP and one SL per account+market. |

        | `entryTpsl` | 2–3 orders: exactly one entry order (no `tpslType`) plus
        1–2 bound TPSL child legs (`tpslType` set). The TPSL children are
        cascade-canceled if the entry is canceled. |
    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
    BatchOrderItemResponse:
      type: object
      required:
        - orderId
        - clientTime
        - marketId
        - marketDisplayName
      description: >
        Per-order row returned by `POST /batchPlaceOrders`. Semantics match
        `OrderResponse`: on the `202` path each accepted row has `status: ACK`
        and no enrichment fields; on the `200` path `status` is the definitive
        go-core state and enrichment fields (`updatedAt`, `remainingSize`,
        `filledSize`, `rejectionReason`) may be populated. Rows that failed
        validation are returned synchronously with `status: ERROR` and an
        `error` message. Definitive per-order state is delivered via the
        `orders` WebSocket channel, which clients must subscribe to.
      properties:
        orderId:
          type: string
          description: Server-generated order ID (hex string).
        clientTime:
          type: integer
          format: int64
          description: Timestamp in epoch microseconds.
        marketId:
          $ref: '#/components/schemas/MarketId'
        marketDisplayName:
          $ref: '#/components/schemas/MarketDisplayName'
        status:
          $ref: '#/components/schemas/order-status'
        updatedAt:
          type: integer
          format: int64
          description: >-
            Epoch microseconds of the definitive go-core state on the `200`
            path. Omitted on `202` — see the `orders` WebSocket channel.
        remainingSize:
          type: string
          description: >-
            Unfilled size at `updatedAt` on the `200` path. Omitted on `202` —
            see the `orders` WebSocket channel.
        filledSize:
          type: string
          description: >-
            Cumulative filled size at `updatedAt` on the `200` path. Omitted on
            `202` — see the `orders` WebSocket channel.
        rejectionReason:
          $ref: '#/components/schemas/RejectionReason'
          description: >-
            Machine-readable reason when `status` is `REJECTED` on the `200`
            path. Otherwise see the `orders` WebSocket channel.
        error:
          type: string
          description: Present when this batch item failed validation (status ERROR).
        goodTilTime:
          type: string
          description: >
            Expiration timestamp in epoch microseconds (as string), echoed from
            the request. Present for GTD/ALO orders; absent for GTC/IOC/FOK
            orders.
    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`).
    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.
  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).

````