> ## 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.

# Get fill by ID

> Returns the single fill belonging to the requested account for the given trade ID. Requires the `address` query parameter (optional `accountIndex` selects a subaccount, default 0): fill data is partitioned by account, so the lookup needs the owning account. A trade can produce one fill per participant that share a `tradeId`; this returns the leg owned by the requested account. No authentication header is required.


<Note>**The response body shown here is a static example, not live data.** After you click **Send**, your live result appears in a separate panel headed **200 OK**. The panel under the status-code tabs is a fixed sample from the spec — its field values (fees, prices, sizes, IDs, timestamps) are placeholders. Use **Send**, or call the endpoint, for current values.</Note>

Returns the single fill belonging to the requested account for the given trade ID. Requires the `address` query parameter (optional `accountIndex` selects a subaccount, default 0): fill data is partitioned by account, so the lookup needs the owning account. A trade can produce one fill per participant that share a `tradeId`; this returns the leg owned by the requested account. No authentication header is required.


## OpenAPI

````yaml /api-reference/openapi.yml get /v1/fill/{tradeId}
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.


    - **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,"g":N,[,"id":"…"],"m":N,"op":3,"p":N,"q":N,"r":0|1,"s":N,"t":N,"v":1}   (exactly one of id / c)
      ```

    `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 still require this header to be present** (its value is
    not verified) — see below.

    - **Batch endpoints are signed per order, but the `X-Signature` header must
    still be present.** `POST /batchPlaceOrders`, `POST /batchCancelOrders`, and
    `POST /batchModifyOrders` require `X-API-Key` + `X-Timestamp` +
    `X-Signature`; the gateway does **not** verify the envelope `X-Signature`
    *value* for these routes (set it to any one element's signature), but
    omitting it — or sending an empty value — rejects every element with
    `invalid order signature`. Instead, each element of the `orders` / `cancels`
    / `modifies` 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`.
        - `modifies` elements use `op=3` — the same payload as a standalone `modifyOrder`.

    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` / `batchModifyOrders` use the same value in the JSON payload
    field `address`.


    ## Order execution is asynchronous

    `POST /placeOrder`, `POST /cancelOrder`, `POST /batchPlaceOrders`, `POST
    /batchCancelOrders`, and `POST /batchModifyOrders` 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.


    ## Order rejection reasons

    When the matching engine 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 the matching engine 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.
servers:
  - url: https://api.arcus.xyz
    description: Mainnet
  - url: https://api.testnet.arcus.xyz
    description: Testnet
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.


      Most read endpoints (`GET`) are signed too, and `?address=` must match the
      API key's master Ethereum address — so a caller only ever reads their own
      referral data. That covers `/inviteCodes` (invite codes are bearer
      secrets) plus `/info`, `/code`, `/referees` and `/commissions`: between
      them those four made a referral code → wallet reverse index constructible
      by anyone, and a referral code is meant to be shareable without disclosing
      the wallet behind it.


      Still public and scoped by `?address=`: `/myReferrer` (returns the
      binding's code and discount, never the referrer's address), `/claims`,
      `/claimStatus`, `/codeAvailable` (returns only whether a code is taken,
      never its owner) and `/leaderboard`.
  - 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 served by a separate
      deployment with its own storage and 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.
paths:
  /v1/fill/{tradeId}:
    get:
      tags:
        - Public
      summary: Get fill by ID
      description: >
        Returns the single fill belonging to the requested account for the given
        trade ID. Requires the `address` query parameter (optional
        `accountIndex` selects a subaccount, default 0): fill data is
        partitioned by account, so the lookup needs the owning account. A trade
        can produce one fill per participant that share a `tradeId`; this
        returns the leg owned by the requested account. No authentication header
        is required.
      operationId: getFillById
      parameters:
        - name: tradeId
          in: path
          required: true
          schema:
            type: string
            example: '12345'
          description: >
            Trade identifier of the fill (the `tradeId` field returned by `GET
            /v1/fills`). A non-integer value → 400.
        - $ref: '#/components/parameters/AddressQuery'
        - $ref: '#/components/parameters/AccountIndexQuery'
      responses:
        '200':
          description: Fill details.
          content:
            application/json:
              schema:
                $ref: '#/components/schemas/fill'
        '400':
          description: >-
            Invalid tradeId, or missing/invalid `address` or `accountIndex`
            query parameter.
          content:
            application/json:
              schema:
                $ref: '#/components/schemas/error'
        '404':
          description: Fill not found for this account and trade ID.
          content:
            application/json:
              schema:
                $ref: '#/components/schemas/error'
        '429':
          $ref: '#/components/responses/TooManyRequests'
      x-codeSamples:
        - lang: Shell
          label: curl
          source: |
            ADDR=0x742d35Cc6634C0532925a3b844Bc9e7595f0bEb
            TRADE_ID=12345
            curl "https://api.arcus.xyz/v1/fill/${TRADE_ID}?address=${ADDR}"
        - lang: Python
          label: requests
          source: |
            import requests
            tradeId = "12345"
            r = requests.get(
                f"https://api.arcus.xyz/v1/fill/{tradeId}",
                params={"address": "0x742d35Cc6634C0532925a3b844Bc9e7595f0bEb"},
            )
            print(r.json())
        - lang: TypeScript
          label: fetch
          source: >
            const tradeId = "12345";

            const url = new URL(`https://api.arcus.xyz/v1/fill/${tradeId}`);

            url.searchParams.set("address",
            "0x742d35Cc6634C0532925a3b844Bc9e7595f0bEb");

            const res = await fetch(url);

            console.log(await res.json());
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.
    AccountIndexQuery:
      name: accountIndex
      in: query
      required: false
      schema:
        type: integer
        minimum: 0
        maximum: 9
        default: 0
      description: >
        Subaccount index (0–9) to scope the request to. Defaults to 0 (the
        primary account). Values above 9 → 400.
  schemas:
    fill:
      type: object
      description: >
        Unified fill shape used across REST responses, WebSocket snapshots, and
        streaming updates. Fields only available from the store are optional and
        may be absent in streaming updates.
      required:
        - tradeId
        - orderId
        - marketDisplayName
        - marketId
        - side
        - originalSize
        - size
        - price
        - fee
        - role
        - createdAt
      properties:
        tradeId:
          type: string
          description: Unique trade identifier.
          examples:
            - '12345'
        orderId:
          type: string
          description: Order ID that generated this fill.
          examples:
            - order-abc-123
        address:
          type: string
          description: >-
            Ethereum address (lowercase 0x + 40 hex). Only present on REST and
            snapshot responses.
          examples:
            - '0x0000000000000000000000000000000000000000'
        accountIndex:
          type: integer
          minimum: 0
          maximum: 9
          description: >-
            Account index (account index, 0–9). Identifies the account for
            orders, positions, fills, and API keys.
        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.
        marketDisplayName:
          type: string
          description: Market symbol (e.g. BTC-USD).
          examples:
            - BTC-USD
        clientId:
          type: string
          description: Client-provided order ID. Only present on streaming updates.
        side:
          type: string
          enum:
            - BUY
            - SELL
          description: Order side.
        originalSize:
          type: string
          description: >-
            Original order size in human-readable base-asset units (decimal
            string).
          examples:
            - '1.0'
        size:
          type: string
          description: Fill size in human-readable base-asset units (decimal string).
          examples:
            - '0.01'
        price:
          type: string
          description: Fill price in human-readable USD (decimal string).
          examples:
            - '50000.5'
        fee:
          type: string
          description: >
            Fee amount (decimal string). On the liquidated leg of an engine
            liquidation this is the liquidation penalty (the market's
            liquidation fee rate applied to the fill notional, paid to the
            insurance fund) — "0" when the market has no liquidation fee
            configured. ADL legs carry no fee.
          examples:
            - '0.5'
        closedPnl:
          type: string
          description: >
            Realized PnL for this fill (decimal string, quote currency):
            cost-basis release (`old borrowedCapital − new borrowedCapital −
            Δsize × price`) minus the fill's fee. Opening and same-direction add
            legs contribute `−fee` (price PnL is 0). On a flat account, Σ
            closedPnl + Σ funding equals netQuoteBalance − net deposits. On the
            liquidated leg of a forced closure (`liquidation.method ==
            LIQUIDATION`) the engine does not stamp realized PnL on the fill —
            closedPnl reads "0" by design; the realized loss is already
            reflected in the account's `netQuoteBalance` on the accompanying
            account update.
          examples:
            - '12.34'
        role:
          type: string
          enum:
            - MAKER
            - TAKER
          description: Liquidity role (maker or taker).
        remainingSize:
          type: string
          description: >-
            Remaining unfilled order size after this fill. Only present on
            streaming updates.
        positionEffect:
          type: string
          enum:
            - OPEN_LONG
            - OPEN_SHORT
            - ADD_LONG
            - ADD_SHORT
            - CLOSE_LONG
            - CLOSE_SHORT
            - FLIP_LONG_TO_SHORT
            - FLIP_SHORT_TO_LONG
          description: >
            How this fill affected the account's position in marketId. For taker
            fills the value is the net effect across all fills in the aggregated
            update (FLIP_* covers a position that closed and reopened on the
            opposite side within one user order). May be absent on persisted
            rows from before the field was introduced.
        createdAt:
          type: integer
          format: int64
          description: Fill timestamp (epoch microseconds).
        sequenceNumber:
          type: integer
          format: uint64
          description: >-
            Sequence number for ordering and reconciliation. Only present on
            streaming updates.
        liquidation:
          type: object
          description: >
            Present only when this fill closed a position involuntarily — on the
            liquidated account's leg of an engine liquidation (`LIQUIDATION`) or
            the deleveraged counterparty's leg of an auto-deleverage (`ADL`).
            The voluntary counterparty leg of a liquidation match (a resting
            maker order that got hit) does not carry this marker. Absent on
            voluntary fills and on rows persisted before the marker was
            introduced. This field, not the order-ID format, is the supported
            way to detect forced closures (the engine's synthetic `liq:`
            order-ID prefix is an internal detail and not part of the API
            contract).
          required:
            - method
            - liquidatedUser
          properties:
            method:
              type: string
              enum:
                - LIQUIDATION
                - ADL
              description: How the position was force-closed.
            liquidatedUser:
              type: string
              description: >
                Account wire ID (unpadded base64 of address + account index) of
                the account that was force-closed — always the account that owns
                this fill row.
    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. |

            | `FILL_WILL_EXCEED_TRADING_BOUND`   | Outside regular trading
            hours, the order would fill at or beyond the active off-hours
            trading bound. Per-order rejection; in-band trading continues. |

            | `OPEN_INTEREST_CAP_EXCEEDED`       | Market is at its operator-set
            open-interest cap and the order's next fill would have increased
            open interest. OI-neutral and OI-reducing orders still match. |

            | `ORDER_WILL_TAKE_LIQUIDITY_DURING_MARKET_HALT` | Deprecated — no
            longer emitted; replaced by `FILL_WILL_EXCEED_TRADING_BOUND`. |

            | `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. Under cancel-replace modify semantics the
            rejected modify is also buffered briefly and applied if the target
            placement arrives shortly after. |

            | `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. |

            | `POSITION_SIZE_CAP_EXCEEDED`       | The order, modify, or
            resulting fill would exceed the per-market position notional cap.
            Distinct from `UNDERCOLLATERALIZED`: the account may be fully funded
            — the size is the problem. |

            | `MODIFY_TPSL_NOT_SUPPORTED`        | `modifyOrder` targeted an
            untriggered TPSL order; TPSL trigger parameters cannot be changed
            via modify. A live TPSL survives unchanged; a buffered modify that
            meets an arriving TPSL placement cancels that placement as a
            precaution. |

            | `MODIFY_SUPERSEDED_BY_CANCEL`      | `modifyOrder` was discarded
            because a `cancelOrder` for the same order is pending; cancels
            always dominate modifies and the order's final state is canceled. |

            | `MODIFY_SIZE_ALREADY_FILLED`       | Under total-size modify
            semantics, `modifyOrder`'s new size (the order's new TOTAL size) is
            at or below the quantity already filled, leaving nothing to rest.
            The original order is canceled (a `CANCELED` update precedes this
            rejection). |
          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
            - FILL_WILL_EXCEED_TRADING_BOUND
            - OPEN_INTEREST_CAP_EXCEEDED
            - POSITION_SIZE_CAP_EXCEEDED
            - MODIFY_TPSL_NOT_SUPPORTED
            - MODIFY_SUPERSEDED_BY_CANCEL
            - MODIFY_SIZE_ALREADY_FILLED
    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`.
    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.
      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

````