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

# Sell spot collateral to settle the account's loan

> Voluntarily settle an outstanding loan balance by selling spot collateral to the exchange's liquidity provider at the mark price minus a configured spread (25 bps by default).

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

Voluntarily settle an outstanding loan balance by selling spot collateral to the exchange's liquidity provider at the mark price minus a configured spread (25 bps by default). The sale proceeds land as account cash, which pays down the loan (and any accrued interest) immediately — the way out of a loan that remains after closing perp positions at a loss, without depositing external funds or waiting for liquidation.

Requires `address` query parameter or JSON `address` field (must match `X-API-Key`'s master if both are present). `accountIndex` is required in the signed JSON body (default `0`); it may also be sent as `?accountIndex=`. If both are present they must match.

`maxQuantity` (asset units) and `minPrice` (USD per unit) are decimal strings. Both are explicit, signed bounds: the engine sells at most `maxQuantity`, never below `minPrice`, and never more than the loan (plus accrued interest) needs at the execution price — so a client settling in full can safely overshoot the quantity and rely on the debt cap, and any price improvement passes through to the account. Partial fills are possible when the buyer can only absorb part; compare `executedQuantity` in the response.

### Response behavior

**Asynchronous.** The endpoint returns `200 OK` (engine executed the settlement — check `executedQuantity`, `repaidAmount`, `newLoanBalance`), `422 Unprocessable Entity` (engine rejected — see `rejectReason`), or `202 Accepted` (request forwarded but the confirmation didn't arrive within the timeout; the engine may still execute — observe the account WebSocket channels to confirm).


## OpenAPI

````yaml /api-reference/openapi.yml post /v1/settleLoan
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/settleLoan:
    post:
      tags:
        - Exchange
      summary: Sell spot collateral to settle the account's loan
      description: >-
        Voluntarily settle an outstanding loan balance by selling spot
        collateral to the exchange's liquidity provider at the mark price minus
        a configured spread (25 bps by default).
      operationId: settleLoan
      parameters:
        - $ref: '#/components/parameters/AddressQuery'
        - $ref: '#/components/parameters/AccountIndexQuery'
      requestBody:
        required: true
        content:
          application/json:
            schema:
              $ref: '#/components/schemas/SettleLoanRequest'
      responses:
        '200':
          description: >-
            Engine executed the settlement (`status: APPLIED`) — possibly a
            partial fill; compare `executedQuantity` to `maxQuantity`.
          content:
            application/json:
              schema:
                $ref: '#/components/schemas/SettleLoanResponse'
        '202':
          description: >
            Request forwarded to the matching engine but the confirmation didn't
            arrive within the timeout (`status: ACK`). The engine may still
            execute the settlement; subscribe to the account WebSocket channels
            to confirm the post-state.
          content:
            application/json:
              schema:
                $ref: '#/components/schemas/SettleLoanResponse'
        '400':
          description: >-
            Validation error (missing/invalid address, invalid `accountIndex`,
            invalid `spotAssetId`, or invalid maxQuantity/minPrice).
          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, or `accountIndex`
            does not match.
          content:
            application/json:
              schema:
                $ref: '#/components/schemas/error'
        '422':
          description: >
            Engine rejected the settlement (`status: REJECTED`) — see
            `rejectReason`.
          content:
            application/json:
              schema:
                $ref: '#/components/schemas/SettleLoanResponse'
        '429':
          $ref: '#/components/responses/TooManyRequests'
        '500':
          description: Internal server error.
          content:
            application/json:
              schema:
                $ref: '#/components/schemas/error'
      security:
        - apiKey: []
          timestamp: []
          signature: []
        - {}
components:
  parameters:
    AddressQuery:
      name: address
      in: query
      required: true
      schema:
        $ref: '#/components/schemas/EthereumAddressHex'
      description: >
        Master Ethereum address for this API key (must match `address` from
        `POST /createApiKey` for the same key). Required on REST for
        account-scoped reads and for place/cancel. Invalid hex → 400; mismatch
        with key → 403.
    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:
    SettleLoanRequest:
      type: object
      required:
        - address
        - spotAssetId
        - maxQuantity
        - minPrice
      properties:
        address:
          $ref: '#/components/schemas/EthereumAddressHex'
          description: >-
            Master EVM address for this account. May also be supplied via the
            `?address=` query parameter; if both are present they must match.
            When `X-API-Key` is enforced the body / query address must match the
            key's master, otherwise the request is rejected with HTTP 403.
        spotAssetId:
          type: integer
          minimum: 1
          description: >-
            The spot collateral market to sell (`1` = SPY, `2` = TSLA) — the
            same `spotAssetId` namespace `/v1/spotAssets` and spot deposits /
            withdrawals use, NOT a perp market id. `0` (USDG) is invalid: the
            quote currency is not sellable collateral.
        maxQuantity:
          type: string
          description: >-
            Maximum asset quantity to sell, as a decimal string in asset units
            (e.g. `"2.5"` for 2.5 shares). Must be positive and a multiple of
            the asset's quantum. This is an explicit signed upper bound — there
            is no "settle everything" sentinel. The engine executes the minimum
            of this quantity, what the loan (plus accrued interest) needs at the
            execution price, the account's holding, and the buyer's capacity —
            so overshooting the debt is safe (execution is capped, reduce-only
            style), partial fills are possible (compare `executedQuantity` in
            the response), and any price improvement passes through to the
            account.
          examples:
            - '2.5'
        minPrice:
          type: string
          description: >-
            Lowest acceptable per-unit execution price, as a decimal USD string.
            Must be positive and a multiple of the asset's tick size. The engine
            executes at the spread-discounted mark (`mark × (1 − settleSpread)`,
            spread 25 bps by default); if that price falls below `minPrice` the
            request is rejected with `PRICE_BELOW_MIN_PRICE`. This is the
            limit-price analog: it pins the signed intent against mark moves
            between sign and execute.
          examples:
            - '498.75'
        accountIndex:
          type: integer
          minimum: 0
          maximum: 9
          default: 0
          description: >-
            Subaccount index (0–9). Required in the signed body; omit or send
            `0` for the primary account. May also be supplied via the
            `?accountIndex=` query parameter; if both are present they must
            match. When `X-API-Key` is enforced the index must match the key's
            authorized subaccount, otherwise the request is rejected with HTTP
            403.
    SettleLoanResponse:
      type: object
      required:
        - requestId
        - address
        - accountIndex
        - spotAssetId
        - maxQuantity
        - minPrice
        - executedQuantity
        - executionPrice
        - proceeds
        - repaidAmount
        - newLoanBalance
        - status
      properties:
        requestId:
          type: string
          description: >-
            Server-generated UUID identifying this request in subsequent
            WebSocket events.
        address:
          $ref: '#/components/schemas/EthereumAddressHex'
        accountIndex:
          $ref: '#/components/schemas/AccountIndex'
        spotAssetId:
          type: integer
          description: The spot collateral market sold, echoed from the request.
        maxQuantity:
          type: string
          description: >-
            The requested maximum quantity, echoed back as a decimal string in
            asset units.
          examples:
            - '2.5'
        minPrice:
          type: string
          description: The requested price floor, echoed back as a decimal USD string.
          examples:
            - '498.75'
        executedQuantity:
          type: string
          description: >-
            Asset quantity actually sold, as a decimal string in asset units. At
            most `maxQuantity`, and less when the loan needed less, the holding
            was smaller, or the buyer could only absorb part (a partial fill).
            `"0"` on `ACK` and `REJECTED`.
          examples:
            - '1.5'
            - '0'
        executionPrice:
          type: string
          description: >-
            The spread-discounted per-unit price paid, as a decimal USD string.
            `"0"` on `ACK` and `REJECTED`.
          examples:
            - '498.75'
            - '0'
        proceeds:
          type: string
          description: >-
            USDG credited to the account (`executedQuantity × executionPrice`),
            in dollars as a decimal string. `"0"` on `ACK` and `REJECTED`.
          examples:
            - '748.5'
            - '0'
        repaidAmount:
          type: string
          description: >-
            The loan reduction in dollars as a decimal string, including accrued
            interest settled by the same event. `"0"` on `ACK` and `REJECTED`.
          examples:
            - '748.5'
            - '0'
        newLoanBalance:
          type: string
          description: >-
            The post-settlement loan balance in dollars as a decimal string.
            `"0"` on `ACK` (engine not yet confirmed); the unchanged balance on
            `REJECTED`.
          examples:
            - '1.5'
            - '0'
        status:
          $ref: '#/components/schemas/SettleLoanStatus'
        rejectReason:
          $ref: '#/components/schemas/SettleLoanRejectReason'
    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`.
    AccountIndex:
      type: integer
      minimum: 0
      maximum: 9
      description: >-
        Account index (account index, 0–9). Identifies the account for orders,
        positions, fills, and API keys.
    SettleLoanStatus:
      type: string
      enum:
        - ACK
        - APPLIED
        - REJECTED
      description: >
        Engine-lifecycle marker for the settleLoan response body. Distinct from
        the HTTP status code, which is a transport-level signal.


        - `ACK` (HTTP 202): the request was accepted; the engine has not yet
        confirmed. **Not** a failure — subscribe to the account WebSocket
        channels for the engine-confirmed settlement.

        - `APPLIED` (HTTP 200): engine executed the settlement — possibly a
        partial fill; compare `executedQuantity` to `maxQuantity`.

        - `REJECTED` (HTTP 422): engine rejected the settlement; see
        `rejectReason`.
    SettleLoanRejectReason:
      type: string
      enum:
        - FEATURE_DISABLED
        - UNKNOWN_MARKET
        - MISSING_MARK_PRICE
        - INVALID_QUANTITY
        - INVALID_MIN_PRICE
        - NO_LOAN
        - ACCOUNT_IN_WIND_DOWN
        - NO_HOLDING
        - PRICE_BELOW_MIN_PRICE
        - NO_BUYER
      description: >
        Engine-side rejection reason, present on HTTP 422 responses:


        - `FEATURE_DISABLED`: loan settlement is not enabled on this deployment.

        - `UNKNOWN_MARKET`: `spotAssetId` is not an allocated spot collateral
        market.

        - `MISSING_MARK_PRICE`: the asset has no live mark price (a transient
        oracle gap), so the settlement cannot be priced. Retry once pricing
        resumes.

        - `INVALID_QUANTITY`: `maxQuantity` was non-positive or malformed at the
        engine boundary.

        - `INVALID_MIN_PRICE`: `minPrice` was non-positive or malformed at the
        engine boundary.

        - `NO_LOAN`: the account has no outstanding loan balance to settle.

        - `ACCOUNT_IN_WIND_DOWN`: the account is in post-liquidation wind-down;
        its estate resolves through the liquidation machinery and cannot be
        voluntarily settled.

        - `NO_HOLDING`: the account holds none of the named asset.

        - `PRICE_BELOW_MIN_PRICE`: the spread-discounted execution price fell
        below the signed `minPrice` floor. Nothing was sold; re-sign at a lower
        floor or wait for the mark to recover.

        - `NO_BUYER`: no liquidity provider is currently able to buy the asset
        (none configured for the segment, or insufficient capacity). Nothing was
        sold; retry later.
    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
  securitySchemes:
    apiKey:
      type: apiKey
      in: header
      name: X-API-Key
      description: >
        Hex-encoded Ed25519 public key (64 chars). The public key IS the API key
        — register it via `POST /createApiKey`. Required on every authenticated
        request, both read-only and signed.
    timestamp:
      type: apiKey
      in: header
      name: X-Timestamp
      description: >
        Unix time in **nanoseconds** as a decimal string (e.g.
        `"1713825891591000000"`). Millisecond or second epochs are rejected with
        `401 Unauthorized`. Must be within ±30,000 ms (`MaxTimestampDriftMs`,
        the drift window stays configured in milliseconds) of server wall-clock,
        or the request is rejected with `401 Unauthorized`. Required on all
        mutating / credential-creating endpoints. This same value must appear as
        the `ct` field in the ordersign typed canonical payload (single-order
        endpoints) or in each element's `ct` field (batch endpoints).
    signature:
      type: apiKey
      in: header
      name: X-Signature
      description: >
        Lowercase hex-encoded Ed25519 signature (128 chars).


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


        ```

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

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

        modifyOrder: 
        {"ad":"0x…","ai":N,[,"c":"…"],"ct":N,"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` must equal the `X-Timestamp` header value. Keys in brackets are
        conditional (omitted when empty). `op` values: `1`=place, `2`=cancel,
        `3`=modify. See the `ordersign` package for field definitions and
        reference signing code.


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


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


        Read endpoints are authenticated by `?address=` (and optionally
        `X-API-Key`) only — no signature is required. The one exception is `GET
        /v1/affiliate/inviteCodes`, which returns bearer secrets and therefore
        requires the full header triple; with no body its signing message is
        `X-Timestamp + ACTION`. `canonicalJSON(body)` is the JSON body with
        object keys sorted lexicographically at every level and no whitespace;
        the server canonicalizes the received body before verifying, so only the
        bytes signed over must be canonical. Required on all mutating /
        credential-creating endpoints.

````