{"openapi":"3.1.0","info":{"title":"order-management-system","description":"REST and WebSocket API for the Tplus Order Management System.\n\n## Tplus Architecture\n\nTplus is a trading system with an offchain matching layer and onchain custody. API clients enter through the Order Management System (OMS), which exposes the REST and WebSocket API, authenticates users, performs pre-trade checks, dispatches orders to the orderbook, and mirrors clearing-engine state for account, position, and registry reads.\n\nOrders match offchain in the orderbook for low-latency execution. The clearing engine (CE) is the authoritative ledger for inventory, positions, fills, funding, deposits, withdrawals, and settlements. The production security model treats CE authority as an MPC+TEE quorum running in Intel TDX TEEs; OMS, orderbook, and related services can propose state transitions, but the CE revalidates inventory and risk before finalization.\n\nOnchain custody and settlement are handled by deposit vault contracts. Deposits and withdrawals move funds between user wallets and vaults, while settlement approvals let CE-authorized flows use vault-held assets onchain. The asset and vault model is multi-chain across EVM chains such as Ethereum and Arbitrum plus Solana-style chain identifiers; clients should discover listed assets, deposit caps, risk parameters, decimals, and vault addresses through the Registry endpoints instead of hardcoding them.\n\n## Authentication\n\nAuthenticated endpoints require two headers: `Authorization: Bearer <token>` and `User-Id: <hex public key>`.\n\n1. `GET /nonce/{user_id}` - returns `{\"value\": \"<nonce>\", \"expiry_ns\": <u64>}` (valid 5 min)\n2. `POST /auth` - sign the nonce with Ed25519, submit `{\"user_id\", \"nonce\", \"signature\"}`, receive `{\"token\", \"expiry_ns\"}` (valid 24h)\n\n## Signing Orders\n\nOrders, cancels, and replaces require Ed25519 signature bytes as a JSON array of integers. Create signs the `order` object, cancel signs `cancel`, and replace signs `request`; see `/guides/signing` for preimage construction.\n\n## Sub-Account Model\n\nSub-accounts are identified by `AccountIndex` (u64):\n- **0** - Spot (deposits, spot balances)\n- **1** - Cross-margin (shared margin across positions)\n- **2+** - Isolated margin (one per asset)\n\n## Book Decimals\n\nEvery order requires `book_price_decimals` and `book_quantity_decimals` matching values from `GET /market/{asset_id}`. Mismatch triggers `MissingBookDecimals` rejection.\n\n## Asset Identifiers\n\nEvery asset is referenced by an `AssetIdentifier`, a tagged union with two variants:\n- `Index(u64)` - a protocol-listed asset. The same index spans every chain (e.g. index `0` is USD on Ethereum, Arbitrum, Solana, etc.). Use this for normal trading, balances, and markets.\n- `Address({ address: bytes32, chain: bytes9 })` - an isolated, chain-specific asset. Used when the protocol has not fungified the token across chains, or when per-chain deposit caps have been exceeded for an otherwise listed asset.\n\n**JSON encoding.** REST endpoints serialize `AssetIdentifier` as a plain string in either form:\n- Index form: `\"0\"` (the numeric index as a string).\n- Address form: `\"<32-byte hex address>@<9-byte hex chain>\"` (e.g. `\"62622e7700000000000000000000000000000000000000000000000000000000@00000000000000a4b1\"`).\n\nThe tagged-object forms `{\"Index\": 0}` and `{\"Address\": {\"address\": \"0x...\", \"chain\": \"0x...\"}}` are also accepted on input. The `chain` field is a 1-byte VM routing tag followed by an 8-byte VM chain id (so Arbitrum `42161` = `0x00000000000000a4b1`).\n\n**Where each form appears:**\n- Markets (`GET /market/{asset_id}`, order payloads): `AssetIdentifier`. Most are `Index(n)`; isolated markets carry an `Address`.\n- Balances, inventory, and positions: keyed by `AssetIdentifier`. A user may simultaneously hold an `Index(0)` balance and an `Address(USDC@Arbitrum)` balance for the same underlying token - these are **not** fungible until per-chain caps relax.\n- Withdrawals: identified by `AssetAddress` (= `ChainAddress`), since the user must specify which chain the funds leave on. The CE resolves back to an index internally if the asset is listed.\n- Deposits: produced by onchain events; the user does not specify an identifier. The deposit credits either an `Index(n)` (when the asset is fungified across chains) or an `Address(token@chain)` (when isolated) according to the registry and per-chain caps.\n\n**Implications for clients:**\n- Do not assume `Index(0)` and `Address(USDC@Ethereum)` are the same balance - they are separate ledger entries that share an underlying token.\n- For trading, prefer `Index(n)` markets where they exist; `Address(...)` markets are isolated and may have lower liquidity.\n- When displaying balances, group by underlying for a \"total\" view, but always retain the per-identifier breakdown — it is required to plan withdrawals.\n\n## Identifier & Signature Encodings\n\n- **User ids** (`User-Id` header, `{user_id}` path params, `user`/`tplus_user` fields): 64 **lowercase** hex chars encoding the 32-byte Ed25519 public key, **no 0x prefix**. Several paths compare the string exactly, so an uppercase or 0x-prefixed id fails auth (401 or an in-band WebSocket rejection) even with a valid token.\n- **Signatures** (`signature` fields on orders, cancels, replaces, transfers, withdrawals, settlement and multisig requests): a JSON **array of integers** — the 64 raw Ed25519 signature bytes — not a hex string.\n- **Order ids**: client-supplied strings, at most 24 bytes.\n- **Timestamps**: nanoseconds since the Unix epoch as `u64`. Values exceed JavaScript's 2^53 safe-integer range — parse as BigInt/strings, not doubles.\n\n## Units & Amount Encodings\n\nRead each amount field's description carefully — two different `U256` string codecs are in use:\n\n- **Hex-encoded integer strings** (parsed base-16; bare lowercase hex, NO `0x` prefix — the parser rejects `0x`): `/settlement/init` `inner.amount_in` / `inner.amount_out` (and maker-attachment amounts), `/withdrawal/init` `inner.amount`. These are in **CE-internal 1e18 units** (1 whole token = `de0b6b3a7640000`), NOT the token's on-chain decimals — the CE converts to chain decimals only when signing the on-chain approval. Sending a decimal string here is mis-parsed as hex (e.g. `\"1000000\"` is read as 16,777,216) — and because digits-only strings round-trip the hex codec unchanged, the signature you computed still VERIFIES, so the wrong amount is accepted silently. There is no rejection safety net.\n- **Decimal integer strings** (parsed base-10): `/account/transfer/sub-account` `inner.transfer_amount` and the withdrawal-queue views. `transfer_amount` is in CE-internal units.\n- **CE-internal units**: the clearing engine normalizes every balance to 18 decimals (1e18 = 1 whole token) regardless of the token's on-chain decimals. Inventory, balance, and transfer amounts are in these units.\n- **Risk-parameter scaling**: factor-type risk parameters (collateral/liability factors, margin clamps) are integers scaled by 100 (`50` = 50%); rate-type parameters (funding/utilization settings, fee-tier rates such as `max_trading_fees_rate` ppm) are scaled by 1,000,000 (`500000` = 50%).\n- **Funding**: funding rates are applied hourly; values from `/funding-rate` endpoints are fractions of 1 (e.g. `0.0001` = 0.01% per hour).\n\n## Response Status Conventions\n\nUnless an endpoint documents otherwise:\n\n- Errors use the `ApiErrorResponse` envelope: `{\"error\": {\"code\", \"message\", \"details\"?, \"retryable\"?, \"trace_id\"?, \"span_id\"?}}`.\n- Most mutating endpoints return **503** while the OMS is still bootstrapping, with the literal body `{\"error\": \"warming up\", \"reason\": \"OMS bootstrapping from CE/peers\"}` — note this body is NOT `ApiErrorResponse`-shaped. Exceptions: `/settlement/init` and `/sync_book/create` are not gated and accept requests during warm-up.\n- Malformed query parameters on GET endpoints return **400** `INVALID_QUERY`.\n- Malformed **path** parameters (e.g. a bad asset id in `/asset/{id}`) and unmatched routes both currently surface as **405** `METHOD_NOT_ALLOWED` (`{\"error\": {\"code\": \"METHOD_NOT_ALLOWED\", \"message\": \"Method not allowed\", \"retryable\": false}}`) — not 400/404. warp's method-mismatch rejection from sibling routes outranks not-found. Treat an unexpected 405 as a likely path-encoding error.\n- Exceeding global or per-user rate limits returns **429**.\n- Orderbook confirmation timeouts on order routes return **504** (`TIMEOUT` / `TIMEOUT_UNKNOWN_STATE` — the request may still take effect; reconcile before retrying). Some CE-proxied routes instead return **502** `CE_COMMUNICATION_ERROR` on the equivalent ambiguous timeout; per-endpoint notes call this out.\n\n## WebSocket Contract\n\nAll `/…/ws` and WebSocket-upgradable routes share this contract:\n\n- **Auth**: send `Authorization: Bearer <token>` + `User-Id` headers, or the subprotocol header `Sec-WebSocket-Protocol: Bearer-{token}, User-{id}`. Authorization is checked at upgrade, but **identity** (token vs path user id) is checked after the 101: a mismatch produces an in-band rejection frame — v0: `{\"type\":\"subscriptions\",\"channels\":[],\"errors\":\"unauthorized\"}`, v1: an error envelope with code `unauthorized` — never an HTTP 401.\n- **Welcome frame**: first text frame is `{\"type\":\"subscriptions\",\"channels\":[{\"name\":\"<channel>\"}],\"errors\":null}`. Exception: `/control`'s welcome frame is `{\"type\":\"control\",\"channels\":[{\"name\":\"\"}],\"errors\":null}`.\n- **Keepalive**: the server sends a WebSocket protocol ping every 20 s. Data streams also answer an application-level `{\"type\":\"ping\"}` text frame with `{\"type\":\"pong\"}` (the `/control` stream in v1 mode does NOT — it treats it as a malformed request and replies `BAD_REQUEST`).\n- **Lag**: a client that falls behind the broadcast buffer is disconnected with close code **1013** (reason `resync_required`); in v1 a `RESYNC_REQUIRED` error envelope (with a `lagged` count) is sent first. Re-fetch REST state and reconnect.\n- **Protocol versions**: by default frames are raw payloads (v0). Offering `Sec-WebSocket-Protocol: tplus.ws.v1` opts into versioned envelopes: every frame becomes `WsEnvelope` `{type, channel, request_id, timestamp_ns, data, error}` (see the `WsEnvelope` schema).","license":{"name":""},"version":"0.1.0"},"paths":{"/account/events/{user_id}":{"get":{"tags":["WebSockets"],"summary":"Stream typed user-activity events","description":"Streams typed user-activity events so the FE can render notifications without diffing balances: deposits landed, withdrawals completed, positions cleared, and sub-account asset transfers. The payload is an **externally tagged** `UserActivityEvent`: `{\"DepositLanded\": {…}}`, `{\"WithdrawalCompleted\": {…}}`, `{\"PositionCleared\": {…}}`, or `{\"SubAccountAssetTransferred\": {…}}` (see those schemas). Amount fields are U256 serialized as 0x-prefixed hex strings in CE-internal 1e18 units — format using the asset's display decimals from `/asset/{id}`. Default v0 frames carry this payload raw; v1 frames wrap the same payload in `WsEnvelope.data`. Authentication follows the shared *WebSocket Contract* (header pair or `Sec-WebSocket-Protocol: Bearer-{token}, User-{id}`); a valid token whose user does not match `{user_id}` is rejected in-band after the upgrade, not with HTTP 401.","operationId":"document_user_events_ws","parameters":[{"name":"user_id","in":"path","description":"Hex-encoded user public key","required":true,"schema":{"type":"string"}}],"responses":{"101":{"description":"WebSocket upgrade established; welcome frame, then externally tagged `UserActivityEvent` frames (DepositLanded / WithdrawalCompleted / PositionCleared / SubAccountAssetTransferred)","content":{"application/json":{"schema":{"$ref":"#/components/schemas/UserActivityEventDoc"}}}},"401":{"description":"Auth token missing or invalid (identity mismatch is rejected in-band after the upgrade, not with 401)","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ApiErrorResponse"}}}}},"security":[{"bearer_token":[],"user_id_header":[]}]}},"/account/simulate/{user_id}":{"post":{"tags":["Account"],"summary":"Simulate a trade and return projected margin state","description":"Computes post-trade margin breakdown without executing the trade. Applies optional pending transfers, then the simulated trade, and returns projected equity, available margin, leverage, and per-position details.","operationId":"handle_simulate_margin","parameters":[{"name":"user_id","in":"path","description":"Hex-encoded user public key","required":true,"schema":{"type":"string"}}],"requestBody":{"content":{"application/json":{"schema":{"$ref":"#/components/schemas/MarginSimulateRequest"},"example":{"sub_account":1,"trade":{"asset":"1","is_buy":true,"size":"50.0","limit_price":"100.0","trade_type":"margin"},"pending_transfers":[{"asset":"0","amount":"5000.0"}]}}},"required":true},"responses":{"200":{"description":"Simulated margin result","content":{"application/json":{"schema":{"$ref":"#/components/schemas/MarginSimulateResult"},"example":{"account_equity":"250000.0","available_margin":"195000.0","mm_surplus":"225000.0","account_leverage":"2.0","utilized_margin":"55000.0","margin_required":"5000.0","margin_impact":"-5000.0","is_solvent":true,"positions":[]}}}},"400":{"description":"Invalid request or user public key format","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ApiErrorResponse"}}}},"401":{"description":"Auth token missing or does not match user_id","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ApiErrorResponse"}}}},"404":{"description":"User or sub-account not found","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ApiErrorResponse"}}}},"429":{"description":"Rate limit exceeded","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ApiErrorResponse"}}}},"503":{"description":"Risk parameters not available","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ApiErrorResponse"}}}}},"security":[{"bearer_token":[],"user_id_header":[]}]}},"/account/stats/{user_id}":{"get":{"tags":["WebSockets"],"summary":"Stream account stats updates","description":"\n    ### Account Stats Stream\n    **Events:** Account stats updates for a user. Only pushes deltas containing changed sub-accounts and assets.\n\n    **Authentication:** Required.\n    * Preferred: `Authorization: Bearer <token>` + `User-Id: <id>`\n    * WS-Compatible: `Sec-WebSocket-Protocol: Bearer-<token>, User-<id>`\n\n    A valid token whose user does not match `{user_id}` does NOT yield HTTP 401: the upgrade succeeds (101) and the server sends an in-band rejection frame (lowercase `unauthorized`), then closes. See *WebSocket Contract* in the introduction.\n\n    **Resync Policy:** On reconnection or detected lag, clients **must** re-fetch REST snapshots to ensure state consistency.\n\n    **Lag Signal (v1):** Server emits `WsError` with code `RESYNC_REQUIRED` and closes the socket with code `1013` (`resync_required`).\n\n    **Message Shapes:** See `WsWelcome`, `WsEnvelope`, and `WsError` in the Schemas section.\n\n    **Protocol:** Default raw (v0). Opt-in envelope via `Sec-WebSocket-Protocol: tplus.ws.v1`.","operationId":"document_account_stats_ws","parameters":[{"name":"user_id","in":"path","description":"Hex-encoded user public key","required":true,"schema":{"type":"string"}}],"responses":{"101":{"description":"WebSocket upgrade established","content":{"application/json":{"schema":{"$ref":"#/components/schemas/AccountStatsUpdate"}}}},"401":{"description":"Auth token missing or invalid (identity mismatch is rejected in-band after the upgrade, not with 401)","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ApiErrorResponse"}}}}},"security":[{"bearer_token":[],"user_id_header":[]}]}},"/account/transfer/close-position":{"post":{"tags":["Account"],"summary":"Close margin position (transfer quote)","description":"Forwards a signed close-position request to Clearing Engine over overlay and waits for success/failure response. `signature` is an Ed25519 signature over the compact JSON of `inner` (see *Signing Orders* in the introduction). Note: a timed-out overlay wait surfaces as 502 `CE_COMMUNICATION_ERROR` — the close may still have been applied; re-read positions before retrying.","operationId":"handle_close_position_action","requestBody":{"content":{"application/json":{"schema":{"$ref":"#/components/schemas/ClosePositionRequest"}}},"required":true},"responses":{"200":{"description":"Position close applied","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ClosePositionActionResponse"}}}},"400":{"description":"Close rejected","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ApiErrorResponse"}}}},"401":{"description":"Auth token missing or does not match signer","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ApiErrorResponse"}}}},"429":{"description":"Rate limit exceeded","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ApiErrorResponse"}}}},"502":{"description":"Failed to communicate with clearing engine, including the ambiguous wait timeout (close state unknown)","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ApiErrorResponse"}}}},"503":{"description":"OMS warming up; literal body `{\"error\": \"warming up\", \"reason\": …}` (not ApiErrorResponse)"}},"security":[{"bearer_token":[],"user_id_header":[]}]}},"/account/transfer/sub-account":{"post":{"tags":["Account"],"summary":"Transfer between sub-accounts","description":"Forwards a signed transfer request to Clearing Engine over overlay and waits for success/failure response. `inner.transfer_amount` is a base-10 integer string in CE-internal 1e18 units (1 token = \"1000000000000000000\"); `signature` is an Ed25519 signature over the compact JSON of `inner` (see *Signing Orders* in the introduction). Note: a timed-out overlay wait surfaces as 502 `CE_COMMUNICATION_ERROR` — the transfer may still have been applied; re-read inventory before retrying.","operationId":"handle_transfer_sub_account","requestBody":{"content":{"application/json":{"schema":{"$ref":"#/components/schemas/SubAccountTransferRequest"}}},"required":true},"responses":{"200":{"description":"Transfer applied","content":{"application/json":{"schema":{"$ref":"#/components/schemas/TransferSubAccountResponse"}}}},"400":{"description":"Transfer rejected","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ApiErrorResponse"}}}},"401":{"description":"Auth token missing or does not match signer","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ApiErrorResponse"}}}},"429":{"description":"Rate limit exceeded","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ApiErrorResponse"}}}},"502":{"description":"Failed to communicate with clearing engine, including the ambiguous wait timeout (transfer state unknown)","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ApiErrorResponse"}}}},"503":{"description":"OMS warming up; literal body `{\"error\": \"warming up\", \"reason\": …}` (not ApiErrorResponse)"}},"security":[{"bearer_token":[],"user_id_header":[]}]}},"/account/{user_id}/mds-export":{"put":{"tags":["Account"],"summary":"Set MDS data-export opt-in","operationId":"handle_set_mds_export","parameters":[{"name":"user_id","in":"path","description":"Hex-encoded user public key","required":true,"schema":{"type":"string"}}],"requestBody":{"content":{"application/json":{"schema":{"$ref":"#/components/schemas/MdsExportConsentRequest"}}},"required":true},"responses":{"200":{"description":"Opt-in updated","content":{"application/json":{"schema":{"$ref":"#/components/schemas/MdsExportConsentResponse"}}}},"401":{"description":"Auth token missing or does not match user_id","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ApiErrorResponse"}}}}},"security":[{"bearer_token":[],"user_id_header":[]}]}},"/account/{user_id}/sub-account/{account_index}/name":{"patch":{"tags":["Account"],"summary":"Rename a sub-account","description":"Set a custom human-readable name for a sub-account. Reserved accounts (Main=0, Margin=1) cannot be renamed. Note: the name is currently held in OMS memory only — it is not persisted and reverts on an OMS restart.","operationId":"handle_rename_sub_account","parameters":[{"name":"user_id","in":"path","description":"User public key, 64 lowercase hex chars (no 0x prefix)","required":true,"schema":{"type":"string"}},{"name":"account_index","in":"path","description":"Sub-account index (must be >= 2)","required":true,"schema":{"type":"integer","format":"int64","minimum":0}}],"requestBody":{"content":{"application/json":{"schema":{"$ref":"#/components/schemas/RenameSubAccountRequest"}}},"required":true},"responses":{"200":{"description":"Sub-account renamed successfully","content":{"application/json":{"schema":{"$ref":"#/components/schemas/RenameSubAccountResponse"}}}},"400":{"description":"Invalid name or reserved sub-account","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ApiErrorResponse"}}}},"401":{"description":"Auth token missing or does not match user_id","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ApiErrorResponse"}}}},"404":{"description":"Sub-account not found","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ApiErrorResponse"}}}},"503":{"description":"OMS warming up; literal body `{\"error\": \"warming up\", \"reason\": …}` (not ApiErrorResponse)"}},"security":[{"bearer_token":[],"user_id_header":[]}]}},"/asset/{id}":{"get":{"tags":["Markets"],"summary":"Get asset details","description":"Returns per-chain configurations (addresses, deposit limits) and associated markets for an asset.","operationId":"handle_get_asset","parameters":[{"name":"id","in":"path","description":"Asset index or hex address","required":true,"schema":{"type":"string"}}],"responses":{"200":{"description":"Asset details with chain configs and markets","content":{"application/json":{"schema":{"$ref":"#/components/schemas/AssetResponse"}}}},"404":{"description":"Asset not found (no chain config or markets)","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ApiErrorResponse"}}}}}}},"/auth":{"post":{"tags":["Authentication"],"summary":"Authenticate (get bearer token)","description":"Sign the nonce `value` (UTF-8 bytes) with Ed25519. Returns a bearer token.\n- `Authorization: Bearer <token>`\n- `User-Id: <hex public key>`\n\nMultisig: include `additional_signers` for co-signing.","operationId":"handle_auth_request","requestBody":{"description":"Signed nonce payload","content":{"application/json":{"schema":{"$ref":"#/components/schemas/AuthRequestBody"}}},"required":true},"responses":{"200":{"description":"Authentication successful","content":{"application/json":{"schema":{"$ref":"#/components/schemas/AuthTokenResponse"}}}},"400":{"description":"Invalid nonce, expired nonce, or bad signature","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ApiErrorResponse"}}}},"429":{"description":"Rate limit exceeded","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ApiErrorResponse"}}}}}}},"/control":{"get":{"tags":["WebSockets"],"summary":"Order control channel","description":"\n    ### Bi-directional Control Stream\n\n    **Auth:** `Authorization: Bearer <token>` + `User-Id` headers, or via subprotocol: `new WebSocket(url, [\"Bearer-<token>\", \"User-<id>\"])`.\n\n    **Sending (v0 - raw):** JSON enum with outer key selecting action:\n    ```json\n    {\"CreateOrderRequest\": {\"order\": {...}, \"signature\": [...], \"post_sign_timestamp\": 123}}\n    {\"ReplaceOrderRequest\": {\"request\": {...}, \"signer\": \"eb886a56f9f0efa64432678cebf1270e9314a758e6eb697a606202a451e3e82e\", ...}}\n    {\"CancelOrderRequest\": {\"cancel\": {...}, \"signature\": [...], \"post_sign_timestamp\": 123}}\n    {\"CancelAllOrdersRequest\": {\"max_ts\": null}}\n    {\"CancelAllOrdersForAssetRequest\": {\"asset_id\": {...}, \"max_ts\": null}}\n    {\"BatchCancelRequest\": {\"order_ids\": [\"oid_1\", \"oid_2\"]}}\n    {\"BatchCreateRequest\": {\"orders\": [{\"order\": {...}, \"signature\": [...]}, ...]}}\n    {\"ClosePositionRequest\": {\"inner\": {...}, \"signature\": [...]}}\n    ```\n    `post_sign_timestamp` (ns since epoch, client-set at signing time) is REQUIRED on\n    create and cancel requests — a frame without it fails to deserialize, and in v0 the\n    frame is silently dropped with no error response.\n\n    **Sending (v1 - envelope):** `{\"request_id\": \"req_1\", \"data\": {\"CreateOrderRequest\": {...}}}`. `data` may be double-encoded JSON string.\n\n    **Receiving:** `ObResponse` variants for orders on this connection only. Bulk cancel\n    variants are dispatched fire-and-forget to the orderbook and acknowledged with an\n    immediate `{\"request\": {\"status\": \"submitted\"}}` payload; individual order\n    cancellations stream back as `CancelOrderResponse` events on the connection that\n    submitted the cancel (or bulk-cancel) request — NOT on the connection that created\n    the order (its routing entry is dropped once the create response is delivered).\n    `BatchCreateRequest` (max 50 orders) is acknowledged once with `submitted` and\n    streams a `CreateOrderResponse` event per order; each order is authorized\n    individually (signer must match the authenticated user AND satisfy the account's\n    multisig policy), so orders that fail are rejected on their own with a\n    `CreateOrderResponse` while the rest proceed — only an over-size batch (>50 orders)\n    is rejected wholesale with an error ack.\n    `ClosePositionRequest` is forwarded to the clearing engine, acknowledged with\n    `submitted`, and its outcome arrives later as a `{\"ClosePositionResponse\": {\"success\": bool, \"failure_reason\": ...}}` event (request-id correlated under v1).\n\n    **Resync:** Clients must re-fetch REST snapshots on reconnect. v1 lag signal: `WsError` code `RESYNC_REQUIRED`, socket close `1013`.\n\n    **Protocol:** Default raw (v0). Opt-in envelope via `Sec-WebSocket-Protocol: tplus.ws.v1`.\n\n    See *WebSocket Contract* in the introduction for the shared connection contract\n    (welcome frame, pings, lag handling, in-band auth rejection). Control-specific\n    quirks: in v1 mode an application-level `{\"type\":\"ping\"}` is NOT answered with a\n    pong — it is treated as a malformed request and rejected with a `BAD_REQUEST` error\n    envelope; in v0 mode, requests sent while the OMS is still warming up are dropped\n    without any error frame.","operationId":"document_control_ws","responses":{"101":{"description":"WebSocket upgrade established","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ObResponse"}}}}},"security":[{"bearer_token":[],"user_id_header":[]}]}},"/funding-rate/history":{"get":{"tags":["Market Data"],"summary":"Get historical applied funding rates","description":"Returns historical funding snapshots for rates that CE actually applied, using half-open timestamp filters: start_timestamp_ns is inclusive and end_timestamp_ns is exclusive.","operationId":"handle_get_funding_rate_history","parameters":[{"name":"asset_id","in":"query","description":"Optional market index or hex address filter","required":false,"schema":{"type":"string"}},{"name":"start_timestamp_ns","in":"query","description":"Start timestamp in nanoseconds (inclusive)","required":false,"schema":{"type":"integer","format":"int64","minimum":0}},{"name":"end_timestamp_ns","in":"query","description":"End timestamp in nanoseconds (exclusive)","required":false,"schema":{"type":"integer","format":"int64","minimum":0}},{"name":"page","in":"query","description":"Zero-based page number (default: 0)","required":false,"schema":{"type":"integer","minimum":0}},{"name":"limit","in":"query","description":"Items per page, max 1000 (default: 100)","required":false,"schema":{"type":"integer","minimum":0}}],"responses":{"200":{"description":"Funding rate history returned successfully","content":{"application/json":{"schema":{"type":"array","items":{"$ref":"#/components/schemas/FundingRateResponse"}}}}},"400":{"description":"Invalid query parameters","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ApiErrorResponse"}}}},"404":{"description":"Market not found","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ApiErrorResponse"}}}}}}},"/funding-rate/{asset_id}":{"get":{"tags":["Market Data"],"summary":"Get current funding rate for a market","description":"Returns the latest current funding rate cached by OMS from Interest Engine live computations. This can update between funding payments; use /funding-rate/history for rates that were actually applied.","operationId":"handle_get_funding_rate","parameters":[{"name":"asset_id","in":"path","description":"Market index or hex address","required":true,"schema":{"type":"string"}}],"responses":{"200":{"description":"Funding rate returned successfully","content":{"application/json":{"schema":{"$ref":"#/components/schemas/FundingRateResponse"}}}},"404":{"description":"Market not found","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ApiErrorResponse"}}}},"503":{"description":"Funding rate data unavailable","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ApiErrorResponse"}}}}}}},"/health":{"get":{"tags":["Account"],"summary":"OMS liveness check","description":"Returns `{\"status\":\"healthy\"}` with HTTP 200 when the OMS instance is serving requests, or `{\"status\":\"unhealthy\"}` with HTTP 503 when the storage layer has marked itself unhealthy. No authentication required.","operationId":"document_health","responses":{"200":{"description":"Service is healthy"},"503":{"description":"Service is unhealthy"}}}},"/inventory/user/{user_id}":{"get":{"tags":["Account"],"summary":"Get user inventory","description":"Returns sub-accounts with spot balances and margin positions (base + quote credits/liabilities). By default spans all sub-accounts; pass `sub_account` to scope to one.","operationId":"handle_get_user_inventory","parameters":[{"name":"user_id","in":"path","description":"Hex-encoded user public key","required":true,"schema":{"type":"string"}},{"name":"sub_account","in":"query","description":"Restrict to a single sub-account by account index (default: all sub-accounts)","required":false,"schema":{"type":"integer","format":"int64","minimum":0}}],"responses":{"200":{"description":"User inventory returned successfully","content":{"application/json":{"schema":{"$ref":"#/components/schemas/UserInventoryJson"},"example":{"accounts":{"0":{"kind":"spot","spot":{"0":"1.0","1":"0.5"},"margins":{}},"1":{"kind":"cross_margin","spot":{"0":"2.0"},"margins":{"1":{"base":{"credits":"0.1","liabilities":"0.0"},"quote":{"credits":"0.0","liabilities":"0.05"}}}}},"is_mm":false}}}},"401":{"description":"Auth token missing or does not match user_id","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ApiErrorResponse"}}}},"404":{"description":"User not found","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ApiErrorResponse"}}}}},"security":[{"bearer_token":[],"user_id_header":[]}]}},"/liquidation/at_risk/ws":{"get":{"tags":["WebSockets"],"summary":"Stream at-risk-of-liquidation asset updates","description":"\n    ### At risk of liquidation Stream\n    **Events:** At risk updates triggered by position and price changes (fills, deposits, settlements, oracle update, etc).\n\n    **Scope:** This is a MARKET-WIDE aggregate stream: every connected client receives at-risk updates for ALL users' positions, not only the authenticated caller's. Filter client-side before rendering — entries are not the caller's own unless they match the caller's user id.\n\n    **Authentication:** Required.\n    * Preferred: `Authorization: Bearer <token>` + `User-Id: <id>`\n    * WS-Compatible: `Sec-WebSocket-Protocol: Bearer-<token>, User-<id>`\n\n    **Resync Policy:** On reconnection or detected lag, clients **must** re-fetch REST snapshots to ensure state consistency.\n\n    **Lag Signal (v1):** Server emits `WsError` with code `RESYNC_REQUIRED` and closes the socket with code `1013` (`resync_required`).\n\n    **Message Shapes:** See `WsWelcome`, `WsEnvelope`, and `WsError` in the Schemas section.\n\n    **Protocol:** Default raw (v0). Opt-in envelope via `Sec-WebSocket-Protocol: tplus.ws.v1`.","operationId":"document_at_risk_ws","responses":{"101":{"description":"WebSocket upgrade established","content":{"application/json":{"schema":{"$ref":"#/components/schemas/AtRiskOfLiquidation"}}}},"401":{"description":"Auth token missing or invalid","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ApiErrorResponse"}}}}},"security":[{"bearer_token":[],"user_id_header":[]}]}},"/logout":{"post":{"tags":["Authentication"],"summary":"Logout (revoke tokens)","description":"Revokes all bearer tokens for the caller.","operationId":"handle_logout_request","requestBody":{"description":"Must match the authenticated user","content":{"application/json":{"schema":{"$ref":"#/components/schemas/LogoutRequestBody"}}},"required":true},"responses":{"200":{"description":"Logout successful"},"401":{"description":"Unauthorized","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ApiErrorResponse"}}}}},"security":[{"bearer_token":[],"user_id_header":[]}]}},"/margin/user/{user_id}":{"get":{"tags":["Account"],"summary":"Get user margin info","description":"Returns detailed margin breakdown (equity, IM/MM surplus, leverage, per-position margin) for each sub-account.","operationId":"handle_get_margin_info","parameters":[{"name":"user_id","in":"path","description":"Hex-encoded user public key","required":true,"schema":{"type":"string"}},{"name":"sub_account","in":"query","description":"Single sub-account index to include (omit for all)","required":false,"schema":{"type":"integer","format":"int64","minimum":0}},{"name":"include_positions","in":"query","description":"Include per-position breakdown (default false)","required":true,"schema":{"type":"boolean"}}],"responses":{"200":{"description":"Margin info per sub-account","content":{"application/json":{"schema":{"$ref":"#/components/schemas/MarginInfoResponse"},"example":{"accounts":{"1":{"account_equity":"250000.0","available_margin":"200000.0","utilized_margin":"50000.0","maintenance_margin_surplus":"225000.0","mm_requirement":"25000.0","account_leverage":"2.0","is_solvent":true,"is_liquidatable":false,"total_upnl":"1250.0","im_surplus":null,"net_apy":null}}}}}},"400":{"description":"Invalid user public key format","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ApiErrorResponse"}}}},"401":{"description":"Auth token missing or does not match user_id","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ApiErrorResponse"}}}},"404":{"description":"User not found in OMS inventory","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ApiErrorResponse"}}}},"429":{"description":"Rate limit exceeded","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ApiErrorResponse"}}}},"503":{"description":"Risk parameters not available","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ApiErrorResponse"}}}}},"security":[{"bearer_token":[],"user_id_header":[]}]}},"/market/create":{"post":{"tags":["Markets"],"summary":"Create a market","description":"Returns 200 if the market already exists, 201 if newly created. No authentication is required (rate limit and warm-up gate only). All creation failures (orderbook probe, CE decimals fetch, book-start timeout) collapse to 500.","operationId":"handle_create_market","requestBody":{"content":{"application/json":{"schema":{"$ref":"#/components/schemas/CreateMarketRequest"}}},"required":true},"responses":{"200":{"description":"Market already exists","content":{"application/json":{"schema":{"$ref":"#/components/schemas/CreateMarketResponse"}}}},"201":{"description":"Market created successfully","content":{"application/json":{"schema":{"$ref":"#/components/schemas/CreateMarketResponse"}}}},"400":{"description":"Invalid request body","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ApiErrorResponse"}}}},"429":{"description":"Rate limit exceeded","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ApiErrorResponse"}}}},"500":{"description":"Internal error (any orderbook/CE failure during creation)","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ApiErrorResponse"}}}},"503":{"description":"OMS warming up; literal body `{\"error\": \"warming up\", \"reason\": …}` (not ApiErrorResponse)"}}}},"/market/{id}":{"get":{"tags":["Markets"],"summary":"Get market details","operationId":"handle_get_market","parameters":[{"name":"id","in":"path","description":"Market index or hex address","required":true,"schema":{"type":"string"}}],"responses":{"200":{"description":"Market details","content":{"application/json":{"schema":{"$ref":"#/components/schemas/MarketDetailsResponse"}}}},"404":{"description":"Market not found","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ApiErrorResponse"}}}}}}},"/markets":{"get":{"tags":["Markets"],"summary":"List all markets","operationId":"handle_get_markets","responses":{"200":{"description":"All active markets","content":{"application/json":{"schema":{"type":"array","items":{"$ref":"#/components/schemas/MarketResponse"}}}}}}}},"/multisig/add-signer":{"post":{"tags":["Account"],"summary":"Add single signer to multisig config","description":"Forwards a signed add-single-signer request to Clearing Engine over overlay and waits for success/failure response. `signature` is an Ed25519 signature over the compact JSON of `inner` (see *Signing Orders* in the introduction); the request consumes `inner.ce_nonce` (current value from `GET /nonce/{user_id}`). Note: a timed-out overlay wait surfaces as 502 `CE_COMMUNICATION_ERROR` — the signer may still have been added; re-read `GET /multisig/config/{user_id}` before retrying (the nonce is consumed).","operationId":"handle_add_single_signer_action","requestBody":{"content":{"application/json":{"schema":{"$ref":"#/components/schemas/AddSingleSignerRequest"}}},"required":true},"responses":{"200":{"description":"Signer added","content":{"application/json":{"schema":{"$ref":"#/components/schemas/AddSingleSignerActionResponse"}}}},"400":{"description":"Add signer rejected","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ApiErrorResponse"}}}},"401":{"description":"Auth token missing or does not match signer","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ApiErrorResponse"}}}},"429":{"description":"Rate limit exceeded","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ApiErrorResponse"}}}},"502":{"description":"Failed to communicate with clearing engine, including the ambiguous wait timeout (signer state unknown)","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ApiErrorResponse"}}}},"503":{"description":"OMS warming up; literal body `{\"error\": \"warming up\", \"reason\": …}` (not ApiErrorResponse)"}},"security":[{"bearer_token":[],"user_id_header":[]}]}},"/multisig/config":{"post":{"tags":["Account"],"summary":"Update multisig config","description":"Forwards a signed full multisig config update request to Clearing Engine over overlay and waits for success/failure response.","operationId":"handle_update_multisig_config_action","requestBody":{"content":{"application/json":{"schema":{"$ref":"#/components/schemas/UpdateMultisigConfigRequest"}}},"required":true},"responses":{"200":{"description":"Multisig config updated","content":{"application/json":{"schema":{"$ref":"#/components/schemas/UpdateMultisigConfigActionResponse"}}}},"400":{"description":"Multisig config rejected","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ApiErrorResponse"}}}},"401":{"description":"Auth token missing or does not match signer","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ApiErrorResponse"}}}},"429":{"description":"Rate limit exceeded","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ApiErrorResponse"}}}},"502":{"description":"Failed to communicate with clearing engine","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ApiErrorResponse"}}}},"503":{"description":"OMS warming up; literal body `{\"error\": \"warming up\", \"reason\": …}` (not ApiErrorResponse)"}},"security":[{"bearer_token":[],"user_id_header":[]}]}},"/multisig/config/{user_id}":{"get":{"tags":["Account"],"summary":"Get multisig config for user","description":"Reads multisig config from OMS in-memory state (`OmsStorage.multisig_configs`).","operationId":"handle_get_multisig_config","parameters":[{"name":"user_id","in":"path","description":"Hex-encoded user public key","required":true,"schema":{"type":"string"}}],"responses":{"200":{"description":"Multisig config","content":{"application/json":{"schema":{"$ref":"#/components/schemas/MultisigConfig"}}}},"400":{"description":"Invalid user_id format","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ApiErrorResponse"}}}},"401":{"description":"Auth token missing or does not match user_id","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ApiErrorResponse"}}}},"404":{"description":"No multisig config found for user","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ApiErrorResponse"}}}},"429":{"description":"Rate limit exceeded","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ApiErrorResponse"}}}}},"security":[{"bearer_token":[],"user_id_header":[]}]}},"/multisig/signers":{"post":{"tags":["Account"],"summary":"Get master users for signer key","description":"Returns master user public keys whose multisig config contains the provided signer key. Reads from OMS in-memory state (`OmsStorage.multisig_configs`). This endpoint is intentionally unauthenticated to support pre-auth signer-to-master lookup.","operationId":"handle_get_master_keys_for_signer","requestBody":{"content":{"application/json":{"schema":{"$ref":"#/components/schemas/SignerKey"}}},"required":true},"responses":{"200":{"description":"Matching master user keys","content":{"application/json":{"schema":{"type":"array","items":{"type":"string"}}}}},"429":{"description":"Rate limit exceeded","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ApiErrorResponse"}}}}}}},"/nonce/{user_id}":{"get":{"tags":["Authentication"],"summary":"Get auth nonce","description":"Random nonce bound to user. Valid 5 minutes. Sign `value` with Ed25519 private key, submit to `POST /auth`.","operationId":"handle_nonce_request","parameters":[{"name":"user_id","in":"path","description":"Hex-encoded Ed25519 public key (with or without 0x prefix)","required":true,"schema":{"type":"string"}}],"responses":{"200":{"description":"Nonce generated","content":{"application/json":{"schema":{"$ref":"#/components/schemas/NonceResponse"}}}},"400":{"description":"Invalid user ID format","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ApiErrorResponse"}}}},"429":{"description":"Rate limit exceeded","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ApiErrorResponse"}}}}}}},"/orders":{"get":{"tags":["WebSockets"],"summary":"Stream order updates for authenticated user","description":"\n    ### Orders Stream\n    **Events:** Order updates for the authenticated user.\n\n    **Authentication:** Required.\n    * Preferred: `Authorization: Bearer <token>` + `User-Id: <id>`\n    * WS-Compatible: `Sec-WebSocket-Protocol: Bearer-<token>, User-<id>`\n\n    **Resync Policy:** On reconnection or detected lag, clients **must** re-fetch REST snapshots to ensure state consistency.\n\n    **Lag Signal (v1):** Server emits `WsError` with code `RESYNC_REQUIRED` and closes the socket with code `1013` (`resync_required`).\n\n    **Message Shapes:** See `WsWelcome`, `WsEnvelope`, and `WsError` in the Schemas section.\n\n    **Protocol:** Default raw (v0). Opt-in envelope via `Sec-WebSocket-Protocol: tplus.ws.v1`.","operationId":"document_orders_ws","responses":{"101":{"description":"WebSocket connection established for order updates","content":{"application/json":{"schema":{"$ref":"#/components/schemas/OrderEvent"}}}},"401":{"description":"Auth token missing or invalid","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ApiErrorResponse"}}}}},"security":[{"bearer_token":[],"user_id_header":[]}]}},"/orders/batch-create":{"post":{"tags":["Orders"],"summary":"Batch create orders","description":"Validates and submits multiple signed orders. Each order is processed independently through the standard order pipeline (margin checks, signature validation). Returns per-order status. Max 50 orders per batch.","operationId":"handle_batch_create_orders","requestBody":{"description":"Signed order payloads","content":{"application/json":{"schema":{"$ref":"#/components/schemas/BatchCreateRequest"}}},"required":true},"responses":{"200":{"description":"Per-order results","content":{"application/json":{"schema":{"$ref":"#/components/schemas/BatchCreateResponse"}}}},"400":{"description":"Invalid request or batch too large","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ApiErrorResponse"}}}},"401":{"description":"Auth token missing","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ApiErrorResponse"}}}},"429":{"description":"Rate limit exceeded","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ApiErrorResponse"}}}},"503":{"description":"OMS warming up; literal body `{\"error\": \"warming up\", \"reason\": …}` (not ApiErrorResponse)"}},"security":[{"bearer_token":[],"user_id_header":[]}]}},"/orders/cancel":{"delete":{"tags":["Orders"],"summary":"Cancel order","description":"Forwards cancel to the orderbook. Returns 504 (TIMEOUT) if the orderbook does not confirm within the deadline — the cancel may still have been applied; reconcile via GET /orders before retrying.\n\n**Note:** This endpoint requires a JSON request body with the DELETE method.","operationId":"handle_cancel_order","requestBody":{"description":"Signed cancel payload","content":{"application/json":{"schema":{"$ref":"#/components/schemas/CancelOrderRequest"}}},"required":true},"responses":{"200":{"description":"Cancellation confirmed by orderbook","content":{"application/json":{"schema":{"$ref":"#/components/schemas/CancelOrderResponse"}}}},"400":{"description":"Rejected: invalid JSON or order ID too long","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ApiErrorResponse"}}}},"401":{"description":"Auth token missing or does not match signer","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ApiErrorResponse"}}}},"404":{"description":"Order not found","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ApiErrorResponse"}}}},"429":{"description":"Rate limit exceeded","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ApiErrorResponse"}}}},"503":{"description":"OMS warming up; literal body `{\"error\": \"warming up\", \"reason\": …}` (not ApiErrorResponse)"},"504":{"description":"Orderbook timeout (cancel state unknown)","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ApiErrorResponse"}}}}},"security":[{"bearer_token":[],"user_id_header":[]}]}},"/orders/cancel-all":{"delete":{"tags":["Orders"],"summary":"Cancel all orders","description":"Asynchronously cancels all open orders across all markets. Returns 202 immediately; cancellations are dispatched per-market to the orderbook. A 202 means queued, not completed — watch the orders stream or re-query GET /orders/user/{user_id} to confirm the cancellations landed.","operationId":"handle_cancel_all_orders","parameters":[{"name":"max_ts","in":"query","description":"Only cancel orders created before this timestamp in nanoseconds (default: now)","required":false,"schema":{"type":"integer","format":"int64","minimum":0}}],"responses":{"202":{"description":"Cancel-all request queued (not yet completed)","content":{"application/json":{"schema":{"$ref":"#/components/schemas/CancelAllOrdersResponse"}}}},"401":{"description":"Auth token missing","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ApiErrorResponse"}}}},"429":{"description":"Rate limit exceeded","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ApiErrorResponse"}}}},"500":{"description":"Failed to dispatch to orderbook","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ApiErrorResponse"}}}},"503":{"description":"OMS warming up; literal body `{\"error\": \"warming up\", \"reason\": …}` (not ApiErrorResponse)"}},"security":[{"bearer_token":[],"user_id_header":[]}]}},"/orders/cancel-all/{asset_id}":{"delete":{"tags":["Orders"],"summary":"Cancel all orders for market","description":"Asynchronously cancels all open orders for the specified market. Returns 202 immediately; cancellation is dispatched to the orderbook.","operationId":"handle_cancel_all_orders_for_asset","parameters":[{"name":"asset_id","in":"path","description":"Market index or hex address","required":true,"schema":{"type":"string"}},{"name":"max_ts","in":"query","description":"Only cancel orders created before this timestamp in nanoseconds (default: now)","required":false,"schema":{"type":"integer","format":"int64","minimum":0}}],"responses":{"202":{"description":"Cancel-all request queued (not yet completed)","content":{"application/json":{"schema":{"$ref":"#/components/schemas/CancelAllOrdersResponse"}}}},"401":{"description":"Auth token missing","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ApiErrorResponse"}}}},"429":{"description":"Rate limit exceeded","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ApiErrorResponse"}}}},"500":{"description":"Failed to dispatch to orderbook (also returned for a malformed asset_id path parameter)","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ApiErrorResponse"}}}},"503":{"description":"OMS warming up; literal body `{\"error\": \"warming up\", \"reason\": …}` (not ApiErrorResponse)"}},"security":[{"bearer_token":[],"user_id_header":[]}]}},"/orders/cancel-batch":{"delete":{"tags":["Orders"],"summary":"Batch cancel orders","description":"Asynchronously cancels a list of orders by ID. Order IDs not owned by the caller are silently excluded. Returns 202 immediately; cancellations are dispatched per-market.","operationId":"handle_batch_cancel","requestBody":{"description":"List of order IDs to cancel","content":{"application/json":{"schema":{"$ref":"#/components/schemas/BatchCancelRequest"}}},"required":true},"responses":{"202":{"description":"Batch cancel accepted; dispatched count may be less than requested. On partial dispatch the body's `status` is `\"partial\"` and a `failures` array of asset ids is included.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/BatchCancelResponseDoc"},"example":{"status":"accepted","dispatched":2}}}},"400":{"description":"Invalid request body","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ApiErrorResponse"}}}},"401":{"description":"Auth token missing","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ApiErrorResponse"}}}},"429":{"description":"Rate limit exceeded","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ApiErrorResponse"}}}},"500":{"description":"Failed to dispatch to orderbook","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ApiErrorResponse"}}}},"503":{"description":"OMS warming up; literal body `{\"error\": \"warming up\", \"reason\": …}` (not ApiErrorResponse)"}},"security":[{"bearer_token":[],"user_id_header":[]}]}},"/orders/create":{"post":{"tags":["Orders"],"summary":"Create order","description":"Validates margin requirements locally before forwarding to the orderbook. Returns 504 (TIMEOUT) if the orderbook does not confirm within the deadline — the order may still have been placed; reconcile via GET /orders before retrying.","operationId":"handle_create_order","requestBody":{"description":"Signed order payload","content":{"application/json":{"schema":{"$ref":"#/components/schemas/CreateOrderRequest"}}},"required":true},"responses":{"200":{"description":"Order accepted by orderbook","content":{"application/json":{"schema":{"$ref":"#/components/schemas/CreateOrderResponse"}}}},"400":{"description":"Rejected: invalid JSON or margin check failed","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ApiErrorResponse"}}}},"401":{"description":"Auth token missing or does not match signer (ApiErrorResponse). Exception: a multisig additional-signer rejection returns 401 with a `CreateOrderResponse` body instead — `{\"order_id\": …, \"status\": \"Rejected\", \"reason\": \"InvalidAuthToken\", …}`","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ApiErrorResponse"}}}},"429":{"description":"Rate limit exceeded","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ApiErrorResponse"}}}},"503":{"description":"OMS warming up; literal body `{\"error\": \"warming up\", \"reason\": …}` (not ApiErrorResponse)"},"504":{"description":"Orderbook timeout (order state unknown)","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ApiErrorResponse"}}}}},"security":[{"bearer_token":[],"user_id_header":[]}]}},"/orders/lookup":{"get":{"tags":["Orders"],"summary":"Get order by ID","description":"Looks up a single order across all of the caller's sub-accounts.","operationId":"handle_get_order","parameters":[{"name":"order_id","in":"query","description":"User-assigned order identifier","required":true,"schema":{"type":"string"}}],"responses":{"200":{"description":"Order found","content":{"application/json":{"schema":{"$ref":"#/components/schemas/JsonBookOrder"}}}},"400":{"description":"Invalid order ID (too long)","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ApiErrorResponse"}}}},"401":{"description":"Auth token missing","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ApiErrorResponse"}}}},"404":{"description":"Order not found","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ApiErrorResponse"}}}}},"security":[{"bearer_token":[],"user_id_header":[]}]}},"/orders/replace":{"patch":{"tags":["Orders"],"summary":"Replace order","description":"Atomically updates price, quantity, or trigger on an existing order. Returns 504 (TIMEOUT) if the orderbook does not confirm within the deadline — the replace may still have been applied; reconcile via GET /orders before retrying. The replace payload is signed: serialize the `ReplaceOrder` payload to compact JSON (fields in struct declaration order — the schema's `required` order, NOT the alphabetized `properties` order — whitespace stripped) and sign the UTF-8 bytes with Ed25519 — see *Signing Orders* in the introduction; `signature` is the 64-byte integer array.","operationId":"handle_replace_order","requestBody":{"description":"Signed replace payload","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ReplaceOrderRequest"}}},"required":true},"responses":{"200":{"description":"Replacement accepted by orderbook","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ReplaceOrderResponse"}}}},"400":{"description":"Rejected: invalid JSON or margin check failed","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ApiErrorResponse"}}}},"401":{"description":"Auth token missing or does not match signer","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ApiErrorResponse"}}}},"404":{"description":"Order not found","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ApiErrorResponse"}}}},"429":{"description":"Rate limit exceeded","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ApiErrorResponse"}}}},"503":{"description":"OMS warming up; literal body `{\"error\": \"warming up\", \"reason\": …}` (not ApiErrorResponse)"},"504":{"description":"Orderbook timeout (replace state unknown)","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ApiErrorResponse"}}}}},"security":[{"bearer_token":[],"user_id_header":[]}]}},"/orders/user/{user_id}":{"get":{"tags":["Orders"],"summary":"List user orders","description":"Returns the user's live, working order state, with pagination metadata — poll this while trading, when you need the current truth. For **full order history** (every order including filled, cancelled and expired, for history views, audits and reporting) use the Market Data Service `/orders/user` endpoint instead. By default spans all sub-accounts; pass `sub_account` to scope to one. Use `open_only=true` to exclude filled/cancelled orders.","operationId":"handle_get_user_orders","parameters":[{"name":"user_id","in":"path","description":"Hex-encoded user public key","required":true,"schema":{"type":"string"}},{"name":"sub_account","in":"query","description":"Restrict to a single sub-account by account index (default: all sub-accounts)","required":false,"schema":{"type":"integer","format":"int64","minimum":0}},{"name":"page","in":"query","description":"Zero-based page number (default: 0)","required":false,"schema":{"type":"integer","minimum":0}},{"name":"limit","in":"query","description":"Items per page, max 1000 (default: 100)","required":false,"schema":{"type":"integer","minimum":0}},{"name":"open_only","in":"query","description":"Return only open orders (default: false)","required":false,"schema":{"type":"boolean"}}],"responses":{"200":{"description":"Orders returned successfully","content":{"application/json":{"schema":{"$ref":"#/components/schemas/UserOrdersPageResponse"}}}},"401":{"description":"Auth token missing or does not match user_id","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ApiErrorResponse"}}}},"404":{"description":"User not found in OMS state","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ApiErrorResponse"}}}}},"security":[{"bearer_token":[],"user_id_header":[]}]}},"/orders/user/{user_id}/{asset_id}":{"get":{"tags":["Orders"],"summary":"List user orders for market","description":"Returns orders for a specific market. By default spans all sub-accounts; pass `sub_account` to scope to one. Use `open_only=true` to exclude filled/cancelled orders.","operationId":"handle_get_user_orders_for_book","parameters":[{"name":"user_id","in":"path","description":"Hex-encoded user public key","required":true,"schema":{"type":"string"}},{"name":"asset_id","in":"path","description":"Market index or hex address","required":true,"schema":{"type":"string"}},{"name":"sub_account","in":"query","description":"Restrict to a single sub-account by account index (default: all sub-accounts)","required":false,"schema":{"type":"integer","format":"int64","minimum":0}},{"name":"page","in":"query","description":"Zero-based page number (default: 0)","required":false,"schema":{"type":"integer","minimum":0}},{"name":"limit","in":"query","description":"Items per page, max 1000 (default: 100)","required":false,"schema":{"type":"integer","minimum":0}},{"name":"open_only","in":"query","description":"Return only open orders (default: false)","required":false,"schema":{"type":"boolean"}}],"responses":{"200":{"description":"Orders returned successfully","content":{"application/json":{"schema":{"type":"array","items":{"$ref":"#/components/schemas/JsonBookOrder"}}}}},"401":{"description":"Auth token missing or does not match user_id","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ApiErrorResponse"}}}},"404":{"description":"User or market not found","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ApiErrorResponse"}}}}},"security":[{"bearer_token":[],"user_id_header":[]}]}},"/positions/close-all/{user_id}/{sub_account}":{"get":{"tags":["Positions"],"summary":"Preview close-all orders","description":"Returns the unsigned order payloads needed to close all open margin positions in the specified sub-account. The frontend signs each order and submits them via POST /orders/batch-create.","operationId":"handle_close_all_preview","parameters":[{"name":"user_id","in":"path","description":"Hex-encoded user public key","required":true,"schema":{"type":"string"}},{"name":"sub_account","in":"path","description":"Sub-account index","required":true,"schema":{"type":"integer","format":"int64","minimum":0}}],"responses":{"200":{"description":"Unsigned close orders","content":{"application/json":{"schema":{"$ref":"#/components/schemas/CloseAllPreviewResponse"}}}},"401":{"description":"Auth token missing or does not match user_id","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ApiErrorResponse"}}}},"404":{"description":"User not found","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ApiErrorResponse"}}}}},"security":[{"bearer_token":[],"user_id_header":[]}]}},"/positions/ws/{user_id}":{"get":{"tags":["WebSockets"],"summary":"Stream position updates for user","description":"\n    ### Positions Stream\n    **Events:** Position updates triggered by CE inventory changes (fills, deposits, settlements).\n\n    **Authentication:** Required.\n    * Preferred: `Authorization: Bearer <token>` + `User-Id: <id>`\n    * WS-Compatible: `Sec-WebSocket-Protocol: Bearer-<token>, User-<id>`\n\n    A valid token whose user does not match `{user_id}` does NOT yield HTTP 401: the upgrade succeeds (101) and the server sends an in-band rejection frame, then closes. See *WebSocket Contract* in the introduction.\n\n    **Resync Policy:** On reconnection or detected lag, clients **must** re-fetch REST snapshots to ensure state consistency.\n\n    **Lag Signal (v1):** Server emits `WsError` with code `RESYNC_REQUIRED` and closes the socket with code `1013` (`resync_required`).\n\n    **Message Shapes:** See `WsWelcome`, `WsEnvelope`, and `WsError` in the Schemas section.\n\n    **Protocol:** Default raw (v0). Opt-in envelope via `Sec-WebSocket-Protocol: tplus.ws.v1`.","operationId":"document_positions_ws","parameters":[{"name":"user_id","in":"path","description":"User public key, 64 lowercase hex chars (no 0x prefix). Uppercase or 0x-prefixed ids fail the identity check even with a valid token.","required":true,"schema":{"type":"string"}}],"responses":{"101":{"description":"WebSocket upgrade established","content":{"application/json":{"schema":{"$ref":"#/components/schemas/PositionUpdate"}}}},"401":{"description":"Auth token missing or invalid (identity mismatch is rejected in-band after the upgrade, not with 401)","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ApiErrorResponse"}}}}},"security":[{"bearer_token":[],"user_id_header":[]}]}},"/positions/{user_id}":{"get":{"tags":["Positions"],"summary":"List user positions","description":"Returns open positions derived from margin and spot holdings across margin sub-accounts. Spot-only holdings in margin accounts are included; the main spot account and USD collateral balances are excluded.","operationId":"handle_get_user_positions","parameters":[{"name":"user_id","in":"path","description":"Hex-encoded user public key","required":true,"schema":{"type":"string"}},{"name":"sub_account","in":"query","description":"Filter to a single sub-account index","required":false,"schema":{"type":"integer","format":"int64","minimum":0}},{"name":"page","in":"query","description":"Zero-based page number (default: 0)","required":false,"schema":{"type":"integer","minimum":0}},{"name":"limit","in":"query","description":"Items per page, max 1000 (default: 100)","required":false,"schema":{"type":"integer","minimum":0}}],"responses":{"200":{"description":"Positions returned successfully","content":{"application/json":{"schema":{"$ref":"#/components/schemas/UserPositionsPageResponse"},"example":{"positions":[],"page":0,"limit":100,"total_positions":0,"total_pages":0,"cursor_size":0,"has_next_page":false,"next_page":null}}}},"401":{"description":"Auth token missing or does not match user_id","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ApiErrorResponse"}}}},"404":{"description":"User not found","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ApiErrorResponse"}}}}},"security":[{"bearer_token":[],"user_id_header":[]}]}},"/positions/{user_id}/{asset_id}":{"get":{"tags":["Positions"],"summary":"Get user position for asset","description":"Returns the position for a specific asset, including spot holdings in margin accounts if no derivative margin entry exists for that asset.","operationId":"handle_get_user_position_for_asset","parameters":[{"name":"user_id","in":"path","description":"Hex-encoded user public key","required":true,"schema":{"type":"string"}},{"name":"asset_id","in":"path","description":"Asset index or hex-encoded asset address","required":true,"schema":{"type":"string"}},{"name":"sub_account","in":"query","description":"Filter to a single sub-account index","required":false,"schema":{"type":"integer","format":"int64","minimum":0}}],"responses":{"200":{"description":"Position returned successfully","content":{"application/json":{"schema":{"type":"array","items":{"$ref":"#/components/schemas/PositionResponse"}}}}},"401":{"description":"Auth token missing or does not match user_id","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ApiErrorResponse"}}}},"404":{"description":"User not found","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ApiErrorResponse"}}}}},"security":[{"bearer_token":[],"user_id_header":[]}]}},"/registry/assets":{"get":{"tags":["Registry"],"summary":"Asset configuration from clearing engine","description":"Returns the CE asset registry snapshot received via overlay (same data CE previously exposed on its permissionless `/assets`). Empty until the first CE snapshot or gossip update.\n\nShape: a nested map `asset index (stringified u64) -> chain asset address (`\"<32-byte-hex>@<9-byte-hex-chain>\"`) -> AssetConfig`, where `AssetConfig` is `{\"max_deposits\": \"<base-10 U256, token native units>\", \"address\": \"<addr@chain>\", \"max_1hr_deposits\": \"<base-10 U256>\", \"min_weight\": \"<base-10 U256, weight target — NOT a percentage>\"}`. Deposit caps are per chain asset; exceeding them makes further deposits credit an isolated `Address(token@chain)` balance instead of the fungible index.","operationId":"handle_get_registry_assets","responses":{"200":{"description":"Asset config map: asset index -> chain address -> AssetConfig","content":{"application/json":{"schema":{"type":"object"},"example":{"0":{"62622e77d1349face943c6e7d5c01c61465fe1dc000000000000000000000000@00000000000000a4b1":{"max_deposits":"1000000000000","address":"62622e77d1349face943c6e7d5c01c61465fe1dc000000000000000000000000@00000000000000a4b1","max_1hr_deposits":"100000000000","min_weight":"1"}}}}}},"429":{"description":"Rate limit exceeded","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ApiErrorResponse"}}}},"503":{"description":"Registry not yet available","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ApiErrorResponse"}}}}}}},"/registry/decimals":{"post":{"tags":["Registry"],"summary":"Asset decimals from clearing engine","description":"Returns OMS-cached decimals for the requested asset addresses.","operationId":"handle_get_registry_decimals","requestBody":{"content":{"application/json":{"schema":{"$ref":"#/components/schemas/AssetDecimalsRequest"}}},"required":true},"responses":{"200":{"description":"Asset decimals map","content":{"application/json":{"schema":{}}}},"429":{"description":"Rate limit exceeded","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ApiErrorResponse"}}}},"503":{"description":"Decimals not yet available","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ApiErrorResponse"}}}}}}},"/registry/decimals/update":{"post":{"tags":["Registry"],"summary":"Request CE decimals refresh","description":"Forwards a decimals update request to CE and waits for success/failure event. Note: a timed-out overlay wait surfaces as 502 `CE_COMMUNICATION_ERROR` — the refresh may still happen asynchronously.","operationId":"handle_update_registry_decimals","requestBody":{"content":{"application/json":{"schema":{"$ref":"#/components/schemas/AssetDecimalsRequest"}}},"required":true},"responses":{"200":{"description":"Refresh accepted"},"400":{"description":"Refresh rejected","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ApiErrorResponse"}}}},"401":{"description":"Auth token missing or invalid","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ApiErrorResponse"}}}},"429":{"description":"Rate limit exceeded","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ApiErrorResponse"}}}},"502":{"description":"Failed to communicate with clearing engine, including the ambiguous wait timeout","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ApiErrorResponse"}}}},"503":{"description":"OMS warming up; literal body `{\"error\": \"warming up\", \"reason\": …}` (not ApiErrorResponse)"}},"security":[{"bearer_token":[],"user_id_header":[]}]}},"/registry/risk-parameters":{"get":{"tags":["Registry"],"summary":"Risk parameters from clearing engine","description":"Returns the CE risk parameter map received via overlay (same data CE previously exposed on its permissionless `/params`). Empty until the first CE snapshot or gossip update.\n\nShape: a map `asset index (stringified u64) -> RiskParameters`. Scaling conventions (see *Units & Amount Encodings* in the introduction): `collateral_factor`/`liability_factor` are u8 percentages scaled by 100 (`50` = 50%); rate/clamp fields (`initial_margin_clamps`, `initial_margin_factors`, `max_funding_rate`, `max_utilization_rate`, `utilization_kinks`, `rate_at_kinks`, `quote_*`, `skew_factor`, `base_funding_rate` (hourly, signed), `skew_cliff`, `premium_clamp`) are scaled by 1,000,000 (`500000` = 50%); U256 caps (`max_collateral`, `max_total_open_interest_notional`, `max_spot_open_interest`, `max_utilization`, `min_sub_account_balance_usd`) are base-10 decimal strings in 1e18 CE-internal units. These numbers gate margin and liquidation — do not guess the scaling.","operationId":"handle_get_registry_risk_parameters","responses":{"200":{"description":"Risk parameters map: asset index -> RiskParameters","content":{"application/json":{"schema":{"type":"object"},"example":{"2":{"collateral_factor":80,"liability_factor":90,"max_collateral":"1000000000000000000000000","max_total_open_interest_notional":"5000000000000000000000000","max_spot_open_interest":"1000000000000000000000","max_utilization":"900000000000000000","isolated_only":false,"initial_margin_clamps":[0,500000],"initial_margin_factors":[100000,200000],"max_funding_rate":10000,"max_utilization_rate":1500000,"utilization_kinks":[0,400000,700000,850000,950000,1000000],"rate_at_kinks":[0,20000,50000,100000,300000,1500000],"quote_utilization_kinks":[0,1000000],"quote_rate_at_kinks":[0,100000],"skew_factor":500000,"base_funding_rate":1250,"skew_cliff":100000,"premium_clamp":50000,"buffer_multiplier":2,"min_sub_account_balance_usd":"10000000000000000000"}}}}},"429":{"description":"Rate limit exceeded","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ApiErrorResponse"}}}},"503":{"description":"Risk parameters not yet available","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ApiErrorResponse"}}}}}}},"/registry/vaults":{"get":{"tags":["Registry"],"summary":"Vaults from clearing engine","description":"Returns OMS-cached custody vault addresses from CE snapshot + incremental vault updates. Each entry decomposes a `ChainAddress` into its parts: `chainId` (VM chain id as an integer), `routingId` (tplus chain-type route: `0` EVM, `1` Solana, `2` Bitcoin), and `address` (`0x`-prefixed hex of the 32-byte address, encoded identically for every chain — use `routingId` to interpret it).","operationId":"handle_get_registry_vaults","responses":{"200":{"description":"Custody vault addresses, one object per vault","content":{"application/json":{"schema":{"type":"array","items":{"$ref":"#/components/schemas/VaultEntry"}},"example":[{"chainId":42161,"routingId":0,"address":"0x62622e77d1349face943c6e7d5c01c61465fe1dc000000000000000000000000"}]}}},"429":{"description":"Rate limit exceeded","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ApiErrorResponse"}}}},"503":{"description":"Vaults not yet available","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ApiErrorResponse"}}}}}}},"/settlement/init":{"post":{"tags":["Account"],"summary":"Initialize settlement via OMS->overlay","description":"Proxies a signed settlement request to the clearing engine and waits for the init outcome. **Amounts are HEX-encoded integer strings** in CE-internal 1e18 units (see *Units & Amount Encodings* in the introduction — sending base-10 here silently moves the wrong amount: digits-only strings round-trip the hex codec unchanged, so the signature still verifies and there is no rejection safety net; the CE converts to the token's on-chain decimals when signing the approval). `signature` is an Ed25519 signature over the compact JSON of `inner` (see *Signing Orders*). A CE business rejection returns HTTP 200 with `success: false` — check the body. A timed-out overlay wait surfaces as 502 `CE_COMMUNICATION_ERROR`; the settlement may still have been initiated and inventory locked — check `GET /settlement/signatures/{user_id}` before retrying.","operationId":"handle_init_settlement_action","requestBody":{"content":{"application/json":{"schema":{"$ref":"#/components/schemas/TxSettlementRequest"}}},"required":true},"responses":{"200":{"description":"CE replied — check the body: rejections also return HTTP 200 with `success: false`","content":{"application/json":{"schema":{"$ref":"#/components/schemas/SettlementInitResponse"},"example":{"success":true,"approval":{}}}}},"401":{"description":"Auth token missing or does not match signer","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ApiErrorResponse"}}}},"429":{"description":"Rate limit exceeded","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ApiErrorResponse"}}}},"502":{"description":"Failed to communicate with clearing engine, including the ambiguous wait timeout (settlement state unknown)","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ApiErrorResponse"}}}}},"security":[{"bearer_token":[],"user_id_header":[]}]}},"/settlement/signatures/{user_id}":{"get":{"tags":["Account"],"summary":"Get settlement signatures via OMS proxy","description":"Returns the user's outstanding CE settlement-approval signatures (e.g. to re-deliver to a settler). These payloads are forwarded on-chain: each entry is `{\"inner\": {\"signature\": \"<hex-encoded approval signature>\", \"nonce\": <u64 settlement nonce>}, \"expiry\": <unix seconds expiry of the signature, not of the locked inventory>, \"chain_id\": \"<9-byte hex chain id>\", \"epoch_hash\": <32-byte array or null — only set for withdrawals>}.","operationId":"handle_get_settlement_signatures","parameters":[{"name":"user_id","in":"path","description":"User public key, 64 lowercase hex chars (no 0x prefix)","required":true,"schema":{"type":"string"}}],"responses":{"200":{"description":"Outstanding settlement approval signatures for this user","content":{"application/json":{"schema":{"type":"array","items":{"$ref":"#/components/schemas/OneTimeSignatureDoc"}},"example":[{"inner":{"signature":"0123456789abcdef","nonce":7},"expiry":1893456000,"chain_id":"000000000000000001","epoch_hash":null}]}}},"401":{"description":"Auth token missing or does not match requested user","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ApiErrorResponse"}}}},"429":{"description":"Rate limit exceeded","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ApiErrorResponse"}}}},"502":{"description":"Failed to communicate with clearing engine","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ApiErrorResponse"}}}}},"security":[{"bearer_token":[],"user_id_header":[]}]}},"/solvency/user/{user_id}":{"get":{"tags":["Account"],"summary":"Check user solvency (deprecated)","description":"Deprecated: use GET /margin/user/{user_id} instead, which returns solvency as part of a richer margin breakdown.\n\nComputes solvency status and distance from liquidation for each sub-account. By default spans all sub-accounts; pass `sub_account` to scope to one.","operationId":"handle_get_user_solvency","parameters":[{"name":"user_id","in":"path","description":"Hex-encoded user public key","required":true,"schema":{"type":"string"}},{"name":"sub_account","in":"query","description":"Restrict to a single sub-account by account index (default: all sub-accounts)","required":false,"schema":{"type":"integer","format":"int64","minimum":0}}],"responses":{"200":{"description":"Solvency results per sub-account","content":{"application/json":{"schema":{"$ref":"#/components/schemas/SolvencyResponse"}}}},"400":{"description":"Invalid user public key format","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ApiErrorResponse"}}}},"401":{"description":"Auth token missing or does not match user_id","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ApiErrorResponse"}}}},"404":{"description":"User not found in OMS inventory","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ApiErrorResponse"}}}},"429":{"description":"Rate limit exceeded","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ApiErrorResponse"}}}},"503":{"description":"Risk parameters not available","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ApiErrorResponse"}}}}},"security":[{"bearer_token":[],"user_id_header":[]}]}},"/sync/pending_settlements":{"get":{"tags":["WebSockets"],"summary":"Stream pending sync-book settlements for a market maker","description":"Authenticated market-maker stream. The OMS only emits a pending settlement on this socket when `mm_pubkey` matches the authenticated `User-Id`. Each message carries the taker, executor address, user-signed settlement request, maker-side fills, and filled taker quantity needed to complete atomic settlement. Messages are one-shot broadcasts: the subscription is established after the welcome frame is sent, so settlements emitted while the connection is being set up are not delivered — connect before quoting, and treat a gap as possible after any reconnect. See *WebSocket Contract* in the introduction.","operationId":"document_pending_settlements_ws","responses":{"101":{"description":"WebSocket upgrade established","content":{"application/json":{"schema":{"$ref":"#/components/schemas/PendingSyncSettlement"}}}},"401":{"description":"Auth token missing or invalid","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ApiErrorResponse"}}}}},"security":[{"bearer_token":[],"user_id_header":[]}]}},"/sync_book/create":{"post":{"tags":["Markets"],"summary":"Create a sync orderbook for the authenticated MM","description":"Creates a sync orderbook for one market asset. The caller must authenticate as a pre-registered market maker; otherwise OMS returns 403 with `Caller is not registered as a market maker`.","operationId":"handle_create_sync_book","requestBody":{"content":{"application/json":{"schema":{"$ref":"#/components/schemas/CreateSyncBookRequest"}}},"required":true},"responses":{"200":{"description":"Sync book created","content":{"application/json":{"schema":{"$ref":"#/components/schemas/CreateSyncBookResponse"}}}},"400":{"description":"Invalid request body","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ApiErrorResponse"}}}},"401":{"description":"Auth token missing or invalid","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ApiErrorResponse"}}}},"403":{"description":"Authenticated caller is not registered as a market maker","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ApiErrorResponse"}}}},"429":{"description":"Rate limit exceeded","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ApiErrorResponse"}}}},"500":{"description":"Internal error","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ApiErrorResponse"}}}},"504":{"description":"Timed out waiting for orderbook confirmation","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ApiErrorResponse"}}}}},"security":[{"bearer_token":[],"user_id_header":[]}]}},"/trades/user/events/{user_id}":{"get":{"tags":["WebSockets"],"summary":"Stream all trade events for user","description":"\n    ### User Trades Stream (All Events)\n    **Events:** Pending, confirmed, and rollbacked trade events for a user, as `UserTrade` objects (order_id, side, maker/taker details).\n\n    **Authentication:** Required.\n    * Preferred: `Authorization: Bearer <token>` + `User-Id: <id>`\n    * WS-Compatible: `Sec-WebSocket-Protocol: Bearer-<token>, User-<id>`\n\n    A valid token whose user does not match `{user_id}` does NOT yield HTTP 401: the upgrade succeeds (101) and the server sends an in-band rejection frame, then closes. See *WebSocket Contract* in the introduction.\n\n    **Resync Policy:** On reconnection or detected lag, clients **must** re-fetch REST snapshots to ensure state consistency.\n\n    **Lag Signal (v1):** Server emits `WsError` with code `RESYNC_REQUIRED` and closes the socket with code `1013` (`resync_required`).\n\n    **Message Shapes:** See `WsWelcome`, `WsEnvelope`, and `WsError` in the Schemas section.\n\n    **Protocol:** Default raw (v0). Opt-in envelope via `Sec-WebSocket-Protocol: tplus.ws.v1`.","operationId":"document_user_trades_ws","parameters":[{"name":"user_id","in":"path","description":"Hex-encoded user public key","required":true,"schema":{"type":"string"}}],"responses":{"101":{"description":"WebSocket upgrade established","content":{"application/json":{"schema":{"$ref":"#/components/schemas/UserTrade"}}}},"401":{"description":"Auth token missing or invalid (identity mismatch is rejected in-band after the upgrade, not with 401)","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ApiErrorResponse"}}}}},"security":[{"bearer_token":[],"user_id_header":[]}]}},"/trades/user/{user_id}":{"get":{"tags":["WebSockets"],"summary":"Stream confirmed trades for user","description":"\n    ### User Trades Stream (Finalized)\n    **Events:** Confirmed trade events for a user.\n\n    **Authentication:** Required.\n    * Preferred: `Authorization: Bearer <token>` + `User-Id: <id>`\n    * WS-Compatible: `Sec-WebSocket-Protocol: Bearer-<token>, User-<id>`\n\n    **Resync Policy:** On reconnection or detected lag, clients **must** re-fetch REST snapshots to ensure state consistency.\n\n    **Lag Signal (v1):** Server emits `WsError` with code `RESYNC_REQUIRED` and closes the socket with code `1013` (`resync_required`).\n\n    **Message Shapes:** See `WsWelcome`, `WsEnvelope`, and `WsError` in the Schemas section.\n\n    **Protocol:** Default raw (v0). Opt-in envelope via `Sec-WebSocket-Protocol: tplus.ws.v1`.","operationId":"document_user_finalized_trades_ws","parameters":[{"name":"user_id","in":"path","description":"Hex-encoded user public key","required":true,"schema":{"type":"string"}}],"responses":{"101":{"description":"WebSocket upgrade established","content":{"application/json":{"schema":{"$ref":"#/components/schemas/UserTrade"}}}},"401":{"description":"Auth token missing or does not match user_id","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ApiErrorResponse"}}}}},"security":[{"bearer_token":[],"user_id_header":[]}]}},"/withdrawal/cancel":{"post":{"tags":["Account"],"summary":"Cancel withdrawal via OMS proxy","description":"Proxies a signed cancel request to the clearing engine.\n\n**State consistency:** Same timeout ambiguity as init - CE may have cancelled and unlocked inventory while OMS returns `TIMEOUT_UNKNOWN_STATE`. Use `GET /withdrawal/queue/{user_id}` before retrying.","operationId":"handle_cancel_withdrawal_action","requestBody":{"content":{"application/json":{"schema":{"$ref":"#/components/schemas/CancelWithdrawalRequest"}}},"required":true},"responses":{"200":{"description":"CE replied — check the body before treating this as success: a CE rejection ALSO returns HTTP 200, with `success: false` and the reason in `details`. Only `success: true` means the withdrawal was cancelled.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/WithdrawalInitResponse"}}}},"400":{"description":"Invalid request body","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ApiErrorResponse"}}}},"401":{"description":"Auth token missing or does not match signer","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ApiErrorResponse"}}}},"429":{"description":"Rate limit exceeded","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ApiErrorResponse"}}}},"502":{"description":"Failed to communicate with clearing engine","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ApiErrorResponse"}}}},"503":{"description":"OMS warming up; literal body `{\"error\": \"warming up\", \"reason\": …}` (not ApiErrorResponse)"},"504":{"description":"Timed out waiting for CE; outcome unknown - check withdrawal queue","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ApiErrorResponse"}}}}},"security":[{"bearer_token":[],"user_id_header":[]}]}},"/withdrawal/init":{"post":{"tags":["Account"],"summary":"Initialize withdrawal via OMS->overlay","description":"Proxies a signed withdrawal to the clearing engine and waits for success or rejection.\n\n`inner.amount` is a hex string without `0x` on the JSON wire.\n\n**State consistency:** If the CE applies the request but the reply is delayed or lost, OMS returns HTTP 504 with error code `TIMEOUT_UNKNOWN_STATE`. Inventory may already be locked and the withdrawal queued - call `GET /withdrawal/queue/{user_id}` before retrying (retries can fail with nonce conflicts).","operationId":"handle_init_withdrawal_action","requestBody":{"content":{"application/json":{"schema":{"$ref":"#/components/schemas/WithdrawalRequest"}}},"required":true},"responses":{"200":{"description":"Withdrawal initialization accepted","content":{"application/json":{"schema":{"$ref":"#/components/schemas/WithdrawalInitResponse"},"example":{"success":true}}}},"400":{"description":"Withdrawal initialization rejected by the CE, OR the OMS failed to send the request to the CE — both return `WithdrawalInitResponse` with `success: false` and the reason in `details` (a send failure was never applied and is safe to retry)","content":{"application/json":{"schema":{"$ref":"#/components/schemas/WithdrawalInitResponse"},"example":{"success":false,"details":"insufficient inventory"}}}},"401":{"description":"Auth token missing or does not match signer","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ApiErrorResponse"}}}},"429":{"description":"Rate limit exceeded","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ApiErrorResponse"}}}},"502":{"description":"Unexpected response from clearing engine (rare; CE send failures surface as 400, timeouts as 504)","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ApiErrorResponse"}}}},"503":{"description":"OMS warming up; literal body `{\"error\": \"warming up\", \"reason\": …}` (not ApiErrorResponse)"},"504":{"description":"Timed out waiting for CE; outcome unknown — check withdrawal queue","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ApiErrorResponse"}}}}},"security":[{"bearer_token":[],"user_id_header":[]}]}},"/withdrawal/queue/{user_id}":{"get":{"tags":["Account"],"summary":"Get queued withdrawals via OMS proxy","description":"Lists queued withdrawals for the user.\n\nIf the CE reply times out, OMS returns HTTP 504 with `TIMEOUT_UNKNOWN_STATE`; retry this read.","operationId":"handle_get_queued_withdrawals","parameters":[{"name":"user_id","in":"path","description":"Hex-encoded user public key","required":true,"schema":{"type":"string"}}],"responses":{"200":{"description":"Queued withdrawals for the user","content":{"application/json":{"schema":{"type":"array","items":{"$ref":"#/components/schemas/QueuedWithdrawalDoc"}},"example":[{"user":"0123456789abcdef0123456789abcdef0123456789abcdef0123456789abcdef","asset":"0000000000000000000000000000000000000000000000000000000000000000@000000000000000001","amount":"1000000000000000000","nonce":3,"target":"0123456789abcdef0123456789abcdef0123456789abcdef0123456789abcdef","status":{"type":"approved","approvals":[{"inner":{"signature":"0123456789abcdef","nonce":3},"expiry":1893456000,"chain_id":"000000000000000001","epoch_hash":[1,35,69,103,137,171,205,239,1,35,69,103,137,171,205,239,1,35,69,103,137,171,205,239,1,35,69,103,137,171,205,239]}]}}]}}},"401":{"description":"Auth token missing or does not match requested user","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ApiErrorResponse"}}}},"429":{"description":"Rate limit exceeded","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ApiErrorResponse"}}}},"502":{"description":"Failed to communicate with clearing engine","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ApiErrorResponse"}}}},"504":{"description":"Timed out waiting for CE","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ApiErrorResponse"}}}}},"security":[{"bearer_token":[],"user_id_header":[]}]}}},"components":{"schemas":{"AccountIndex":{"type":"integer","format":"int64","minimum":0},"AccountMarginInfo":{"type":"object","description":"Margin breakdown for a single sub-account.","required":["account_equity","available_margin","utilized_margin","maintenance_margin_surplus","is_solvent","is_liquidatable","total_upnl"],"properties":{"account_equity":{"type":"string","description":"Total account equity (sum of all assets at mark price, no haircuts applied).","example":"250000.0"},"account_leverage":{"type":["string","null"],"description":"total_notional / equity. None if equity is zero.","example":"2.0"},"available_margin":{"type":"string","description":"Trading capacity available for new exposure under IM rules.\nThis is IM-based available margin after open-order pressure adjustments.","example":"200000.0"},"im_surplus":{"type":["string","null"],"description":"Raw IM surplus without open-order pressure.\nNull in v1 until full ProtocolValues plumbing is guaranteed everywhere.","example":"null"},"is_liquidatable":{"type":"boolean","description":"Whether the account can be liquidated."},"is_solvent":{"type":"boolean","description":"Whether the account passes the IM solvency check."},"maintenance_margin_surplus":{"type":"string","description":"Distance from liquidation (MM surplus).\nHow much equity can drop before liquidation begins.","example":"225000.0"},"mm_requirement":{"type":["string","null"],"description":"OMS reporting estimate derived from per-position margin details.\nNull when the account has no positions. Liquidation does not use this value:\nthe core margin engine uses `maintenance_margin_surplus` / `is_liquidatable`.","example":"25000.0"},"net_apy":{"type":["string","null"],"description":"Null in v1 until IE funding-rate subscription is wired into OMS responses.","example":"null"},"positions":{"type":["array","null"],"items":{"$ref":"#/components/schemas/PositionMarginInfo"},"description":"Per-position breakdown (only if include_positions=true)."},"total_upnl":{"type":"string","description":"Total unrealized PnL across all positions.","example":"1250.0"},"utilized_margin":{"type":"string","description":"Total margin consumed by positions (adjusted liabilities with LF and IM pricing).\nZero when the account has no positions.","example":"50000.0"}}},"AccountSolvencyResult":{"type":"object","required":["is_solvent"],"properties":{"distance_from_liquidation":{"type":["string","null"],"description":"Buffer (in quote/USD terms) between current equity and the liquidation\nthreshold; `null` when it cannot be computed (e.g. missing risk params).","example":"1000.0"},"is_solvent":{"type":"boolean","description":"`true` if the sub-account currently meets maintenance-margin requirements."}}},"AccountStatsUpdate":{"type":"object","description":"Account stats update containing only changed sub-accounts and assets.\n\nValues are the current state after the change, not the amount of change.","required":["userId","updatedAccounts","timestampNs"],"properties":{"timestampNs":{"type":"integer","format":"int64","description":"Event timestamp (nanoseconds since Unix epoch)","minimum":0},"updatedAccounts":{"type":"object","description":"Only the sub-accounts that changed (keyed by account index as string)","additionalProperties":{"$ref":"#/components/schemas/SubAccountUpdate"},"propertyNames":{"type":"string"}},"userId":{"type":"string","description":"User identifier (hex-encoded public key)"}}},"AddSingleSignerActionResponse":{"type":"object","required":["success","result"],"properties":{"result":{"$ref":"#/components/schemas/AddSingleSignerActionResult","description":"Detailed outcome: success, or failure with an error message."},"success":{"type":"boolean","description":"`true` if the signer was added (mirrors `result`)."}}},"AddSingleSignerActionResult":{"oneOf":[{"type":"string","description":"The signer was added.","enum":["succeeded"]},{"type":"object","description":"The request was rejected; `error` carries the reason.","required":["failed"],"properties":{"failed":{"type":"object","description":"The request was rejected; `error` carries the reason.","required":["error"],"properties":{"error":{"type":"string"}}}}}],"description":"Outcome of adding a single signer to a user's multisig config."},"AddSingleSignerRequest":{"type":"object","description":"Signed request to add one temporary or persistent signer.","required":["inner","signature"],"properties":{"additional_signers":{"type":"array","items":{"$ref":"#/components/schemas/AdditionalSigner"},"description":"Optional multisig co-signatures over the same add-signer payload."},"inner":{"$ref":"#/components/schemas/AddSingleSignerRequestPayload","description":"Add-signer payload being signed and submitted."},"signature":{"type":"array","items":{"type":"integer","format":"int32","minimum":0},"description":"Signature bytes as a JSON array of integers. Sign the action-specific preimage for `inner`; see `/guides/signing`."}}},"AddSingleSignerRequestPayload":{"type":"object","description":"Add-signer payload before signing.","required":["user","oms_nonce","ce_nonce","signer","weight","session_duration_ns"],"properties":{"ce_nonce":{"$ref":"#/components/schemas/u64","description":"CE multisig config nonce from `GET /nonce/{user_id}` (`ce_nonce`)."},"oms_nonce":{"type":"string","description":"OMS nonce from `GET /nonce/{user_id}`."},"session_duration_ns":{"type":"integer","format":"int64","description":"Session duration in nanoseconds.","minimum":0},"signer":{"$ref":"#/components/schemas/SignerKey"},"user":{"$ref":"#/components/schemas/UserPublicKey"},"weight":{"type":"integer","format":"int32","minimum":0}}},"AdditionalSigner":{"type":"object","required":["signer","signature"],"properties":{"signature":{"type":"array","items":{"type":"integer","format":"int32","minimum":0},"description":"Co-signer signature bytes as a JSON array of integers over the same action-specific preimage as the top-level request; see `/guides/signing`."},"signer":{"$ref":"#/components/schemas/SignerKey","description":"Public key / identifier of the co-signer."}}},"ApiErrorBody":{"type":"object","description":"Inner error body - same shape as the WS v1 `WsErrorOut`.","required":["code","message"],"properties":{"code":{"type":"string","description":"Machine-readable error code (`SCREAMING_SNAKE`)."},"details":{"description":"Optional structured details (e.g. order_id, field names, CE error text, and the\nsolvency `rejection` breakdown for InsufficientMargin/InsufficientInventory)."},"message":{"type":"string","description":"Human-readable error description."},"retryable":{"type":["boolean","null"],"description":"Whether the client should retry the request."},"span_id":{"type":["string","null"],"description":"Span id of the request, for correlating with service logs."},"trace_id":{"type":["string","null"],"description":"Trace id of the request, for correlating with service logs."}}},"ApiErrorCode":{"type":"string","description":"Machine-readable error codes for all OMS API errors.\n\nSerialized as `SCREAMING_SNAKE_CASE` strings in JSON responses.\nMirrors the WS v1 error code pattern for cross-transport consistency.","enum":["INVALID_JSON","INVALID_QUERY","INVALID_REQUEST","INVALID_USER_ID","ORDER_ID_TOO_LONG","BATCH_TOO_LARGE","INVALID_NAME","RESERVED_SUB_ACCOUNT","ORDER_NOT_FOUND","MARKET_NOT_FOUND","USER_NOT_FOUND","ASSET_NOT_FOUND","MULTISIG_CONFIG_NOT_FOUND","SUB_ACCOUNT_NOT_FOUND","NOT_FOUND","INSUFFICIENT_MARGIN","INSUFFICIENT_INVENTORY","INVENTORY_NOT_FOUND","REDUCE_ONLY_WOULD_INCREASE","INVALID_ORDER","INVALID_PARENT_ORDER","WOULD_CROSS","ORDER_EXPIRED","COULD_NOT_FILL","CANT_REPLACE_MARKET_ORDER","WRONG_AUTHORIZATION","INVALID_TRIGGER_CONDITION","MISSING_BOOK_DECIMALS","REQUEST_TOO_OLD","INVALID_BOOK_CONFIGURATION","ALREADY_CANCELLED","INVALID_SETTLEMENT_REQUEST","ORDER_ID_ALREADY_EXISTS","UNAUTHORIZED","NONCE_EXPIRED","NONCE_NOT_FOUND","INVALID_NONCE","INVALID_SIGNATURE","SIGNER_NOT_AUTHORIZED","SIGNER_EXPIRED","INSUFFICIENT_SIGNATURE_WEIGHT","RATE_LIMITED","TIMEOUT","TIMEOUT_UNKNOWN_STATE","CE_COMMUNICATION_ERROR","CE_REJECTED","RISK_PARAMS_UNAVAILABLE","FUNDING_RATE_UNAVAILABLE","ASSET_CONFIG_UNAVAILABLE","ASSET_DECIMALS_UNAVAILABLE","VAULTS_UNAVAILABLE","REGISTRATION_FAILED","METHOD_NOT_ALLOWED","INTERNAL_ERROR"]},"ApiErrorResponse":{"type":"object","description":"Top-level API error response envelope.\n\n```json\n{\n  \"error\": {\n    \"code\": \"INSUFFICIENT_MARGIN\",\n    \"message\": \"Insufficient margin for order\",\n    \"details\": {\"order_id\": \"abc123\"},\n    \"retryable\": false\n  }\n}\n```","required":["error"],"properties":{"error":{"$ref":"#/components/schemas/ApiErrorBody","description":"The error payload (code, message, optional details, retry hint)."}}},"AssetDecimalsRequest":{"type":"object","required":["assets"],"properties":{"assets":{"type":"array","items":{"type":"string"},"description":"Asset addresses as stringified `ChainAddress` values:\n`\"<32-byte hex address>@<9-byte hex chain>\"`.","example":["3073f7aaa4db83f95e9fff17424f71d4751a3073000000000000000000000000@000000000000000001"]}}},"AssetIdentifier":{"type":"string","description":"Asset identifier: either a numeric index (e.g. \"200\") or a hex address@chain pair.","examples":["200"]},"AssetPressure":{"type":"object","description":"Open-order pressure consumed by a single asset's resting orders.","required":["asset","pressure_usd"],"properties":{"asset":{"$ref":"#/components/schemas/AssetIdentifier","description":"The asset whose resting orders contribute this pressure."},"pressure_usd":{"type":"string","description":"Open-order pressure attributed to this asset, in USD.","example":"52.50"}}},"AssetResponse":{"type":"object","description":"Per-asset response with chain configurations and associated markets.","required":["asset_id","asset_configs","markets"],"properties":{"asset_configs":{"type":"object","description":"Per-chain asset configurations (address, deposit limits).\nKeyed by chain address in \"hex_address@hex_chain\" format.","additionalProperties":{"$ref":"#/components/schemas/ChainAssetConfig"},"propertyNames":{"type":"string"}},"asset_id":{"type":"string","description":"Asset identifier, as a stringified `AssetIdentifier`: index form like `\"200\"`,\nor `\"<hex address>@<hex chain>\"` for isolated, chain-specific assets.","example":"200"},"markets":{"type":"array","items":{"$ref":"#/components/schemas/MarketResponse"},"description":"Markets that trade this asset."}}},"AtRiskOfLiquidation":{"type":"object","required":["asset_identifier","short_quantity","long_quantity","estimated_short_liquidation_price","estimated_long_liquidation_price"],"properties":{"asset_identifier":{"$ref":"#/components/schemas/AssetIdentifier","description":"At risk asset"},"estimated_long_liquidation_price":{"$ref":"#/components/schemas/LiquidationPriceEstimate","description":"Long liquidation price estimate"},"estimated_short_liquidation_price":{"$ref":"#/components/schemas/LiquidationPriceEstimate","description":"Short liquidation price estimate"},"long_quantity":{"type":"string","description":"Long quantity (to be sold in case of liquidation)","example":"40000"},"short_quantity":{"type":"string","description":"Short quantity (to be bought back in case of liquidation)","example":"21000"}}},"AuthRequestBody":{"type":"object","description":"Request body for `POST /auth`.","required":["user_id","nonce","signature","additional_signers"],"properties":{"additional_signers":{"type":"array","items":{"$ref":"#/components/schemas/AdditionalSigner"},"default":"Vec::new","description":"Optional multisig co-signatures over the same nonce payload."},"nonce":{"type":"string","example":"a1b2c3d4e5f6...","description":"Exact nonce string returned by `GET /nonce/{user_id}`."},"signature":{"type":"array","items":{"type":"integer","format":"int32","minimum":0},"description":"Signature bytes as a JSON array of integers over the raw UTF-8 nonce string; see `/guides/signing`."},"user_id":{"type":"string","example":"0xeb886a56f9f0efa64432678cebf1270e9314a758e6eb697a606202a451e3e82e","description":"Hex-encoded user public key."}}},"AuthTokenResponse":{"type":"object","description":"Bearer token from `POST /auth`.","required":["token","expiry_ns"],"properties":{"expiry_ns":{"type":"integer","format":"int64","example":1710086400000000000,"minimum":0,"description":"Token expiry (ns since the Unix epoch); valid about 24 hours."},"token":{"type":"string","example":"9f8e7d6c5b4a...","description":"Bearer token to send as the `Authorization: Bearer <token>` header."}}},"AutomaticReplaceOrder":{"type":"object","description":"OMS generated reduction in an order:\nuseful to reduce the quantity of an order when a user position\ndoes not allow his initial order to still be valid","required":["order_id","timestamp_ns"],"properties":{"book_quantity_decimals":{"type":"integer","format":"int32","example":"2","description":"Quantity decimals for the market."},"new_quantity":{"type":"integer","format":"int64","example":"1000","minimum":0,"description":"New quantity in book quantity units."},"order_id":{"type":"string","example":"rt6G7V8gRAG4p7lfidkeUw==","description":"Id of the order to replace."},"timestamp_ns":{"type":"integer","format":"int64","example":"1750146943779456128","minimum":0,"description":"Client-set timestamp (ns since the Unix epoch) used to order concurrent replaces."}}},"BalanceJson":{"type":"object","required":["credits","liabilities"],"properties":{"credits":{"type":"string","description":"Amount of the asset owned (the credit side of the ledger), in human-readable\nunits. `null` if the raw value cannot be represented as a decimal.","example":"1000.0"},"liabilities":{"type":"string","description":"Amount of the asset owed (the debit/borrowed side of the ledger), in\nhuman-readable units. `null` if the raw value cannot be represented as a decimal.","example":"0.0"}}},"BatchCancelRequest":{"type":"object","required":["order_ids"],"properties":{"order_ids":{"type":"array","items":{"type":"string"},"description":"Order IDs to cancel."}}},"BatchCancelResponse":{"type":"object","required":["status","dispatched"],"properties":{"dispatched":{"type":"integer","description":"Number of order IDs dispatched for cancellation (IDs not owned by\nthe caller are silently excluded).","minimum":0},"status":{"type":"string","description":"Dispatch status; `\"accepted\"` once the cancels were queued.","example":"accepted"}}},"BatchCancelResponseDoc":{"type":"object","description":"Documented response shape for batch cancel. The handler omits `failures`\nwhen every per-market dispatch succeeds and includes it on partial dispatch.","required":["status","dispatched"],"properties":{"dispatched":{"type":"integer","description":"Number of owned order IDs dispatched for cancellation.","minimum":0},"failures":{"type":["array","null"],"items":{"type":"string"},"description":"Asset ids whose per-market cancel dispatch failed. Omitted on success."},"status":{"type":"string","description":"Dispatch status: `\"accepted\"` when all per-market dispatches were queued,\nor `\"partial\"` when at least one market dispatch failed.","example":"accepted"}}},"BatchCreateOrderResult":{"type":"object","description":"Per-order outcome within a batch-create response.","required":["order_id","status"],"properties":{"order_id":{"type":"string","description":"Client-supplied order id this result corresponds to."},"reason":{"oneOf":[{"type":"null"},{"$ref":"#/components/schemas/OrderFailureReason","description":"Why the order was rejected; present only when `status` is `Rejected`."}]},"status":{"$ref":"#/components/schemas/CreateOrderStatus","description":"Whether the orderbook accepted (`Received`) or rejected this order."}}},"BatchCreateRequest":{"type":"object","required":["orders"],"properties":{"orders":{"type":"array","items":{"$ref":"#/components/schemas/CreateOrderRequest"}}}},"BatchCreateResponse":{"type":"object","required":["results"],"properties":{"results":{"type":"array","items":{"$ref":"#/components/schemas/BatchCreateOrderResult"},"description":"One result per submitted order, in submission order."}}},"CancelAllOrdersResponse":{"type":"object","required":["status"],"properties":{"status":{"type":"string","description":"Dispatch status; `\"accepted\"` once the cancel-all request was queued.","example":"accepted"}}},"CancelOrder":{"type":"object","required":["signer","order_id","asset_id","protocol_version"],"properties":{"asset_id":{"type":"string","example":"200"},"order_id":{"type":"string"},"protocol_version":{"$ref":"#/components/schemas/TplusProtocolVersion"},"signer":{"$ref":"#/components/schemas/UserPublicKey"}}},"CancelOrderRequest":{"type":"object","required":["cancel","signature","post_sign_timestamp"],"properties":{"cancel":{"$ref":"#/components/schemas/CancelOrder","description":"Cancel payload whose compact signable encoding is signed."},"post_sign_timestamp":{"type":"integer","format":"int64","description":"Client-set timestamp (ns since the Unix epoch) of when the cancel was signed.","minimum":0},"receive_timestamp_ns":{"type":["integer","null"],"format":"int64","description":"Server-set OMS receive timestamp; clients should omit this field.","minimum":0},"signature":{"type":"array","items":{"type":"integer","format":"int32","minimum":0},"description":"Signature bytes as a JSON array of integers. Sign the action-specific preimage for `cancel`; see `/guides/signing`."}}},"CancelOrderResponse":{"type":"object","required":["order_id","status"],"properties":{"order_id":{"type":"string","example":"rt6G7V8gRAG4p7lfidkeUw==","description":"Order id targeted by the cancel, echoed back for correlation."},"reason":{"oneOf":[{"type":"null"},{"$ref":"#/components/schemas/OrderFailureReason"}],"description":"Why the cancel was refused. Present only when `status` is `Rejected`."},"span_id":{"type":["string","null"],"description":"Span id of the request that produced this response, for correlating with service logs."},"status":{"$ref":"#/components/schemas/CancelOrderStatus","description":"`Received` if the orderbook accepted the cancel, `Rejected` if it was refused (see `reason`)."},"trace_id":{"type":["string","null"],"description":"Trace id of the request that produced this response, for correlating with service logs."}}},"CancelOrderStatus":{"type":"string","enum":["Received","Rejected"],"description":"Whether the orderbook accepted the cancel request (`Received`) or refused it (`Rejected`)."},"CancelWithdrawalRequest":{"type":"object","description":"The request to cancel a queued withdrawal.","required":["inner","signature"],"properties":{"additional_signers":{"type":"array","items":{"$ref":"#/components/schemas/AdditionalSigner"},"description":"Optional multisig co-signatures over the same cancel payload."},"inner":{"$ref":"#/components/schemas/InnerCancelWithdrawalRequest","description":"Withdrawal-cancel payload being signed and submitted."},"signature":{"type":"array","items":{"type":"integer","format":"int32","minimum":0},"description":"Signature bytes as a JSON array of integers. Sign the action-specific preimage for `inner`; see `/guides/signing`."}}},"ChainAssetConfig":{"type":"object","description":"Asset configuration for a specific chain deployment.","required":["address","max_deposits","max_1hr_deposits"],"properties":{"address":{"type":"string","description":"The onchain address (hex, 32 bytes)"},"max_1hr_deposits":{"type":"string","description":"Maximum deposits per 1-hour window"},"max_deposits":{"type":"string","description":"Maximum total deposits"}}},"CloseAllPreviewResponse":{"type":"object","required":["orders","errors"],"properties":{"errors":{"type":"object","description":"Positions that could not be previewed, mapping asset id to an error message.","additionalProperties":{"type":"string"},"propertyNames":{"type":"string"}},"orders":{"type":"array","items":{"$ref":"#/components/schemas/UnsignedCloseOrder"},"description":"Suggested unsigned close orders for the user's open positions."}}},"ClosePositionActionResponse":{"type":"object","required":["success"],"properties":{"failure_reason":{"type":["string","null"],"description":"Reason the request was rejected; present only when `success` is `false`."},"success":{"type":"boolean","description":"`true` if the close-position request was accepted by the clearing engine."}}},"ClosePositionRequest":{"type":"object","description":"Signed request to close one margin position.","required":["inner","signature"],"properties":{"additional_signers":{"type":"array","items":{"$ref":"#/components/schemas/AdditionalSigner"},"description":"Optional multisig co-signatures over the same close-position payload."},"inner":{"$ref":"#/components/schemas/ClosePositionRequestInner","description":"Close-position payload being signed and submitted."},"signature":{"type":"array","items":{"type":"integer","format":"int32","minimum":0},"description":"Signature bytes as a JSON array of integers. Sign the action-specific preimage for `inner`; see `/guides/signing`."}}},"ClosePositionRequestInner":{"type":"object","description":"Position to close by transferring quote inventory as needed.","required":["user","account","asset_identifier","nonce"],"properties":{"account":{"$ref":"#/components/schemas/AccountIndex"},"asset_identifier":{"$ref":"#/components/schemas/AssetIdentifier"},"nonce":{"$ref":"#/components/schemas/u64","description":"Strictly increasing per-user nonce for the close-position action domain."},"user":{"$ref":"#/components/schemas/UserPublicKey"}}},"CombinedBalanceJson":{"type":"object","required":["base","quote"],"properties":{"base":{"$ref":"#/components/schemas/BalanceJson","description":"Base-asset (traded asset) side of the margin balance."},"quote":{"$ref":"#/components/schemas/BalanceJson","description":"Quote-asset (settlement currency) side of the margin balance."}}},"CreateMarketRequest":{"type":"object","required":["asset_id"],"properties":{"asset_id":{"type":"string","example":"200"}}},"CreateMarketResponse":{"type":"object","required":["asset_id","status"],"properties":{"asset_id":{"type":"string","example":"200","description":"Asset the create-market request targeted (stringified `AssetIdentifier`)."},"market":{"oneOf":[{"type":"null"},{"$ref":"#/components/schemas/Market"}],"description":"The created market's parameters on success; `null` on rejection."},"status":{"$ref":"#/components/schemas/CreateMarketStatus","description":"Whether the request was accepted (`Received`) or rejected."}}},"CreateMarketStatus":{"type":"string","enum":["Received","Rejected"],"description":"Whether the orderbook accepted (`Received`) or rejected (`Rejected`) the create-market request."},"CreateOrderRequest":{"type":"object","required":["order","signature","post_sign_timestamp"],"properties":{"additional_signers":{"type":"array","items":{"$ref":"#/components/schemas/AdditionalSigner"},"description":"Optional multisig co-signatures over the same order payload."},"order":{"$ref":"#/components/schemas/UserOrder","description":"User order payload. For market makers posting sync quotes, set `order.details` to `SyncMaker`."},"post_sign_timestamp":{"type":"integer","format":"int64","description":"Client-set timestamp (nanoseconds since Unix epoch) of when the order was signed.","example":"1750146943779456128","minimum":0},"receive_timestamp_ns":{"type":["integer","null"],"format":"int64","description":"Server-set OMS receive timestamp; clients should omit this field.","minimum":0},"signature":{"type":"array","items":{"type":"integer","format":"int32","minimum":0},"description":"Signature bytes as a JSON array of integers. Sign the action-specific preimage for `order`; see the G1 signing reference at `/guides/signing`."}},"description":"Signed order submission. Market makers use this same request shape for `SyncMaker` quotes after creating a sync orderbook."},"CreateOrderResponse":{"type":"object","required":["order_id","status","processed_at_ns"],"properties":{"details":{"oneOf":[{"type":"null"},{"$ref":"#/components/schemas/OrderRejectionDetails","description":"Structured solvency detail for a rejection, when available."}]},"order_id":{"type":"string","example":"rt6G7V8gRAG4p7lfidkeUw==","description":"Client-supplied order id, echoed back so the caller can correlate this response with the submitted order."},"processed_at_ns":{"type":"integer","format":"int64","example":"1750146943779456128","minimum":0,"description":"OMS processing timestamp, in nanoseconds since the Unix epoch."},"reason":{"oneOf":[{"type":"null"},{"$ref":"#/components/schemas/OrderFailureReason"}],"description":"Why the order was refused. Present only when `status` is `Rejected`."},"span_id":{"type":["string","null"],"description":"Span id of the request that produced this response, for correlating with service logs."},"status":{"$ref":"#/components/schemas/CreateOrderStatus","description":"`Received` if the orderbook accepted the order for matching, `Rejected` if it was refused (see `reason`)."},"trace_id":{"type":["string","null"],"description":"Trace id of the request that produced this response, for correlating with service logs."}}},"CreateOrderStatus":{"type":"string","enum":["Received","Rejected"],"description":"Whether the orderbook accepted the new order (`Received`) or refused it (`Rejected`). `Received` means queued for matching - not necessarily filled."},"CreateSyncBookRequest":{"type":"object","required":["asset_id"],"properties":{"asset_id":{"type":"string","example":"200","description":"Market asset identifier for the sync book. Use an asset id from `GET /markets` or `GET /registry/assets`."}},"description":"Request for an authorized market maker to create a sync orderbook for one market asset."},"CreateSyncBookResponse":{"type":"object","required":["asset_id","mm_pubkey","status"],"properties":{"asset_id":{"type":"string","example":"200","description":"Asset the create-sync-book request targeted (stringified `AssetIdentifier`)."},"mm_pubkey":{"type":"string","description":"Market-maker public key that owns the sync book."},"status":{"$ref":"#/components/schemas/CreateSyncBookStatus","description":"Whether the request was accepted (`Received`) or rejected."}}},"CreateSyncBookStatus":{"type":"string","enum":["Received","Rejected"],"description":"Whether the orderbook accepted (`Received`) or rejected (`Rejected`) the create-sync-book request."},"DepositLanded":{"type":"object","description":"Emitted when an on-chain deposit has been ingested into a user's\ninventory. `amount` is in `INVENTORY_DECIMALS` (1e18) — the FE should\nformat using the asset's display decimals from `/asset/{id}`.","required":["user","asset","amount","chain_id","deposit_nonce","timestamp_ns"],"properties":{"amount":{"type":"string","description":"Amount credited, in `INVENTORY_DECIMALS` (1e18)."},"asset":{"type":"string"},"chain_id":{"type":"string"},"deposit_nonce":{"type":"integer","format":"int64","description":"Per-user, per-chain deposit nonce (for client deduplication).","minimum":0},"timestamp_ns":{"type":"integer","format":"int64","description":"CE-side ingestion timestamp (ns since epoch).","minimum":0},"user":{"$ref":"#/components/schemas/UserPublicKey"}}},"FeeTierConfig":{"type":"object","required":["min_rolling_volume_usd","taker_fee_rate","maker_fee_rate"],"properties":{"maker_fee_rate":{"type":"integer","format":"int32","description":"Maker fee scaled by `RISK_PARAM_RATE_SCALING_DENOMINATOR`.","minimum":0},"maker_rebate_rate_of_taker_fee":{"type":["integer","null"],"format":"int32","description":"Maker rebate share of taker fee scaled by `RISK_PARAM_RATE_SCALING_DENOMINATOR`.\n\nExample:\n- `None` => no rebate\n- `RISK_PARAM_RATE_SCALING_DENOMINATOR / 4` => 25% of taker fee\n- `RISK_PARAM_RATE_SCALING_DENOMINATOR / 2` => 50% of taker fee","minimum":0},"min_rolling_volume_usd":{"type":"integer","format":"int64","description":"Exclusive lower bound for this tier's rolling volume in USD.","minimum":0},"taker_fee_rate":{"type":"integer","format":"int32","description":"Taker fee scaled by `RISK_PARAM_RATE_SCALING_DENOMINATOR`.","minimum":0}}},"FundingRateResponse":{"type":"object","required":["asset_id","rate","utilisation_rate","quote_utilisation_rate","timestamp_ns","next_funding_timestamp_ns"],"properties":{"asset_id":{"type":"string","description":"Market asset identifier.","example":"200"},"lending_rate":{"type":["string","null"],"description":"Estimated asset lending (supply) rate. `utilisation_rate` is the borrow\nrate borrowers pay; suppliers earn `borrow_rate * min(borrowed, supplied)\n/ supplied` from protocol-wide aggregates (`ProtocolValues`). `null` if\nprotocol values are unavailable. An estimate only (per-user solvency caps\nomitted).","example":"0.00037"},"mark_price_at_funding":{"type":["string","null"],"description":"Mark price captured near funding snapshot time.","example":"105350.00"},"next_funding_timestamp_ns":{"type":"integer","format":"int64","description":"Scheduled timestamp for the next funding interval.","example":"1750150543779456128","minimum":0},"previous_funding_rate":{"type":["string","null"],"description":"Previously applied funding rate for this market, if known.","example":"0.00100"},"previous_timestamp_ns":{"type":["integer","null"],"format":"int64","description":"Timestamp for the previously applied funding rate, if known.","example":"1750143343779456128","minimum":0},"quote_lending_rate":{"type":["string","null"],"description":"Estimated USD (quote) lending rate for this market, using\n`quote_utilisation_rate` and protocol-wide USD spot supply. Same formula\nand caveats as `lending_rate`.","example":"0.00012"},"quote_utilisation_rate":{"type":"string","description":"Quote utilisation rate received with the funding snapshot.","example":"0.00025"},"rate":{"type":"string","description":"Latest current funding rate computed from Interest Engine state. This can update between actual funding payments.","example":"0.00125"},"timestamp_ns":{"type":"integer","format":"int64","description":"Timestamp when this funding rate was recorded.","example":"1750146943779456128","minimum":0},"utilisation_rate":{"type":"string","description":"Asset utilisation rate received with the funding snapshot.","example":"0.00075"}}},"InnerCancelWithdrawalRequest":{"type":"object","description":"Withdrawal cancellation details before signing.","required":["tplus_user","asset_address","nonce"],"properties":{"asset_address":{"type":"string","description":"Asset address as `\"<32-byte hex address>@<9-byte hex chain>\"`.","example":"3073f7aaa4db83f95e9fff17424f71d4751a3073000000000000000000000000@000000000000000001"},"nonce":{"$ref":"#/components/schemas/u64"},"tplus_user":{"$ref":"#/components/schemas/UserPublicKey"}}},"InnerMakerOrderAttachment":{"type":"object","description":"The signed inner part of a maker order attachment for delegated settlement.\n\nThe MM signs the committed trading pair and amounts so the CE can verify the\nMM's quoted price is at-or-better than the user's signed price before\napproving the settlement.","required":["mm_pubkey","settler","expires_at","asset_in","amount_in","asset_out","amount_out","chain_id"],"properties":{"amount_in":{"type":"string","description":"Amount of `asset_in` the MM receives, as a HEX-encoded integer string (base-16, bare hex, no `0x` prefix) in CE-internal 1e18 units.","example":"de0b6b3a7640000"},"amount_out":{"type":"string","description":"Amount of `asset_out` the MM gives up, as a HEX-encoded integer string (base-16, bare hex, no `0x` prefix) in CE-internal 1e18 units.","example":"de0b6b3a7640000"},"asset_in":{"type":"string","description":"Asset the MM receives (its incoming leg). Must equal the user's `asset_out`.","example":"62622e77d1349face943c6e7d5c01c61465fe1dc000000000000000000000000"},"asset_out":{"type":"string","description":"Asset the MM gives up (its outgoing leg). Must equal the user's `asset_in`.","example":"62622e77d1349face943c6e7d5c01c61465fe1dc000000000000000000000000"},"chain_id":{"type":"string","description":"9-byte chain id as hex (`routing_id || vm_id`).","example":"00000000000000a4b1"},"expires_at":{"type":"integer","format":"int64","description":"Expiry timestamp in nanoseconds. The CE will reject this attachment after this time.","minimum":0},"mm_pubkey":{"$ref":"#/components/schemas/UserPublicKey","description":"The market maker's public key."},"settler":{"$ref":"#/components/schemas/UserPublicKey","description":"The settler/executor designated by the MM."}}},"InnerOneTimeSignatureDoc":{"type":"object","required":["signature","nonce"],"properties":{"nonce":{"type":"integer","format":"int64","description":"Approval nonce.","minimum":0},"signature":{"type":"string","description":"Onchain approval signature, hex encoded."}}},"InnerSettlementRequest":{"type":"object","description":"Settlement request body before the top-level signature is attached.","required":["tplus_user","sub_account_index","mode","asset_in","amount_in","asset_out","amount_out","chain_id","nonce"],"properties":{"amount_in":{"type":"string","description":"Minimum amount entering the vault, as a HEX-encoded integer string in CE-internal 1e18 units (1 whole token = `de0b6b3a7640000`); the CE converts to the token's on-chain decimals when building the on-chain approval. Base-16, bare lowercase hex with NO `0x` prefix (the deserializer rejects `0x`); outputs are emitted the same way. NOT base-10: `\"1000000\"` is parsed as hex = 16,777,216.","example":"de0b6b3a7640000"},"amount_out":{"type":"string","description":"Amount leaving the vault, as a HEX-encoded integer string in CE-internal 1e18 units (1 whole token = `de0b6b3a7640000`); the CE converts to the token's on-chain decimals when building the on-chain approval. Base-16, bare lowercase hex with NO `0x` prefix (the deserializer rejects `0x`). NOT base-10.","example":"de0b6b3a7640000"},"asset_in":{"type":"string","description":"32-byte token address entering the vault, hex encoded.","example":"62622e77d1349face943c6e7d5c01c61465fe1dc000000000000000000000000"},"asset_out":{"type":"string","description":"32-byte token address leaving the vault, hex encoded.","example":"62622e77d1349face943c6e7d5c01c61465fe1dc000000000000000000000000"},"chain_id":{"type":"string","description":"9-byte chain id as hex (`routing_id || vm_id`).","example":"00000000000000a4b1"},"expires_at":{"type":["integer","null"],"format":"int64","description":"Optional unix-seconds expiry for the CE approval.","minimum":0},"mm_pubkey":{"oneOf":[{"type":"null"},{"$ref":"#/components/schemas/UserPublicKey","description":"Optional market-maker public key for delegated sync settlement."}]},"mode":{"$ref":"#/components/schemas/SettlementMode","description":"Settlement balance domain."},"nonce":{"$ref":"#/components/schemas/u64","description":"Per-user/sub-account settlement nonce committed into the signed request."},"settler":{"oneOf":[{"type":"null"},{"$ref":"#/components/schemas/UserPublicKey","description":"Optional settlement executor identity. Defaults to `tplus_user` unless\na maker-order attachment supplies a settler."}]},"sub_account_index":{"$ref":"#/components/schemas/AccountIndex","description":"Sub-account whose inventory is locked for settlement."},"tplus_user":{"$ref":"#/components/schemas/UserPublicKey","description":"User whose inventory is being settled."},"vault":{"type":["string","null"],"description":"Optional 32-byte target vault address, hex encoded. When omitted, the\nCE settles against the latest registered vault on `chain_id`; when set,\nit must match a registered vault on that chain.","example":"8c86e97d0bbfc9c81da954e6f1aa31d20dbcd581000000000000000000000000"}}},"InnerSubAccountTransferRequest":{"type":"object","description":"Transfer inventory between a user's sub-accounts.","required":["user","source_index","target_index","transfer_asset","transfer_amount","nonce"],"properties":{"nonce":{"$ref":"#/components/schemas/u64","description":"Strictly increasing per-user nonce for the sub-account-transfer action domain."},"source_index":{"$ref":"#/components/schemas/AccountIndex","description":"Source sub-account index."},"target_account_type":{"oneOf":[{"type":"null"},{"$ref":"#/components/schemas/SubAccountType","description":"Optional account type when creating a new destination sub-account.\n\nExamples: `\"CrossMargin\"`, `\"Spot\"`, or\n`{\"Isolated\": {\"asset\": \"2\"}}`."}]},"target_index":{"$ref":"#/components/schemas/AccountIndex","description":"Destination sub-account index."},"transfer_amount":{"type":"string","description":"Amount to transfer, as a base-10 integer string in CE internal units.","example":"1000000000000000000"},"transfer_asset":{"$ref":"#/components/schemas/AssetIdentifier","description":"Asset being moved."},"user":{"$ref":"#/components/schemas/UserPublicKey","description":"User whose inventory is being transferred."}}},"InnerUpdateMultisigConfigRequest":{"type":"object","description":"Multisig config replacement before the top-level signature is attached.","required":["user","new_config","nonce"],"properties":{"new_config":{"$ref":"#/components/schemas/MultisigConfig"},"nonce":{"$ref":"#/components/schemas/u64","description":"CE multisig config nonce from `GET /nonce/{user_id}` (`ce_nonce`)."},"user":{"$ref":"#/components/schemas/UserPublicKey"}}},"InnerWithdrawalRequest":{"type":"object","description":"Withdrawal details before the top-level signature is attached.","required":["tplus_user","asset","amount","target"],"properties":{"amount":{"type":"string","description":"Amount to withdraw, as a HEX-encoded integer string in CE-internal 1e18 units (1 whole token = `de0b6b3a7640000`) — it is locked directly against the 1e18-normalized spot balance and converted to the token's on-chain decimals only when the withdrawal approval is signed. Base-16, bare lowercase hex with NO `0x` prefix (the deserializer rejects `0x`). NOT base-10: `\"1000000\"` is parsed as hex = 16,777,216.","example":"de0b6b3a7640000"},"asset":{"type":"string","description":"Asset address as `\"<32-byte hex address>@<9-byte hex chain>\"`.","example":"3073f7aaa4db83f95e9fff17424f71d4751a3073000000000000000000000000@000000000000000001"},"nonce":{"oneOf":[{"type":"null"},{"$ref":"#/components/schemas/u64","description":"The withdrawal nonce. Defaults to the managed one (1+ last ingested)."}]},"target":{"type":"string","description":"32-byte target address, hex encoded.","example":"0000000000000000000000000000000000000000000000000000000000000000"},"tplus_user":{"$ref":"#/components/schemas/UserPublicKey"}}},"InventoryRejectionDetails":{"type":"object","description":"Spot-inventory rejection breakdown.","required":["asset","side"],"properties":{"asset":{"$ref":"#/components/schemas/AssetIdentifier","description":"The order's base asset whose spot capacity was exceeded."},"buy_shortfall_usd":{"type":"string","description":"Buy order: how much more USD of buying power the order needs than is available\n(open buys plus this order, minus what the account can cover).","example":"100.00"},"sell_shortfall_qty":{"type":"string","description":"Sell order: how much more of the asset the order tries to sell than is available\n(open sells plus this order, minus what the account holds).","example":"5.0"},"side":{"$ref":"#/components/schemas/OrderSide","description":"Side of the rejected order."}}},"JsonBookOrder":{"type":"object","required":["order_id","base_asset","account_index","is_spot","side","limit_price","quantity","amount","max_sellable_amount","max_sellable_quantity","confirmed_filled_quantity","pending_filled_quantity","confirmed_filled_amount","pending_filled_amount","confirmed_trading_fees_amount","pending_trading_fees_amount","timestamp_ns","in_flight","canceled","status","trigger_above_price","trigger_below_price","trigger_enabled_quantity","last_update_timestamp_ns","is_immediate_or_cancel","is_fill_or_kill","is_liquidation","is_auto_deleverage","is_reduce_only","max_trading_fees_rate"],"properties":{"account_index":{"$ref":"#/components/schemas/AccountIndex","description":"Sub-account the order belongs to."},"amount":{"type":["string","null"],"example":"1000","description":"Order notional in human-readable units; `null` when the order is sized by `quantity`."},"base_asset":{"type":"string","example":"200","description":"Asset the order trades, as a stringified `AssetIdentifier`."},"canceled":{"type":"boolean","description":"`true` once the order has been canceled."},"confirmed_filled_amount":{"type":"string","example":"21000","description":"Notional filled and confirmed by the clearing engine."},"confirmed_filled_quantity":{"type":"string","example":"200","description":"Quantity filled and confirmed by the clearing engine."},"confirmed_trading_fees_amount":{"type":"string","example":"210","description":"Trading fees accrued on confirmed fills."},"good_until_timestamp_ns":{"type":["integer","null"],"format":"int64","example":null,"minimum":0,"description":"Expiry for good-til-date orders (ns since the Unix epoch); `null` if not GTD."},"in_flight":{"type":"boolean","description":"`true` while a create/replace/cancel for this order is still being processed."},"is_auto_deleverage":{"type":"boolean","description":"`true` when the order was generated by the auto-deleveraging flow."},"is_fill_or_kill":{"type":"boolean","description":"`true` if the order is fill-or-kill (FOK)."},"is_immediate_or_cancel":{"type":"boolean","description":"`true` if the order is immediate-or-cancel (IOC)."},"is_liquidation":{"type":"boolean","description":"`true` if this is a forced-liquidation order."},"is_reduce_only":{"type":"boolean","description":"`true` if the order may only reduce an existing position."},"is_spot":{"type":"boolean","description":"`true` for a spot order, `false` for a margin/perp order."},"last_update_timestamp_ns":{"type":"integer","format":"int64","example":"1750146943779456128","minimum":0,"description":"Timestamp of the most recent update to this order (ns since the Unix epoch)."},"limit_price":{"type":["string","null"],"example":"105420.25","description":"Limit price in human-readable units; `null` for market orders."},"max_sellable_amount":{"type":["string","null"],"example":"1000","description":"Optional cap on the amount that may be sold (spot sells); `null` if unset."},"max_sellable_quantity":{"type":["string","null"],"example":"1000","description":"Optional cap on the quantity that may be sold (spot sells); `null` if unset."},"max_trading_fees_rate":{"type":"integer","format":"int64","example":"5000","minimum":0,"description":"Maximum trading-fee rate the order will accept, in parts-per-million."},"order_id":{"type":"string","example":"rt6G7V8gRAG4p7lfidkeUw==","description":"Client-supplied order id."},"parent_id":{"type":["string","null"],"example":"rt6G7V8gRAG4p7lfidkeUw==","description":"Order id of the parent order for trigger child orders; `null` if none."},"pending_filled_amount":{"type":"string","example":"42000","description":"Notional filled but not yet confirmed."},"pending_filled_quantity":{"type":"string","example":"400","description":"Quantity filled but not yet confirmed."},"pending_trading_fees_amount":{"type":"string","example":"420","description":"Trading fees accrued on fills not yet confirmed."},"quantity":{"type":["string","null"],"example":"1000","description":"Order quantity in human-readable base units; `null` when the order is sized by `amount`."},"side":{"$ref":"#/components/schemas/OrderSide","description":"Order side (buy or sell)."},"status":{"$ref":"#/components/schemas/ViewOrderStatus","description":"Current lifecycle status of the order."},"timestamp_ns":{"type":"integer","format":"int64","example":"1750146943779456128","minimum":0,"description":"Order creation timestamp (ns since the Unix epoch)."},"trigger_above_price":{"type":["string","null"],"description":"Trigger price for a stop/take-profit that fires when the mark rises above it; `null` if none."},"trigger_below_price":{"type":["string","null"],"description":"Trigger price for a stop/take-profit that fires when the mark falls below it; `null` if none."},"trigger_enabled_quantity":{"type":["string","null"],"description":"Quantity enabled when the trigger fired; `null` when not applicable."},"trigger_touched":{"type":["boolean","null"],"description":"`true` once a trigger condition has been met; `null` for non-trigger orders."}}},"LiquidationPriceEstimate":{"type":"object","description":"Aggregated liquidation prices across multiple account\n\nNote:\n- Exact for isolated-asset\n- Estimate for cross-asset (exact liquidation price depends on shape of market move)","required":["average_price","min_price","max_price"],"properties":{"average_price":{"type":"string","description":"Average price","example":"125.3"},"max_price":{"type":"string","description":"Max price","example":"134.2"},"min_price":{"type":"string","description":"Min price","example":"115.9"}}},"LogoutRequestBody":{"type":"object","description":"Request body for `POST /logout`.","required":["user_id"],"properties":{"user_id":{"type":"string","example":"0xeb886a56f9f0efa64432678cebf1270e9314a758e6eb697a606202a451e3e82e"}}},"MakerOrderAttachment":{"type":"object","description":"A maker order attached to a delegated settlement request.\nNot signed by the user; carries the MM's identity and designated settler.","required":["inner","signature"],"properties":{"inner":{"$ref":"#/components/schemas/InnerMakerOrderAttachment","description":"Maker-order action signed by the market maker."},"signature":{"type":"array","items":{"type":"integer","format":"int32","minimum":0},"description":"Signature bytes as a JSON array of integers. Sign the action-specific preimage for `inner`; see `/guides/signing`."}}},"MarginInfoResponse":{"type":"object","description":"Top-level margin info response containing per-account data.","required":["accounts"],"properties":{"accounts":{"type":"object","description":"Margin breakdown per sub-account, keyed by account index (as a string).","additionalProperties":{"$ref":"#/components/schemas/AccountMarginInfo"},"propertyNames":{"type":"string"}},"warnings":{"type":"array","items":{"$ref":"#/components/schemas/MarginWarning"},"description":"Warnings for sub-accounts whose margin computation failed.\nAbsent when all accounts computed successfully."}}},"MarginRejectionDetails":{"type":"object","description":"Margin-rejection breakdown (all amounts in USD).","required":["trading_capacity_usd","trading_shortfall_usd","im_surplus_before_pressure_usd","total_open_order_pressure_usd","pressure_by_asset","top_positions_by_impact"],"properties":{"im_surplus_before_pressure_usd":{"type":"string","description":"Initial-margin surplus of the sub-account before subtracting open-order pressure.","example":"40.00"},"pressure_by_asset":{"type":"array","items":{"$ref":"#/components/schemas/AssetPressure"},"description":"Largest open-order pressure contributors, descending (up to 3)."},"top_positions_by_impact":{"type":"array","items":{"$ref":"#/components/schemas/PositionImpact"},"description":"Largest positions by margin impact, with the prices used to value them (up to 3)."},"total_open_order_pressure_usd":{"type":"string","description":"Total open-order pressure on the order's side (sum of `pressure_by_asset`).","example":"52.50"},"trading_capacity_usd":{"type":"string","description":"Final trading capacity after open-order pressure and this order. Rejected when\nthis is not strictly above ~1 USD.","example":"-12.50"},"trading_shortfall_usd":{"type":"string","description":"Additional surplus needed to clear the trading threshold (`> 0` means short by this).","example":"13.50"}}},"MarginSimulateRequest":{"type":"object","description":"Request body for POST /margin/simulate.","required":["sub_account","trade"],"properties":{"pending_transfers":{"type":"array","items":{"$ref":"#/components/schemas/SimulatedTransferRequest"},"description":"Optional pending transfers to apply before the trade."},"sub_account":{"type":"integer","format":"int64","description":"Sub-account index to simulate against.","minimum":0},"trade":{"$ref":"#/components/schemas/SimulatedTradeRequest","description":"The trade to simulate."}}},"MarginSimulateResult":{"type":"object","description":"Result of margin simulation after applying the hypothetical trade to a cloned sub-account.","required":["account_equity","available_margin","mm_surplus","utilized_margin","margin_required","margin_impact","is_solvent","trade_accepted","positions"],"properties":{"account_equity":{"type":"string","description":"Post-trade account equity (sum of all assets at mark, no haircuts)."},"account_leverage":{"type":["string","null"],"description":"Post-trade account leverage (total_notional / equity). None if equity <= 0."},"available_margin":{"type":"string","description":"Post-trade margin available to open new positions (IM surplus)."},"is_solvent":{"type":"boolean","description":"Whether the account passes the IM solvency check after the simulated trade."},"liquidation_price":{"type":["string","null"],"description":"Estimated liquidation price for the simulated asset's resulting position."},"margin_impact":{"type":"string","description":"Change in available margin caused by the simulated trade (after - before).\nNegative means the trade reduces available margin."},"margin_required":{"type":"string","description":"Margin required for this specific trade (same IM pressure units as open-order risk)."},"mm_surplus":{"type":"string","description":"Post-trade distance from liquidation (MM surplus)."},"positions":{"type":"array","items":{"$ref":"#/components/schemas/PositionMarginInfo"},"description":"Per-position breakdown after the simulated trade."},"trade_accepted":{"type":"boolean","description":"Whether the same order would pass the OMS trading-capacity preflight check."},"utilized_margin":{"type":"string","description":"Post-trade total margin consumed by positions (adjusted liabilities with LF/IM)."}}},"MarginWarning":{"type":"object","description":"Warning emitted when a sub-account computation fails.","required":["sub_account","code","message"],"properties":{"code":{"type":"string","description":"Machine-readable code indicating the failure type."},"message":{"type":"string","description":"Human-readable error message."},"sub_account":{"type":"integer","format":"int64","description":"Sub-account index that failed.","minimum":0}}},"Market":{"type":"object","required":["asset_id","book_price_decimals","book_quantity_decimals"],"properties":{"asset_id":{"type":"string","example":"200","description":"Asset this market trades, as a stringified `AssetIdentifier`."},"book_price_decimals":{"type":"integer","format":"int32","description":"Number of decimals for market prices\nExample for `2` -> price can be 123.45 but not 123.456","example":"2"},"book_quantity_decimals":{"type":"integer","format":"int32","description":"Number of decimals for market quantities\nExample for `-3` -> a quantity of 1 is actually for 1000 tokens","example":"2"}}},"MarketDepthSnapshot":{"type":"object","required":["asks","bids","sequence_number"],"properties":{"asks":{"type":"array","items":{"type":"array","items":false,"prefixItems":[{"type":"string"},{"type":"string"}]},"description":"Ask price to quantity","example":"[[\"0.00500000\",\"0.00100000\"]]"},"bids":{"type":"array","items":{"type":"array","items":false,"prefixItems":[{"type":"string"},{"type":"string"}]},"description":"Bids price to quantity (highest bid first)","example":"[[\"0.00400000\",\"0.00100000\"], [\"0.00300000\",\"0.00200000\"]]"},"sequence_number":{"type":"integer","format":"int64","minimum":0,"description":"Monotonic sequence number for ordering depth updates and detecting gaps."}}},"MarketDetailsResponse":{"type":"object","required":["asset_id","book_price_decimals","book_quantity_decimals","isolated_only"],"properties":{"asset_id":{"type":"string","description":"Asset identifier, as a stringified `AssetIdentifier`: index form like `\"200\"`,\nor `\"<hex address>@<hex chain>\"` for isolated, chain-specific assets.","example":"200"},"book_price_decimals":{"type":"integer","format":"int32","description":"Number of decimals for market prices\nExample for `2` -> price can be 123.45 but not 123.456","example":"2"},"book_quantity_decimals":{"type":"integer","format":"int32","description":"Number of decimals for market quantities\nExample for `-3` -> a quantity of 1 is actually for 1000 tokens","example":"2"},"current_long_max_leverage":{"type":["string","null"],"description":"Current long-side max leverage from current long OI, the IM cliff curve,\nand the collateral factor.","example":"2.67"},"current_short_max_leverage":{"type":["string","null"],"description":"Current short-side max leverage from current short OI and the IM cliff\ncurve: `1 / (2 - lf/100 - im_factor)`.","example":"1.82"},"fee_schedule":{"oneOf":[{"type":"null"},{"$ref":"#/components/schemas/MarketFeeScheduleResponse","description":"Fee schedule from the active orderbook for this asset (when known)."}]},"isolated_only":{"type":"boolean","description":"Whether the asset is isolated-margin only.","example":false},"max_leverage":{"type":["string","null"],"description":"Static maximum leverage derived from CE risk params (additive IM haircut):\n`1 / (2 - max(cf, lf)/100 - initial_margin_factors[0]/1_000_000)`","example":"3.77"},"min_order_size":{"type":["string","null"],"description":"Minimum order size in base units, derived from quantity decimals.","example":"0.0001"},"tick_size":{"type":["string","null"],"description":"Minimum price increment, derived from price decimals.","example":"0.01"}}},"MarketFeeScheduleResponse":{"type":"object","description":"Fee ladder and fee account for a market, resolved from the active OB config.","required":["fee_account","global","per_asset"],"properties":{"fee_account":{"type":"string","description":"Fee recipient account from OB config (display / hex form)."},"global":{"type":"array","items":{"$ref":"#/components/schemas/FeeTierConfig"},"description":"OB-wide volume tiers (aggregate across pairs)."},"per_asset":{"type":"array","items":{"$ref":"#/components/schemas/FeeTierConfig"},"description":"Per-asset tier override for this market (empty if the OB does not define one)."}}},"MarketQuantity":{"oneOf":[{"type":"object","description":"Buy or Sell a given `quantity` of an asset\nFor Buys, `max_sellable_amount` is the maximal amount of QUOTE_ASSET (USD) paid to execute the order\n\n- quantity is in `book_quantity_decimals`\n- max_sellable_amount is in `book_price_decimals`","required":["BaseAsset"],"properties":{"BaseAsset":{"type":"object","description":"Buy or Sell a given `quantity` of an asset\nFor Buys, `max_sellable_amount` is the maximal amount of QUOTE_ASSET (USD) paid to execute the order\n\n- quantity is in `book_quantity_decimals`\n- max_sellable_amount is in `book_price_decimals`","required":["quantity"],"properties":{"max_sellable_amount":{"type":["integer","null"],"format":"int64","minimum":0},"quantity":{"type":"integer","format":"int64","minimum":0}}}}},{"type":"object","description":"Buy or Sell a given `amount` of an asset (which is quote quantity)\nFor Sells, `max_sellable_quantity` is the maximal amount of BASE_ASSET paid to execute the order\n\n- quantity is in `book_price_decimals`\n- max_sellable_quantity is in `book_quantity_decimals`","required":["QuoteAsset"],"properties":{"QuoteAsset":{"type":"object","description":"Buy or Sell a given `amount` of an asset (which is quote quantity)\nFor Sells, `max_sellable_quantity` is the maximal amount of BASE_ASSET paid to execute the order\n\n- quantity is in `book_price_decimals`\n- max_sellable_quantity is in `book_quantity_decimals`","required":["quantity"],"properties":{"max_sellable_quantity":{"type":["integer","null"],"format":"int64","minimum":0},"quantity":{"type":"integer","format":"int64","minimum":0}}}}}],"description":"Market order specific quantity"},"MarketResponse":{"type":"object","required":["asset_id","book_price_decimals","book_quantity_decimals","isolated_only"],"properties":{"asset_id":{"type":"string","description":"Asset identifier, as a stringified `AssetIdentifier`: index form like `\"200\"`,\nor `\"<hex address>@<hex chain>\"` for isolated, chain-specific assets.","example":"200"},"book_price_decimals":{"type":"integer","format":"int32","description":"Number of decimals for market prices\nExample for `2` -> price can be 123.45 but not 123.456","example":"2"},"book_quantity_decimals":{"type":"integer","format":"int32","description":"Number of decimals for market quantities\nExample for `-3` -> a quantity of 1 is actually for 1000 tokens","example":"2"},"current_long_max_leverage":{"type":["string","null"],"description":"Current long-side max leverage from current long OI, the IM cliff curve,\nand the collateral factor.","example":"2.67"},"current_short_max_leverage":{"type":["string","null"],"description":"Current short-side max leverage from current short OI and the IM cliff\ncurve: `1 / (2 - lf/100 - im_factor)`.","example":"1.82"},"fee_schedule":{"oneOf":[{"type":"null"},{"$ref":"#/components/schemas/MarketFeeScheduleResponse","description":"Fee schedule from the active orderbook for this asset (when known)."}]},"isolated_only":{"type":"boolean","description":"Whether the asset is isolated-margin only.","example":false},"max_leverage":{"type":["string","null"],"description":"Static maximum leverage derived from CE risk params (additive IM haircut):\n`1 / (2 - max(cf, lf)/100 - initial_margin_factors[0]/1_000_000)`","example":"3.77"},"min_order_size":{"type":["string","null"],"description":"Minimum order size in base units, derived from quantity decimals.","example":"0.0001"},"tick_size":{"type":["string","null"],"description":"Minimum price increment, derived from price decimals.","example":"0.01"}}},"MdsExportConsentRequest":{"type":"object","required":["enabled"],"properties":{"enabled":{"type":"boolean"}}},"MdsExportConsentResponse":{"type":"object","required":["user_enabled","system_enabled"],"properties":{"system_enabled":{"type":"boolean","description":"Operator-level switch (`allow_user_data_access`)."},"user_enabled":{"type":"boolean","description":"User-level opt-in state."}}},"MultisigConfig":{"type":"object","description":"Per-user multisig configuration.","required":["master_weight","signers","thresholds"],"properties":{"master_weight":{"type":"integer","format":"int32","minimum":0,"description":"Weight the account's master Ed25519 key contributes toward the action threshold when it signs."},"signers":{"type":"array","items":{"$ref":"#/components/schemas/SignerEntry"},"description":"Additional registered signers; each contributes its `weight` toward the threshold when it co-signs (expired signers and signers outside their signing window contribute nothing)."},"thresholds":{"$ref":"#/components/schemas/MultisigThresholds","description":"Required cumulative signer weight per action category — see `MultisigThresholds`."}}},"MultisigThresholds":{"type":"object","description":"Threshold required for each action category.","required":["low","medium","high"],"properties":{"high":{"type":"integer","format":"int32","minimum":0,"description":"Cumulative signer weight required for high-impact actions (withdrawals and their cancellation, multisig-config changes, adding signers). Must be > 0 and <= the total available signer weight; default 1."},"low":{"type":"integer","format":"int32","minimum":0,"description":"Cumulative signer weight required for low-impact actions (e.g. close-position). Must be > 0; default 1."},"medium":{"type":"integer","format":"int32","minimum":0,"description":"Cumulative signer weight required for medium-impact actions (settlements, sub-account transfers). Must be > 0; default 1."}}},"NonceResponse":{"type":"object","description":"Nonce response from `GET /nonce/{user_id}`.","required":["value","expiry_ns","ce_nonce"],"properties":{"ce_nonce":{"type":"integer","format":"int64","example":2,"minimum":0,"description":"Current clearing-engine config nonce for the user, used when building multisig actions."},"expiry_ns":{"type":"integer","format":"int64","example":1710000300000000000,"minimum":0,"description":"Nonce expiry (ns since the Unix epoch); valid about 5 minutes."},"value":{"type":"string","example":"a1b2c3d4e5f6...","description":"Random nonce to sign (Ed25519) and submit to `POST /auth`."}}},"ObResponse":{"oneOf":[{"type":"object","required":["CreateOrderResponse"],"properties":{"CreateOrderResponse":{"type":"object","required":["response","asset_id"],"properties":{"asset_id":{"$ref":"#/components/schemas/AssetIdentifier"},"initial_receive_timestamp_ns":{"type":["integer","null"],"format":"int64","minimum":0},"response":{"$ref":"#/components/schemas/CreateOrderResponse"}}}}},{"type":"object","required":["CancelOrderResponse"],"properties":{"CancelOrderResponse":{"type":"object","required":["response","asset_id"],"properties":{"asset_id":{"$ref":"#/components/schemas/AssetIdentifier"},"initial_receive_timestamp_ns":{"type":["integer","null"],"format":"int64","minimum":0},"response":{"$ref":"#/components/schemas/CancelOrderResponse"}}}}},{"type":"object","required":["ReplaceOrderResponse"],"properties":{"ReplaceOrderResponse":{"type":"object","required":["response","asset_id"],"properties":{"asset_id":{"$ref":"#/components/schemas/AssetIdentifier"},"initial_receive_timestamp_ns":{"type":["integer","null"],"format":"int64","minimum":0},"response":{"$ref":"#/components/schemas/ReplaceOrderResponse"}}}}}],"description":"Envelope for an orderbook response forwarded to the client: a create/cancel/replace order response together with the asset id and receive timestamp."},"OneTimeSignatureDoc":{"type":"object","required":["inner","expiry","chain_id"],"properties":{"chain_id":{"type":"string","description":"9-byte chain id, hex encoded."},"epoch_hash":{"type":["array","null"],"items":{"type":"integer","format":"int32","minimum":0},"description":"Withdrawal approval epoch hash as 32 raw bytes. `null` for settlement approvals."},"expiry":{"type":"integer","format":"int64","description":"Signature expiry, in unix seconds.","minimum":0},"inner":{"$ref":"#/components/schemas/InnerOneTimeSignatureDoc","description":"Approval signature and nonce."}}},"Order":{"type":"object","required":["user_order","signature","book_timestamp_ns","is_liquidation","is_auto_deleverage"],"properties":{"additional_signers":{"type":"array","items":{"$ref":"#/components/schemas/AdditionalSigner"},"description":"Extra multisig signers co-signing this order."},"automatic_overrides":{"oneOf":[{"type":"null"},{"$ref":"#/components/schemas/AutomaticReplaceOrder"}],"description":"Optional system-generated replace (e.g. trigger sizing); `null` if none."},"book_timestamp_ns":{"type":"integer","format":"int64","example":"1750146943779456128","minimum":0,"description":"Orderbook receive timestamp (ns since the Unix epoch)."},"is_auto_deleverage":{"type":"boolean","description":"System-initiated auto-deleverage (ADL) order.\nLike `is_liquidation`: no user signature;\nCE skip the signature check and the OMS bypass the active-orderbook filter."},"is_liquidation":{"type":"boolean","description":"`true` if this is a forced-liquidation order."},"limit_overrides":{"oneOf":[{"type":"null"},{"$ref":"#/components/schemas/ReplaceOrder"}],"description":"Optional client-signed replace applied at book time; `null` if none."},"limit_overrides_additional_signers":{"type":"array","items":{"$ref":"#/components/schemas/AdditionalSigner"},"description":"Registered co-signers of `limit_overrides`; each contributes its weight toward the replace threshold. Empty when the master key signed alone."},"limit_overrides_book_timestamp_ns":{"type":["integer","null"],"format":"int64","description":"Trusted orderbook timestamp for replacement signer-window validation.","minimum":0},"limit_overrides_signature":{"type":["array","null"],"items":{"type":"integer","format":"int32","minimum":0},"description":"Optional signature bytes as a JSON array of integers over `limit_overrides`; `null` if none. See `/guides/signing`."},"signature":{"type":"array","items":{"type":"integer","format":"int32","minimum":0},"description":"Signature bytes as a JSON array of integers over the order payload; see `/guides/signing`."},"trigger_touched":{"type":["boolean","null"],"description":"`true` if a trigger condition was already met; `null` for non-trigger orders."},"user_order":{"$ref":"#/components/schemas/UserOrder","description":"The signed user order (asset, side, type, quantity, time-in-force, etc.)."}}},"OrderCanceled":{"type":"object","required":["order_id","asset_id","user_id","timestamp_ns","operator_pubkey"],"properties":{"asset_id":{"type":"string","example":"200","description":"Asset of the canceled order (stringified `AssetIdentifier`)."},"initial_receive_timestamp_ns":{"type":["integer","null"],"format":"int64","minimum":0,"description":"When the originating request was first received (ns since the Unix epoch); `null` if unknown."},"operator_pubkey":{"type":"string","description":"Public key of the orderbook operator that produced the event."},"order_id":{"type":"string","example":"rt6G7V8gRAG4p7lfidkeUw==","description":"Id of the canceled order."},"timestamp_ns":{"type":"integer","format":"int64","example":"1750146943779456128","minimum":0,"description":"Cancellation timestamp (ns since the Unix epoch)."},"user_id":{"type":"string","description":"Owner's user id (hex-encoded public key)."}}},"OrderEvent":{"oneOf":[{"type":"object","required":["Created"],"properties":{"Created":{"$ref":"#/components/schemas/Order"}}},{"type":"object","required":["Replaced"],"properties":{"Replaced":{"$ref":"#/components/schemas/OrderUpdated"}}},{"type":"object","required":["Triggered"],"properties":{"Triggered":{"$ref":"#/components/schemas/OrderTriggered"}}},{"type":"object","required":["Canceled"],"properties":{"Canceled":{"$ref":"#/components/schemas/OrderCanceled"}}},{"type":"object","required":["Removed"],"properties":{"Removed":{"$ref":"#/components/schemas/OrderRemoved"}}},{"type":"object","required":["CreateFailed"],"properties":{"CreateFailed":{"type":"object","required":["order_id","user_id"],"properties":{"order_id":{"type":"string","example":"rt6G7V8gRAG4p7lfidkeUw=="},"user_id":{"type":"string","example":"0xeb886a56f9f0efa64432678cebf1270e9314a758e6eb697a606202a451e3e82e"}}}}},{"type":"object","required":["ReplaceFailed"],"properties":{"ReplaceFailed":{"type":"object","required":["order_id","user_id"],"properties":{"order_id":{"type":"string","example":"rt6G7V8gRAG4p7lfidkeUw=="},"user_id":{"type":"string","example":"0xeb886a56f9f0efa64432678cebf1270e9314a758e6eb697a606202a451e3e82e"}}}}},{"type":"object","required":["CancelFailed"],"properties":{"CancelFailed":{"type":"object","required":["order_id","user_id"],"properties":{"order_id":{"type":"string","example":"rt6G7V8gRAG4p7lfidkeUw=="},"user_id":{"type":"string","example":"0xeb886a56f9f0efa64432678cebf1270e9314a758e6eb697a606202a451e3e82e"}}}}}],"description":"Order-lifecycle event on the user orders WebSocket, tagged by `type`: an order was created, replaced, triggered, canceled, or removed, or a create/replace/cancel failed (the `*_failed` variants carry the `order_id` and `user_id`)."},"OrderFailureReason":{"type":"string","description":"Structured reason for order creation, replacement, or cancellation failure, returned to the user via the API.","enum":["InvalidJson","OrderIdTooLong","OrderIdAlreadyExists","InvalidAuthToken","OrderNotFound","InsufficientMargin","InsufficientInventory","InventoryNotFound","ReduceOnlyWouldIncrease","Timeout","AlreadyCancelled","InvalidBookConfiguration","InvalidOrder","InvalidParentOrder","AttachedBracketQuantityExceedsRemaining","WouldCross","Expired","CouldNotFill","CantReplaceMarketOrder","WrongAuthorization","InvalidTriggerCondition","MissingBookDecimals","RequestTooOld","InternalError","Unknown","SyncBookNotFound","InvalidSettlementRequest","ObNotReady"]},"OrderNotFoundResponse":{"type":"object","required":["error"],"properties":{"error":{"type":"string"}}},"OrderRejectionDetails":{"type":"object","description":"Why an order was rejected, with the matching breakdown.\n\nExactly one of `margin` / `inventory` is set, per `reason`.","required":["reason"],"properties":{"inventory":{"oneOf":[{"type":"null"},{"$ref":"#/components/schemas/InventoryRejectionDetails","description":"Spot-inventory breakdown; set when `reason` is `insufficient_inventory`."}]},"margin":{"oneOf":[{"type":"null"},{"$ref":"#/components/schemas/MarginRejectionDetails","description":"Margin breakdown; set when `reason` is `insufficient_margin`."}]},"reason":{"$ref":"#/components/schemas/OrderRejectionKind","description":"Which kind of rejection this is; selects the populated breakdown below."}}},"OrderRejectionKind":{"type":"string","description":"Discriminator for [`OrderRejectionDetails`].","enum":["insufficient_margin","insufficient_inventory"]},"OrderRemovalReason":{"type":"string","enum":["Completed","Canceled","Expired","Rejected"],"description":"Why an order was removed from the book: `Completed` (fully filled), `Canceled`, `Expired` (GTD), or `Rejected` by the clearing engine."},"OrderRemoved":{"type":"object","required":["order_id","asset_id","user_id","timestamp_ns","operator_pubkey","reason","filled_quantity","filled_amount","confirmed_quantity","confirmed_amount","book_quantity_decimals"],"properties":{"asset_id":{"type":"string","example":"200","description":"Asset of the removed order (stringified `AssetIdentifier`)."},"book_quantity_decimals":{"type":"integer","format":"int32","description":"Quantity decimals for this market."},"confirmed_amount":{"type":"integer","description":"Confirmed amount in quantity+price decimals","minimum":0},"confirmed_quantity":{"type":"integer","format":"int64","minimum":0,"description":"Confirmed filled quantity at removal, in book quantity units."},"filled_amount":{"type":"integer","description":"Filled amount in quantity+price decimals","minimum":0},"filled_quantity":{"type":"integer","format":"int64","minimum":0,"description":"Total filled quantity at removal, in book quantity units."},"operator_pubkey":{"type":"string","description":"Public key of the orderbook operator that produced the event."},"order_id":{"type":"string","example":"rt6G7V8gRAG4p7lfidkeUw==","description":"Id of the removed order."},"reason":{"$ref":"#/components/schemas/OrderRemovalReason","description":"Why the order was removed."},"timestamp_ns":{"type":"integer","format":"int64","example":"1750146943779456128","minimum":0,"description":"Removal timestamp (ns since the Unix epoch)."},"user_id":{"type":"string","description":"Owner's user id (hex-encoded public key)."}}},"OrderSide":{"type":"string","enum":["Buy","Sell"],"description":"Order side: `Buy` (bid) or `Sell` (ask)."},"OrderTrigger":{"type":"object","required":["condition"],"properties":{"condition":{"$ref":"#/components/schemas/PriceTrigger","description":"Conditional matching semantics for this order"},"parent_order_id":{"type":["string","null"],"description":"This order will only be enabled when the parent order id is executed","example":"rt6G7V8gRAG4p7lfidkeUw=="}}},"OrderTriggered":{"type":"object","description":"Event triggered when a conditional order is triggered or it's enabled quantity is updated.","required":["order_id","asset_id","user_id","timestamp_ns","quantity","trigger_touched","operator_pubkey"],"properties":{"asset_id":{"type":"string","example":"200","description":"Asset of the triggered order (stringified `AssetIdentifier`)."},"operator_pubkey":{"type":"string","description":"Public key of the orderbook operator that produced the event."},"order_id":{"type":"string","example":"rt6G7V8gRAG4p7lfidkeUw==","description":"Id of the triggered order."},"quantity":{"type":"integer","format":"int64","description":"Quantity enabled - this may differ from original order quantity, if the parent order was partially filled.","minimum":0},"timestamp_ns":{"type":"integer","format":"int64","example":"1750146943779456128","minimum":0,"description":"Trigger timestamp (ns since the Unix epoch)."},"trigger_touched":{"type":"boolean","description":"`true` once the trigger condition was met."},"user_id":{"type":"string","description":"Owner's user id (hex-encoded public key)."}}},"OrderType":{"oneOf":[{"type":"object","required":["Limit"],"properties":{"Limit":{"type":"object","required":["limit_price","quantity"],"properties":{"limit_price":{"type":"integer","format":"int64","description":"Price in book price units (example given would be $105,700.25)","example":"10570025","minimum":0},"quantity":{"type":"integer","format":"int64","description":"Quantity in book quantity units (example given would be 0.1000)","example":"1000","minimum":0},"time_in_force":{"$ref":"#/components/schemas/TimeInForce"}}}}},{"type":"object","required":["Market"],"properties":{"Market":{"type":"object","required":["quantity"],"properties":{"fill_or_kill":{"type":"boolean"},"quantity":{"$ref":"#/components/schemas/MarketQuantity"}}}}},{"type":"object","description":"A market maker's sync quote","required":["SyncMaker"],"properties":{"SyncMaker":{"type":"object","description":"A market maker's sync quote","required":["limit_price","quantity","executor_address"],"properties":{"executor_address":{"type":"string","description":"Executor contract address for atomic settlement."},"limit_price":{"type":"integer","format":"int64","description":"Price in book price units (example given would be $105,700.25)","example":"10570025","minimum":0},"quantity":{"type":"integer","format":"int64","description":"Quantity in book quantity units (example given would be 0.1000)","example":"1000","minimum":0},"time_in_force":{"$ref":"#/components/schemas/SyncTimeInForce"}}}}},{"type":"object","description":"A user's sync take carries a signed delegated settlement request","required":["SyncTaker"],"properties":{"SyncTaker":{"type":"object","description":"A user's sync take carries a signed delegated settlement request","required":["limit_price","quantity","executor_address","settlement_request"],"properties":{"executor_address":{"type":"string","description":"Executor contract address for atomic settlement."},"limit_price":{"type":"integer","format":"int64","description":"Price in book price units (example given would be $105,700.25)","example":"10570025","minimum":0},"quantity":{"type":"integer","format":"int64","description":"Quantity in book quantity units (example given would be 0.1000)","example":"1000","minimum":0},"settlement_request":{"$ref":"#/components/schemas/TxSettlementRequest","description":"User-signed delegated settlement request."},"time_in_force":{"$ref":"#/components/schemas/SyncTimeInForce"}}}}}],"description":"Order type and its parameters: `Limit`, `Market`, `SyncMaker`, or `SyncTaker` - each variant carries the relevant price, quantity, and time-in-force fields."},"OrderUpdated":{"type":"object","required":["order_id","asset_id","user_id","new_quantity","new_price","timestamp_ns","operator_pubkey"],"properties":{"asset_id":{"type":"string","example":"200","description":"Asset of the updated order (stringified `AssetIdentifier`)."},"initial_receive_timestamp_ns":{"type":["integer","null"],"format":"int64","minimum":0,"description":"When the originating request was first received (ns since the Unix epoch); `null` if unknown."},"new_price":{"type":"integer","format":"int64","example":"10542025","minimum":0,"description":"New limit price after the update, in book price units."},"new_quantity":{"type":"integer","format":"int64","example":"1000","minimum":0,"description":"New order quantity after the update, in book quantity units."},"operator_pubkey":{"type":"string","description":"Public key of the orderbook operator that produced the event."},"order_id":{"type":"string","example":"rt6G7V8gRAG4p7lfidkeUw==","description":"Id of the updated order."},"timestamp_ns":{"type":"integer","format":"int64","example":"1750146943779456128","minimum":0,"description":"Update timestamp (ns since the Unix epoch)."},"user_id":{"type":"string","description":"Owner's user id (hex-encoded public key)."}}},"PendingSyncSettlement":{"type":"object","required":["asset_id","mm_pubkey","taker_user","executor_address","settlement_request","maker_fills","taker_fill_quantity","timestamp_ns"],"properties":{"asset_id":{"$ref":"#/components/schemas/AssetIdentifier","description":"Asset whose sync book produced this settlement."},"executor_address":{"type":"string","description":"Onchain address authorized to execute the settlement transaction.","example":"0x0000000000000000000000000000000000000000"},"maker_fills":{"type":"array","items":{"type":"object"},"description":"Maker-side fills included in this pending sync settlement."},"mm_pubkey":{"$ref":"#/components/schemas/UserPublicKey","description":"Public key of the market maker (sync-book owner) party to the settlement."},"settlement_request":{"$ref":"#/components/schemas/TxSettlementRequest","description":"Signed settlement request to be submitted onchain."},"taker_fill_quantity":{"type":"integer","format":"int64","description":"Total taker fill quantity, in the market's book quantity decimals.","minimum":0},"taker_user":{"$ref":"#/components/schemas/UserPublicKey","description":"Public key of the taker user being settled."},"timestamp_ns":{"type":"integer","format":"int64","description":"Event timestamp, nanoseconds since the Unix epoch.","minimum":0}}},"PositionCleared":{"type":"object","description":"Emitted when a margin position is cleared via the close-position flow\n— realized PnL is moved to/from the spot balance and the position is\nremoved from the sub-account. Distinct from \"closing a position\" by\ntrading the base back to zero exposure.","required":["user","sub_account_index","asset","timestamp_ns"],"properties":{"asset":{"type":"string"},"sub_account_index":{"$ref":"#/components/schemas/AccountIndex"},"timestamp_ns":{"type":"integer","format":"int64","minimum":0},"user":{"$ref":"#/components/schemas/UserPublicKey"}}},"PositionImpact":{"type":"object","description":"One position's contribution to the margin surplus, with the prices used.","required":["asset","margin_impact_usd","collateral_price","liability_price"],"properties":{"asset":{"$ref":"#/components/schemas/AssetIdentifier","description":"The asset held in this position."},"collateral_price":{"type":"string","description":"Raw spot price of the collateral leg (before the IM factor and CF/LF haircuts,\nwhich are applied to the value, not the price).","example":"2750.40"},"im_factor":{"type":["string","null"],"description":"The IM factor the solvency check applied to this asset's value (additive haircut;\n1 = no haircut). `null` when it could not be resolved.","example":"0.98"},"liability_price":{"type":"string","description":"Raw spot price of the liability leg (before the IM factor and CF/LF haircuts).","example":"2750.40"},"margin_impact_usd":{"type":"string","description":"Signed margin surplus contribution of this position, in USD (positive = collateral,\nnegative = liability).","example":"-30.00"}}},"PositionMarginInfo":{"type":"object","description":"Per-position details.","required":["asset_id","side","size","notional_value","margin"],"properties":{"asset_id":{"type":"string","description":"Asset identifier of the position (stringified `AssetIdentifier`)."},"margin":{"type":"string","description":"Per-position margin display estimate for this response.\nLiquidation does not use per-position margin values; it uses whole-account\n`maintenance_margin_surplus` from the core margin engine.","example":"50000.0"},"notional_value":{"type":"string","description":"size * mark_price.","example":"250000.0"},"side":{"$ref":"#/components/schemas/PositionSide","description":"Whether the position is long or short."},"size":{"type":"string","description":"Position size as a decimal (converted from inventory decimals).","example":"100.0"}}},"PositionResponse":{"type":"object","required":["asset_id","sub_account_index","name","side","size","base_credits","base_liabilities","quote_credits","quote_liabilities"],"properties":{"asset_id":{"type":"string","description":"Asset identifier for this position","example":"200"},"base_credits":{"type":"string","description":"Raw base asset credits in human-readable decimal","example":"1.5"},"base_liabilities":{"type":"string","description":"Raw base asset liabilities in human-readable decimal","example":"0.0"},"entry_price":{"type":["string","null"],"description":"VWAP entry price from confirmed trade fills, or balance-ratio\napproximation if no trade history is available (e.g. after OMS restart).","example":"105000.00"},"leverage":{"type":["string","null"],"description":"Position leverage = notional / margin. None when margin is unavailable.","example":"4.0"},"liquidation_price":{"type":["string","null"],"description":"Estimated price at which this position would trigger liquidation.\nOMS-side estimate (includes cross-margin credit-line effects); an\napproximation, not the CE's authoritative solvency calculation.","example":"98000.00"},"margin":{"type":["string","null"],"description":"Estimated position margin consumption using current risk params.","example":"3750.00"},"mark_price":{"type":["string","null"],"description":"Current mark price from the price manager","example":"105350.00"},"name":{"type":"string","description":"Human-readable sub-account name","example":"Margin"},"quote_credits":{"type":"string","description":"Raw quote credits in human-readable decimal","example":"0.0"},"quote_liabilities":{"type":"string","description":"Raw quote liabilities in human-readable decimal","example":"157500.0"},"side":{"$ref":"#/components/schemas/PositionSide","description":"Position side: \"long\" or \"short\""},"size":{"type":"string","description":"Absolute position size in human-readable decimal","example":"1.5"},"sub_account_index":{"type":"integer","format":"int64","description":"Sub-account index (0 = main/spot, 1 = margin, 2+ = isolated)","example":1,"minimum":0},"unrealized_pnl":{"type":["string","null"],"description":"Unrealized PnL based on mark price and entry price.","example":"525.00"}}},"PositionSide":{"type":"string","description":"Position side: long or short","enum":["long","short","closed"]},"PositionUpdate":{"type":"object","description":"Event broadcast when a user's positions change (triggered by CE inventory updates).","required":["user_id","positions","timestamp_ns"],"properties":{"positions":{"type":"array","items":{"$ref":"#/components/schemas/PositionResponse"},"description":"Current positions for the user"},"timestamp_ns":{"type":"integer","format":"int64","description":"Server timestamp in nanoseconds","example":1750146943779456128,"minimum":0},"user_id":{"type":"string","description":"User public key, 64 lowercase hex chars (no 0x prefix)","example":"eb886a56f9f0efa64432678cebf1270e9314a758e6eb697a606202a451e3e82e"}}},"PriceTrigger":{"oneOf":[{"type":"object","required":["PriceAbove"],"properties":{"PriceAbove":{"type":"object","required":["price"],"properties":{"price":{"type":"integer","format":"int64","description":"Price in book price units (example given would be 105_700.25$)","example":"10570025","minimum":0}}}}},{"type":"object","required":["PriceBelow"],"properties":{"PriceBelow":{"type":"object","required":["price"],"properties":{"price":{"type":"integer","format":"int64","description":"Price in book price units (example given would be 105_700.25$)","example":"10570025","minimum":0}}}}}],"description":"Conditional trigger for a stop / take-profit order: `PriceAbove` or `PriceBelow` a given book price."},"QueuedWithdrawalDoc":{"type":"object","required":["user","asset","amount","nonce","target","status"],"properties":{"amount":{"type":"string","description":"Queued withdrawal amount, encoded as a decimal string."},"asset":{"type":"string","description":"Asset address as `\"<32-byte hex address>@<9-byte hex chain>\"`."},"nonce":{"type":"integer","format":"int64","description":"Withdrawal nonce.","minimum":0},"status":{"$ref":"#/components/schemas/WithdrawalQueueStatusDoc","description":"Queue status: delayed, filling, or approved."},"target":{"type":"string","description":"32-byte target address, hex encoded."},"user":{"type":"string","description":"User public key."}}},"RenameSubAccountRequest":{"type":"object","required":["name"],"properties":{"name":{"type":"string"}}},"RenameSubAccountResponse":{"type":"object","required":["account_index","old_name","new_name"],"properties":{"account_index":{"type":"integer","format":"int64","description":"Index of the sub-account that was renamed.","minimum":0},"new_name":{"type":"string","description":"Sub-account name now in effect."},"old_name":{"type":"string","description":"Sub-account name before the rename."}}},"ReplaceOrder":{"type":"object","description":"A signed replacement carrying the **complete effective mutable terms** the order will have\nonce installed.","required":["order_id","base_asset","timestamp_ns","new_price_limit","new_quantity","book_quantity_decimals","book_price_decimals","protocol_version"],"properties":{"base_asset":{"type":"string","example":"200","description":"Market asset that contains the order."},"book_price_decimals":{"type":"integer","format":"int32","description":"Decimals `new_price_limit` is expressed in.","example":"2"},"book_quantity_decimals":{"type":"integer","format":"int32","description":"Decimals `new_quantity` is expressed in.","example":"2"},"new_price_limit":{"type":"integer","format":"int64","description":"Effective limit price after this replacement.","example":"10542025","minimum":0},"new_quantity":{"type":"integer","format":"int64","description":"Effective **lifetime-total** quantity after this replacement.","example":"1000","minimum":0},"new_trigger":{"oneOf":[{"type":"null"},{"$ref":"#/components/schemas/PriceTrigger","description":"Effective trigger state after this replacement. `None` means the order has no trigger —\nit does NOT mean \"unchanged\". Only settable on an order that is already a\n(not-yet-triggered) trigger order."}]},"order_id":{"type":"string","example":"rt6G7V8gRAG4p7lfidkeUw==","description":"Id of the order to replace."},"protocol_version":{"$ref":"#/components/schemas/TplusProtocolVersion","description":"Protocol version the replace request is signed against."},"timestamp_ns":{"type":"integer","format":"int64","description":"Client-set timestamp (nanoseconds since Unix epoch). Used for ordering concurrent replaces — a replace with an older timestamp is rejected if a newer one was already applied.","example":"1750146943779456128","minimum":0}}},"ReplaceOrderRequest":{"type":"object","required":["signer","request","signature","post_sign_timestamp"],"properties":{"additional_signers":{"type":"array","items":{"$ref":"#/components/schemas/AdditionalSigner"}},"post_sign_timestamp":{"type":"integer","format":"int64","description":"Client-set timestamp (ns since the Unix epoch) of when the replace was signed.","minimum":0},"receive_timestamp_ns":{"type":["integer","null"],"format":"int64","description":"Server-set OMS receive timestamp; clients should omit this field.","minimum":0},"request":{"$ref":"#/components/schemas/ReplaceOrder","description":"Replace payload whose compact signable encoding is signed."},"signature":{"type":"array","items":{"type":"integer","format":"int32","minimum":0},"description":"Signature bytes as a JSON array of integers. Sign the action-specific preimage for `request`; see `/guides/signing`."},"signer":{"$ref":"#/components/schemas/UserPublicKey","description":"User public key that signs `request` and owns the order."}}},"ReplaceOrderResponse":{"type":"object","required":["order_id","status"],"properties":{"details":{"oneOf":[{"type":"null"},{"$ref":"#/components/schemas/OrderRejectionDetails","description":"Structured solvency detail for a rejection, when available."}]},"order_id":{"type":"string","example":"rt6G7V8gRAG4p7lfidkeUw==","description":"Order id of the replaced order, echoed back for correlation."},"reason":{"oneOf":[{"type":"null"},{"$ref":"#/components/schemas/OrderFailureReason"}],"description":"Why the replacement was refused. Present only when `status` is `Rejected`."},"span_id":{"type":["string","null"],"description":"Span id of the request that produced this response, for correlating with service logs."},"status":{"$ref":"#/components/schemas/ReplaceOrderStatus","description":"`Received` if the orderbook accepted the replacement, `Rejected` if it was refused (see `reason`)."},"trace_id":{"type":["string","null"],"description":"Trace id of the request that produced this response, for correlating with service logs."}}},"ReplaceOrderStatus":{"type":"string","enum":["Received","Rejected"],"description":"Whether the orderbook accepted the replacement (`Received`) or refused it (`Rejected`)."},"RollbackReason":{"type":"string","description":"Reason for rolling back an order when the clearing engine marks it at fault.\nOnly exposed to the user whose order was at fault (counterparty does not receive this).","enum":["CounterpartyAtFault","Insolvent","PricingIssue","BreachTotalOiCap","BreachSpotMarginOiCap","BreachCollateralCap","InvalidInventory","WrongAccountType","WrongSignature","InvalidOrderConditions","Overfilled","InvalidRelativeTime","NotReduceOnly","InvalidLiquidation","ProcessingError"]},"SettlementInitResponse":{"type":"object","required":["success"],"properties":{"approval":{"description":"Signed settlement approval payload returned on success (opaque JSON; forwarded onchain)."},"details":{"type":["string","null"],"description":"Reason the request was rejected; present only when `success` is `false`."},"success":{"type":"boolean","description":"`true` if the settlement request was accepted by the clearing engine."}}},"SettlementMode":{"type":"string","description":"Settlement domain: spot vault balance settlement or margin sub-account settlement.","enum":["margin","spot"]},"SignerEntry":{"type":"object","description":"Signer entry with expiration constraints.","required":["key","weight","created_at_ns"],"properties":{"created_at_ns":{"type":"integer","format":"int64","minimum":0,"description":"When the signer was added (ns since the Unix epoch)."},"expires_at_ns":{"type":["integer","null"],"format":"int64","minimum":0,"description":"Optional signer expiry (ns since the Unix epoch)."},"key":{"$ref":"#/components/schemas/SignerKey","description":"Signer public key (Ed25519, secp256k1, or P-256 variant)."},"max_signing_window_ns":{"type":["integer","null"],"format":"int64","minimum":0,"description":"Optional max age of payload timestamps this signer may sign."},"weight":{"type":"integer","format":"int32","minimum":0,"description":"Signing weight contributed toward multisig thresholds."}}},"SignerKey":{"oneOf":[{"type":"object","required":["Ed25519"],"properties":{"Ed25519":{"type":"array","items":{"type":"integer","format":"int32","minimum":0}}}},{"type":"object","description":"secp256k1 pubkey (e.g EVM wallets).","required":["Secp256k1"],"properties":{"Secp256k1":{"type":"array","items":{"type":"integer","format":"int32","minimum":0},"description":"secp256k1 pubkey (e.g EVM wallets)."}}},{"type":"object","description":"P-256/secp256r1 pubkey (raw payload).","required":["P256"],"properties":{"P256":{"type":"array","items":{"type":"integer","format":"int32","minimum":0},"description":"P-256/secp256r1 pubkey (raw payload)."}}},{"type":"object","description":"P-256/secp256r1 pubkey with WebAuthn ceremony.","required":["WebAuthn"],"properties":{"WebAuthn":{"type":"array","items":{"type":"integer","format":"int32","minimum":0},"description":"P-256/secp256r1 pubkey with WebAuthn ceremony."}}}],"description":"Signer identity with multicurve support."},"SimulatedTradeRequest":{"type":"object","description":"A single trade to simulate in the margin calculation.","required":["asset","is_buy","size","limit_price"],"properties":{"asset":{"$ref":"#/components/schemas/AssetIdentifier","description":"Asset to trade (e.g. \"1\" for Index(1))."},"is_buy":{"type":"boolean","description":"Whether this is a buy (true) or sell (false)."},"limit_price":{"type":"string","description":"Limit price as a decimal string (e.g. \"100.0\").","example":"100.0"},"size":{"type":"string","description":"Trade size as a decimal string (e.g. \"50.0\").","example":"50.0"},"trade_type":{"$ref":"#/components/schemas/TradeType","description":"Trade type: \"margin\" (default) or \"spot\"."}}},"SimulatedTransferRequest":{"type":"object","description":"A pending deposit to apply before the simulated trade.\nOnly positive amounts are supported (deposits into the account).","required":["asset","amount"],"properties":{"amount":{"type":"string","description":"Deposit amount as a decimal string (must be positive, e.g. \"5000.0\").","example":"5000.0"},"asset":{"$ref":"#/components/schemas/AssetIdentifier","description":"Asset to deposit (e.g. \"0\" for USD)."}}},"SolvencyResponse":{"type":"object","required":["accounts"],"properties":{"accounts":{"type":"object","description":"Solvency result per sub-account, keyed by account index (as a string).","additionalProperties":{"$ref":"#/components/schemas/AccountSolvencyResult"},"propertyNames":{"type":"string"}}}},"SubAccountAssetTransferred":{"type":"object","description":"Emitted when a user transfers an asset between two of their own\nsub-accounts. `amount` is in `INVENTORY_DECIMALS` (1e18).","required":["user","source_sub_account_index","target_sub_account_index","asset","amount","timestamp_ns"],"properties":{"amount":{"type":"string","description":"Transfer amount in `INVENTORY_DECIMALS` (1e18)."},"asset":{"type":"string"},"source_sub_account_index":{"$ref":"#/components/schemas/AccountIndex"},"target_sub_account_index":{"$ref":"#/components/schemas/AccountIndex"},"timestamp_ns":{"type":"integer","format":"int64","minimum":0},"user":{"$ref":"#/components/schemas/UserPublicKey"}}},"SubAccountJson":{"type":"object","required":["name","kind","spot","margins"],"properties":{"kind":{"type":"string","description":"Sub-account kind: `\"spot\"`, `\"cross_margin\"`, or `\"isolated:<asset_id>\"`."},"margins":{"type":"object","description":"Margin balances: asset_id -> combined balance (base + quote)","additionalProperties":{"$ref":"#/components/schemas/CombinedBalanceJson"},"propertyNames":{"type":"string"}},"name":{"type":"string","description":"Human-readable sub-account name"},"spot":{"type":"object","description":"Spot balances: asset_id -> balance (`null` when the raw inventory\namount cannot be represented as a decimal)","additionalProperties":{"type":["string","null"]},"propertyNames":{"type":"string"}}}},"SubAccountTransferRequest":{"type":"object","description":"Signed sub-account transfer request.","required":["inner","signature"],"properties":{"additional_signers":{"type":"array","items":{"$ref":"#/components/schemas/AdditionalSigner"},"description":"Optional multisig co-signatures over the same transfer payload."},"inner":{"$ref":"#/components/schemas/InnerSubAccountTransferRequest","description":"Sub-account transfer payload being signed and submitted."},"signature":{"type":"array","items":{"type":"integer","format":"int32","minimum":0},"description":"Signature bytes as a JSON array of integers. Sign the action-specific preimage for `inner`; see `/guides/signing`."}}},"SubAccountType":{"oneOf":[{"type":"object","required":["Isolated"],"properties":{"Isolated":{"type":"object","required":["asset"],"properties":{"asset":{"$ref":"#/components/schemas/AssetIdentifier"}}}}},{"type":"string","enum":["CrossMargin"]},{"type":"string","enum":["Spot"]}]},"SubAccountUpdate":{"type":"object","description":"Update for a single sub-account - contains current values of changed balances.","required":["name","kind"],"properties":{"kind":{"type":"string","description":"Sub-account type (spot, cross_margin, isolated:{asset})"},"marginInfo":{"oneOf":[{"type":"null"},{"$ref":"#/components/schemas/AccountMarginInfo","description":"Computed margin metrics (equity, available margin, leverage, etc.).\nPopulated by the position aggregator on a 1s interval; None on\nfast balance-only updates from the CE message handler."}]},"margins":{"type":["object","null"],"description":"Only margin balances that changed (None = no margin changes)","additionalProperties":{"$ref":"#/components/schemas/CombinedBalanceJson"},"propertyNames":{"type":"string"}},"name":{"type":"string","description":"Human-readable sub-account name"},"spot":{"type":["object","null"],"description":"Only spot balances that changed (None = no spot changes)","additionalProperties":{"type":"string"},"propertyNames":{"type":"string"}}}},"SyncTimeInForce":{"oneOf":[{"type":"string","enum":["GTC"]},{"type":"object","required":["GTD"],"properties":{"GTD":{"type":"object","required":["timestamp_ns"],"properties":{"timestamp_ns":{"type":"integer","format":"int64","minimum":0}}}}}],"description":"Time-in-force for sync-book maker/taker orders: `GTC` (good-til-cancel) or `GTD` (good-til-date)."},"TimeInForce":{"oneOf":[{"type":"object","required":["GTC"],"properties":{"GTC":{"type":"object","properties":{"post_only":{"type":"boolean"}}}}},{"type":"object","required":["GTD"],"properties":{"GTD":{"type":"object","required":["timestamp_ns"],"properties":{"post_only":{"type":"boolean"},"timestamp_ns":{"type":"integer","format":"int64","minimum":0}}}}},{"type":"object","required":["IOC"],"properties":{"IOC":{"type":"object","properties":{"fill_or_kill":{"type":"boolean"}}}}}],"description":"Time-in-force for an order: `GTC` (good-til-cancel, optionally post-only), `GTD` (good-til-date), or `IOC` (immediate-or-cancel)."},"TplusProtocolVersion":{"type":"integer","description":"Protocol version: 0 = Unknown, 1 = InitialVersion","enum":[0,1],"examples":[1]},"TradeStatus":{"type":"string","enum":["Pending","Confirmed","Rollbacked"],"description":"Settlement status of a trade: `Pending`, `Confirmed`, or `Rollbacked`."},"TradeTarget":{"type":"object","required":["account","is_spot"],"properties":{"account":{"$ref":"#/components/schemas/AccountIndex","description":"Offset into trader's subaccounts to use for this order\nIf `None`, then this order is for the user's (sole) spot account.\nOtherwise, use margin account 0, 1, ..."},"is_spot":{"type":"boolean","description":"Identifies whether to spend on the spot balance or margin balances."}}},"TradeType":{"type":"string","description":"Margin vs spot for [`simulate_margin`] and the REST `/margin/simulate` request.","enum":["margin","spot"]},"TransferSubAccountResponse":{"type":"object","required":["success"],"properties":{"failure_reason":{"type":["string","null"],"description":"Reason the transfer was rejected; present only when `success` is `false`."},"success":{"type":"boolean","description":"`true` if the transfer was accepted by the clearing engine."}}},"TxSettlementRequest":{"type":"object","description":"Signed settlement request sent to OMS settlement-init and sync-taker flows.","required":["inner","signature"],"properties":{"additional_signers":{"type":"array","items":{"$ref":"#/components/schemas/AdditionalSigner"},"description":"Optional multisig co-signatures over the same settlement payload."},"inner":{"$ref":"#/components/schemas/InnerSettlementRequest","description":"Settlement payload being signed and submitted."},"maker_order":{"oneOf":[{"type":"null"},{"$ref":"#/components/schemas/MakerOrderAttachment","description":"Optional signed maker-order attachment for delegated sync settlement."}],"description":"Optional signed maker-order attachment for delegated sync settlement."},"nonce":{"oneOf":[{"type":"null"},{"$ref":"#/components/schemas/u64","description":"Deprecated compatibility field. When present, it must match\n`inner.nonce`; the signed nonce in `inner` is authoritative."}],"description":"Deprecated compatibility field. When present, it must match `inner.nonce`; the signed nonce in `inner` is authoritative."},"signature":{"type":"array","items":{"type":"integer","format":"int32","minimum":0},"description":"Signature bytes as a JSON array of integers. Sign the action-specific preimage for `inner`; see `/guides/signing`."}}},"UnsignedCloseOrder":{"type":"object","description":"A suggested order that would close (part of) a position, returned by the\nclose-all preview. Unsigned: the client signs and submits it as a normal order.","required":["asset_id","side","quantity","book_price_decimals","book_quantity_decimals","sub_account_index","reduce_only"],"properties":{"asset_id":{"type":"string","description":"Asset whose position this order would close (stringified `AssetIdentifier`)."},"book_price_decimals":{"type":"integer","format":"int32","description":"Price decimals for this market (echo back when submitting the order)."},"book_quantity_decimals":{"type":"integer","format":"int32","description":"Quantity decimals for this market (echo back when submitting the order)."},"oracle_price":{"type":["string","null"],"description":"Oracle mark price for the asset at preview time; `null` if unavailable."},"quantity":{"type":"integer","format":"int64","description":"Order quantity, in the market's book quantity decimals.","minimum":0},"reduce_only":{"type":"boolean","description":"Always `true`: a close order only reduces the position, never increases it."},"side":{"$ref":"#/components/schemas/OrderSide","description":"Side of the closing order - opposite the position (`Sell` closes a long, `Buy` closes a short)."},"sub_account_index":{"type":"integer","format":"int64","description":"Sub-account holding the position to close.","minimum":0},"suggested_max_sellable_amount":{"type":["integer","null"],"format":"int64","description":"Suggested `max_sellable_amount` to attach to the order when relevant; `null` otherwise.","minimum":0}}},"UpdateMultisigConfigActionResponse":{"type":"object","required":["success","result"],"properties":{"result":{"$ref":"#/components/schemas/UpdateMultisigConfigActionResult","description":"Detailed outcome: success, or failure with an error message."},"success":{"type":"boolean","description":"`true` if the update succeeded (mirrors `result`)."}}},"UpdateMultisigConfigActionResult":{"oneOf":[{"type":"string","description":"The multisig configuration update was applied.","enum":["succeeded"]},{"type":"object","description":"The update was rejected; `error` carries the reason.","required":["failed"],"properties":{"failed":{"type":"object","description":"The update was rejected; `error` carries the reason.","required":["error"],"properties":{"error":{"type":"string"}}}}}],"description":"Outcome of a multisig-config update applied by the clearing engine."},"UpdateMultisigConfigRequest":{"type":"object","description":"Signed multisig configuration replacement request.","required":["inner","signature"],"properties":{"additional_signers":{"type":"array","items":{"$ref":"#/components/schemas/AdditionalSigner"},"description":"Optional multisig co-signatures over the same config payload."},"inner":{"$ref":"#/components/schemas/InnerUpdateMultisigConfigRequest","description":"Multisig config replacement payload being signed and submitted."},"signature":{"type":"array","items":{"type":"integer","format":"int32","minimum":0},"description":"Signature bytes as a JSON array of integers. Sign the action-specific preimage for `inner`; see `/guides/signing`."}}},"UserActivityDepositLandedDoc":{"type":"object","required":["user","asset","amount","chain_id","deposit_nonce","timestamp_ns"],"properties":{"amount":{"type":"string","description":"Amount in CE inventory decimals (1e18), serialized as a 0x-prefixed hex string."},"asset":{"type":"string","description":"Credited asset as a stringified asset identifier."},"chain_id":{"type":"string","description":"Chain id where the deposit was observed."},"deposit_nonce":{"type":"integer","format":"int64","description":"Per-user, per-chain deposit nonce for client deduplication.","minimum":0},"timestamp_ns":{"type":"integer","format":"int64","description":"Event timestamp, in nanoseconds since the Unix epoch.","minimum":0},"user":{"type":"string","description":"User public key whose account was credited."}}},"UserActivityEventDoc":{"oneOf":[{"type":"object","required":["DepositLanded"],"properties":{"DepositLanded":{"$ref":"#/components/schemas/UserActivityDepositLandedDoc"}}},{"type":"object","required":["WithdrawalCompleted"],"properties":{"WithdrawalCompleted":{"$ref":"#/components/schemas/UserActivityWithdrawalCompletedDoc"}}},{"type":"object","required":["PositionCleared"],"properties":{"PositionCleared":{"$ref":"#/components/schemas/UserActivityPositionClearedDoc"}}},{"type":"object","required":["SubAccountAssetTransferred"],"properties":{"SubAccountAssetTransferred":{"$ref":"#/components/schemas/UserActivityTransferDoc"}}}],"description":"Account activity event emitted on the authenticated account-events stream."},"UserActivityPositionClearedDoc":{"type":"object","required":["user","sub_account_index","asset","timestamp_ns"],"properties":{"asset":{"type":"string","description":"Cleared position asset as a stringified asset identifier."},"sub_account_index":{"type":"integer","format":"int64","description":"Sub-account index containing the cleared position.","minimum":0},"timestamp_ns":{"type":"integer","format":"int64","description":"Event timestamp, in nanoseconds since the Unix epoch.","minimum":0},"user":{"type":"string","description":"User public key whose flat position was cleared."}}},"UserActivityTransferDoc":{"type":"object","required":["user","source_sub_account_index","target_sub_account_index","asset","amount","timestamp_ns"],"properties":{"amount":{"type":"string","description":"Amount in CE inventory decimals (1e18), serialized as a 0x-prefixed hex string."},"asset":{"type":"string","description":"Transferred asset as a stringified asset identifier."},"source_sub_account_index":{"type":"integer","format":"int64","description":"Source sub-account index.","minimum":0},"target_sub_account_index":{"type":"integer","format":"int64","description":"Target sub-account index.","minimum":0},"timestamp_ns":{"type":"integer","format":"int64","description":"Event timestamp, in nanoseconds since the Unix epoch.","minimum":0},"user":{"type":"string","description":"User public key whose sub-accounts were updated."}}},"UserActivityWithdrawalCompletedDoc":{"type":"object","required":["user","asset","amount","chain_id","withdrawal_nonce","timestamp_ns"],"properties":{"amount":{"type":"string","description":"Amount in CE inventory decimals (1e18), serialized as a 0x-prefixed hex string."},"asset":{"type":"string","description":"Withdrawn asset as a stringified asset identifier."},"chain_id":{"type":"string","description":"Chain id where the withdrawal completed."},"timestamp_ns":{"type":"integer","format":"int64","description":"Event timestamp, in nanoseconds since the Unix epoch.","minimum":0},"user":{"type":"string","description":"User public key whose withdrawal completed."},"withdrawal_nonce":{"type":"integer","format":"int64","description":"Per-user, per-chain withdrawal nonce for client deduplication.","minimum":0}}},"UserInventoryJson":{"type":"object","required":["accounts","is_mm"],"properties":{"accounts":{"type":"object","description":"Sub-accounts indexed by account index (0 = main/spot, 1 = margin, 2+ = isolated)","additionalProperties":{"$ref":"#/components/schemas/SubAccountJson"},"propertyNames":{"type":"string"}},"is_mm":{"type":"boolean","description":"`true` if this user is a registered market maker (affects fee tiers and sync-book access)."}}},"UserOrder":{"type":"object","description":"User-provided information for an order","required":["signer","order_id","base_asset","book_price_decimals","book_quantity_decimals","details","side","creation_timestamp_ns","target","reduce_only","max_trading_fees_rate","protocol_version"],"properties":{"base_asset":{"type":"string","description":"Asset being traded","example":"200"},"book_price_decimals":{"type":"integer","format":"int32","description":"Decimal precision of prices in the order book for this asset","example":"2"},"book_quantity_decimals":{"type":"integer","format":"int32","description":"Decimal precision of quantities in the order book for this asset","example":"4"},"creation_timestamp_ns":{"type":"integer","format":"int64","description":"Timestamp, in nanoseconds, when this order was created","example":"1750146943779456128","minimum":0},"details":{"$ref":"#/components/schemas/OrderType","description":"Type of this order (i.e., limit or market)"},"max_trading_fees_rate":{"type":"integer","format":"int64","description":"Maximal trading fees rate for the execution of the order.\nThis is expressed as 100th of bps (divided by 1 000 000)","minimum":0},"order_id":{"type":"string","description":"User-defined (presumably unique) identifier for this order","example":"rt6G7V8gRAG4p7lfidkeUw=="},"protocol_version":{"$ref":"#/components/schemas/TplusProtocolVersion","description":"Used for backward compatibility"},"reduce_only":{"type":"boolean","description":"Set to true if the order should be rejected if it would increase position or flip position side"},"side":{"$ref":"#/components/schemas/OrderSide","description":"Which side of the market that this order is on"},"signer":{"$ref":"#/components/schemas/UserPublicKey","description":"Public-key used to sign this order"},"target":{"$ref":"#/components/schemas/TradeTarget","description":"Specify subaccount and trade type (spot or margin) for this order"},"trigger":{"oneOf":[{"type":"null"},{"$ref":"#/components/schemas/OrderTrigger","description":"Criteria to enable the order"}]}}},"UserOrdersPageResponse":{"type":"object","description":"One page of a user's orders, with pagination metadata.","required":["orders","page","limit","total_orders","total_pages","cursor_size","has_next_page"],"properties":{"cursor_size":{"type":"integer","description":"Number of orders actually returned on this page.","example":100,"minimum":0},"has_next_page":{"type":"boolean","description":"`true` if another page follows this one.","example":true},"limit":{"type":"integer","description":"Maximum number of orders per page.","example":100,"minimum":0},"next_page":{"type":["integer","null"],"description":"Index of the next page, or `null` if this is the last page.","example":1,"minimum":0},"orders":{"type":"array","items":{"$ref":"#/components/schemas/JsonBookOrder"},"description":"Orders on this page."},"page":{"type":"integer","description":"Zero-based index of this page.","example":0,"minimum":0},"total_orders":{"type":"integer","description":"Total number of orders across all pages.","example":253,"minimum":0},"total_pages":{"type":"integer","description":"Total number of pages at the current `limit`.","example":3,"minimum":0}}},"UserPositionsPageResponse":{"type":"object","required":["positions","page","limit","total_positions","total_pages","cursor_size","has_next_page"],"properties":{"cursor_size":{"type":"integer","description":"Number of positions actually returned in this page (at most `limit`).","example":12,"minimum":0},"has_next_page":{"type":"boolean","description":"`true` if another page follows this one.","example":false},"limit":{"type":"integer","description":"Maximum number of positions per page that was requested.","example":100,"minimum":0},"next_page":{"type":["integer","null"],"description":"Page number of the next page, or `null` if this is the last page.","example":1,"minimum":0},"page":{"type":"integer","description":"Zero-indexed page number returned.","example":0,"minimum":0},"positions":{"type":"array","items":{"$ref":"#/components/schemas/PositionResponse"},"description":"The page of open positions, sorted by sub-account index, then asset id."},"total_pages":{"type":"integer","description":"Total number of pages available for the current `limit`.","example":1,"minimum":0},"total_positions":{"type":"integer","description":"Total number of positions matching the query across all pages.","example":12,"minimum":0}}},"UserPublicKey":{"type":"string","description":"Ed25519 public key as a 64-character hex string (32 bytes)","examples":["eb886a56f9f0efa64432678cebf1270e9314a758e6eb697a606202a451e3e82e"]},"UserTrade":{"type":"object","required":["asset_id","trade_id","order_id","price","quantity","timestamp_ns","is_maker","is_buyer","status","sub_account","trading_fee"],"properties":{"asset_id":{"type":"string","example":"200","description":"Asset traded, as a stringified `AssetIdentifier`."},"is_auto_deleverage":{"type":"boolean","description":"True when the order was an auto-deleverage order (AVS backstop ADL)."},"is_buyer":{"type":"boolean","description":"`true` if the caller was the buyer."},"is_liquidation":{"type":"boolean","description":"True when the taker order was a forced-liquidation order."},"is_maker":{"type":"boolean","description":"`true` if the caller's order was the resting maker."},"order_id":{"type":"string","example":"rt6G7V8gRAG4p7lfidkeUw==","description":"The caller's order id that participated in this trade."},"price":{"type":"string","example":"105400.25","description":"Execution price in human-readable units."},"quantity":{"type":"string","example":"0.1","description":"Executed quantity in human-readable base units."},"rollback_reason":{"oneOf":[{"type":"null"},{"$ref":"#/components/schemas/RollbackReason","description":"Reason for rollback when status is Rollbacked; only present for the user whose order was at fault."}]},"status":{"$ref":"#/components/schemas/TradeStatus","description":"Settlement status of the trade."},"sub_account":{"type":"integer","format":"int64","description":"Sub-account the order traded against (`0` = spot, `1` = default margin, etc.).","example":1,"minimum":0},"timestamp_ns":{"type":"integer","format":"int64","example":"1750146943779456128","minimum":0,"description":"Execution timestamp (ns since the Unix epoch)."},"trade_id":{"type":"integer","format":"int64","minimum":0,"description":"Monotonic trade identifier for this market."},"trading_fee":{"type":"string","description":"Trading fee for this user on the fill. Positive = paid, negative = rebate (USD).","example":"0.42"}}},"VaultEntry":{"type":"object","required":["chainId","routingId","address"],"properties":{"address":{"type":"string"},"chainId":{"type":"integer","format":"int64","minimum":0},"routingId":{"type":"integer","format":"int32","minimum":0}}},"ViewOrderStatus":{"type":"string","enum":["Pending","Open","Partial","Cancelled","Closed","Completed"],"description":"Lifecycle status of an order in the book: `Pending` (accepted, not yet open), `Open`, `Partial` (partially filled), `Cancelled`, `Closed` (cancelled after a partial fill), or `Completed` (fully filled)."},"WithdrawalCompleted":{"type":"object","description":"Emitted when an on-chain withdrawal has been ingested.\n`amount` is in `INVENTORY_DECIMALS` (1e18).","required":["user","asset","amount","chain_id","withdrawal_nonce","timestamp_ns"],"properties":{"amount":{"type":"string","description":"Amount debited, in `INVENTORY_DECIMALS` (1e18)."},"asset":{"type":"string"},"chain_id":{"type":"string"},"timestamp_ns":{"type":"integer","format":"int64","description":"CE-side ingestion timestamp (ns since epoch).","minimum":0},"user":{"$ref":"#/components/schemas/UserPublicKey"},"withdrawal_nonce":{"type":"integer","format":"int64","description":"Per-user, per-chain withdrawal nonce (for client deduplication).","minimum":0}}},"WithdrawalInitResponse":{"type":"object","description":"Outcome of a withdrawal init or cancel request, proxied from the clearing engine.","required":["success"],"properties":{"details":{"type":["string","null"],"description":"CE-provided rejection reason; present only when `success` is `false`."},"success":{"type":"boolean","description":"`true` if the CE accepted the request, `false` if it rejected it (see `details`)."}}},"WithdrawalQueueApprovedDoc":{"type":"object","required":["type","approvals"],"properties":{"approvals":{"type":"array","items":{"$ref":"#/components/schemas/OneTimeSignatureDoc"},"description":"Quorum approval signatures that authorize the onchain withdrawal."},"type":{"type":"string","description":"Queue status discriminator. Always `\"approved\"` for this variant.","example":"approved"}}},"WithdrawalQueueDelayedDoc":{"type":"object","required":["type","execution_time_ns"],"properties":{"execution_time_ns":{"type":"integer","format":"int64","description":"Earliest execution time, in nanoseconds since the Unix epoch.","minimum":0},"type":{"type":"string","description":"Queue status discriminator. Always `\"delayed\"` for this variant.","example":"delayed"}}},"WithdrawalQueueFillingDoc":{"type":"object","required":["type","amount_filled"],"properties":{"amount_filled":{"type":"string","description":"Amount already filled, encoded as a decimal string."},"type":{"type":"string","description":"Queue status discriminator. Always `\"filling\"` for this variant.","example":"filling"}}},"WithdrawalQueueStatusDoc":{"oneOf":[{"$ref":"#/components/schemas/WithdrawalQueueDelayedDoc"},{"$ref":"#/components/schemas/WithdrawalQueueFillingDoc"},{"$ref":"#/components/schemas/WithdrawalQueueApprovedDoc"}],"description":"Current processing state for a queued withdrawal."},"WithdrawalRequest":{"type":"object","description":"The request when initializing a withdrawal.","required":["inner","signature"],"properties":{"additional_signers":{"type":"array","items":{"$ref":"#/components/schemas/AdditionalSigner"},"description":"Optional multisig co-signatures over the same withdrawal payload."},"inner":{"$ref":"#/components/schemas/InnerWithdrawalRequest","description":"Withdrawal payload being signed and submitted."},"signature":{"type":"array","items":{"type":"integer","format":"int32","minimum":0},"description":"Signature bytes as a JSON array of integers. Sign the action-specific preimage for `inner`; see `/guides/signing`."}}},"WsAck":{"type":"object","description":"WS acknowledgment schema (request_id echoes client-provided request_id for v1 envelope)","required":["request_id","status"],"properties":{"error":{"oneOf":[{"type":"null"},{"$ref":"#/components/schemas/WsError"}]},"request_id":{"type":"string","example":"req_123"},"status":{"type":"string","example":"submitted"}}},"WsChannelDoc":{"type":"object","required":["name"],"properties":{"name":{"type":"string","example":"orders"}}},"WsEnvelope":{"type":"object","description":"WS envelope schema for versioned/structured messages.\nProtocol note: default/raw behavior is treated as v0 (no subprotocol). Opt-in envelope via\n`Sec-WebSocket-Protocol: tplus.ws.v1`.","required":["type"],"properties":{"channel":{"type":["string","null"],"example":"trades"},"data":{},"error":{"oneOf":[{"type":"null"},{"$ref":"#/components/schemas/WsError"}]},"request_id":{"type":["string","null"],"example":"req_123"},"timestamp_ns":{"type":["integer","null"],"format":"int64","example":1710000000000000000,"minimum":0},"type":{"type":"string","example":"event"}}},"WsError":{"type":"object","description":"WS error schema - same shape as the REST `ApiErrorBody` for cross-transport consistency.","required":["code","message"],"properties":{"code":{"type":"string","example":"UNAUTHORIZED"},"details":{},"message":{"type":"string","example":"Invalid or missing auth token"},"retryable":{"type":["boolean","null"],"example":true}}},"WsWelcome":{"type":"object","description":"WS welcome schema","required":["type","channels"],"properties":{"channels":{"type":"array","items":{"$ref":"#/components/schemas/WsChannelDoc"}},"errors":{"type":["string","null"]},"type":{"type":"string","example":"subscriptions"}}},"u64":{"type":"integer","format":"int64","minimum":0}},"securitySchemes":{"bearer_token":{"type":"http","scheme":"bearer"},"user_id_header":{"type":"apiKey","in":"header","name":"User-Id","description":"Caller's user public key (32-byte Ed25519 key, hex). Required alongside the bearer token on every authenticated call. The header value is hex-parsed (optional 0x prefix, case-insensitive) into the raw key bytes, which must match the key the token was issued for. Bare lowercase hex is still recommended: `{user_id}` PATH parameters (unlike this header) are compared as exact lowercase no-0x strings."}}},"tags":[{"name":"Authentication","description":"Nonce generation, token issuance, and logout"},{"name":"Orders","description":"Order creation, modification, cancellation, and lookup"},{"name":"Trades","description":"Trade history and execution records"},{"name":"Markets","description":"Market lifecycle and configuration. `max_leverage` is the static initial-clamp value. Use `current_long_max_leverage` and `current_short_max_leverage` for live side-specific limits derived from current OI and the IM cliff curve; longs use CF, shorts use the `2 - LF` liability formula."},{"name":"Market Data","description":"OMS-hosted market state needed by trading clients: current funding rates and applied funding-rate history. Tickers, public trades, klines, and market-depth snapshots are served by the market-data service, not OMS."},{"name":"Registry","description":"Read OMS-cached registry state mirrored from the clearing engine and onchain registry. Use these endpoints to discover protocol-listed asset indices, chain-specific token addresses, per-chain deposit caps, risk parameters, token decimals, and live deposit-vault addresses. `/registry/assets` corresponds to `IRegistry.AssetData` fields such as `index`, `assetAddress`, `chainId`, `maxDeposits`, `max1hrDeposits`, and `minWeight`; `/registry/risk-parameters` exposes collateral/liability factors, open-interest limits, utilization/funding settings, and margin clamps; `/registry/decimals` returns decimals for requested chain addresses; `/registry/decimals/update` asks the CE to refresh decimals; `/registry/vaults` returns custody vault addresses. Deposit caps are enforced per chain asset and affect whether deposits credit a fungible `Index(n)` balance or an isolated `Address(token@chain)` balance."},{"name":"Positions","description":"Open position tracking across sub-accounts"},{"name":"Account","description":"Inventory, solvency, and account health"},{"name":"WebSockets","description":"Real-time data streams via WebSocket. All streams send a welcome message on connect: `{\"type\":\"subscriptions\",\"channels\":[{\"name\":\"<channel>\"}]}`. All timestamps are nanoseconds since Unix epoch."},{"name":"Smart Contracts","description":"User funds are custodied onchain in the **deposit vault** (`DepositVault.sol`), one deployment per supported chain. The vault holds every deposited token and is the only contract that moves user funds onchain: deposits credit a Tplus inventory balance, settlements swap one vault-held asset for another against an approved settler, and withdrawals release funds back to the user. All three are authorized offchain by the clearing engine (CE) and verified onchain by the vault.\n\nThe interface is `tplus-contracts/src/interfaces/IDepositVault.sol`. **Live vault addresses per chain are returned by `GET /registry/vaults` - do not hardcode them.**\n\nIn the structs below, amounts are in each token's native onchain decimals (the vault normalizes to the CE's 18-decimal internal units itself). `user` is the 32-byte Tplus user id - the same public key used in the `User-Id` header - and `account` is the sub-account index (see *Sub-Account Model* in the Introduction). Settlement and withdrawal nonces are per-user and tracked on the vault (`settlementCounts`, `withdrawalCounts`, `depositCounts`).\n\n---\n\n**Settlement**\n\nSettlement lets a user deploy balances held inside Tplus into onchain DeFi without first withdrawing. One asset leaves the vault (`tokenOut` / `amountOut`) and another enters it (`tokenIn` / `amountIn`), with the onchain leg executed by an **approved settler**. The CE signs a time-bound custom approval digest that the vault verifies onchain.\n\n*Offchain initiation - clients never call the vault directly:*\n\n1. `POST /settlement/init` - submit a multisig-signed settlement request. The OMS forwards it to the CE over the overlay; the CE runs the solvency / initial-margin checks, locks the outgoing inventory, and signs an approval, which is returned in the response and delivered to the settler.\n2. The approved settler executes the onchain leg via `executeAtomicSettlement`.\n3. `GET /settlement/signatures/{user_id}` - fetch a user's outstanding approval signatures (e.g. to re-deliver to a settler).\n\n*Settlement approval digest.* This is not standard EIP-712: there is no `\\x19\\x01` envelope, and `SETTLEMENT_TYPEHASH()` is a bare domain tag, currently `keccak256(\"SettlementApprovalV2\")`. For `executeAtomicSettlement`, the CE/admin signature is over this flat digest:\n\n```solidity\nenum SettlementMode { Spot, Margin }\n\nstruct Settlement {\n    address tokenOut;    // token sent OUT of the vault to the settler's target\n    uint256 amountOut;   // quantity of tokenOut to send out\n    address tokenIn;     // token the vault expects to receive IN\n    uint256 amountIn;    // quantity of tokenIn the vault must receive (enforced as a minimum)\n    SettlementMode mode; // 0 = Spot, 1 = Margin\n    bytes32 user;        // Tplus user id being settled\n    uint64  account;     // sub-account index\n    uint64  nonce;       // per-(user, account) settlement nonce\n    uint256 validUntil;  // unix seconds; rejected once block.timestamp > validUntil\n}\n\nbytes32 digest = keccak256(bytes.concat(\n    SETTLEMENT_TYPEHASH(),          // keccak256(\"SettlementApprovalV2\")\n    domainSeparator,\n    bytes32(uint256(uint160(order.tokenOut)) << 96),\n    bytes32(order.amountOut),\n    bytes32(uint256(uint160(order.tokenIn)) << 96),\n    bytes32(order.amountIn),\n    bytes32(uint256(uint8(order.mode))),\n    order.user,\n    bytes32(uint256(uint160(order.account))),\n    bytes32(order.nonce),\n    bytes32(order.validUntil),\n    settler\n));\n```\n\n*Atomic settlement* - the supported onchain settlement path. One CE-signed order moves a single asset pair in one vault transaction:\n\n```solidity\nfunction executeAtomicSettlement(\n    Settlement calldata order, // CE-approved user/account/assets/amounts/nonce/expiry\n    bytes32 settler,           // Tplus settler id; must be approved on the vault\n    bytes calldata data,       // opaque executor calldata forwarded to onAtomicSettlement\n    bytes calldata signature   // CE/admin signature over the settlement approval digest\n) external;\n```\n\nThe transaction caller must be an executor registered for `settler`. The vault verifies expiry, settler approval, executor authorization, nonce, and CE signature, then calls the executor contract at `msg.sender`. The `data` argument is not decoded by the vault - it is passed verbatim into the executor callback, so the executor contract defines its own ABI for this payload (for example router calldata, swap path, minimum-out policy, or venue-specific instructions).\n\nThe executor contract must implement `IAtomicSettlementCallback`:\n\n```solidity\nfunction onAtomicSettlement(address token, uint256 amount, bytes calldata data)\n    external returns (uint256); // minimum tokenIn the vault will receive\n```\n\nThe relevant vault call sequence is:\n\n```solidity\nuint256 expectedAmountIn = IAtomicSettlementCallback(msg.sender)\n    .onAtomicSettlement(order.tokenOut, order.amountOut, data);\nif (expectedAmountIn < order.amountIn) {\n    revert InsufficientAmountFromExecutor(expectedAmountIn, order.amountIn);\n}\n\nSafeTransferLib.safeTransferFrom(order.tokenIn, msg.sender, address(this), expectedAmountIn);\nSafeTransferLib.safeTransfer(order.tokenOut, msg.sender, order.amountOut);\n```\n\nIn practice, callers ABI-encode whatever instructions their executor expects into `data`, call `executeAtomicSettlement`, then decode `data` inside `onAtomicSettlement`. The callback returns the amount of `tokenIn` the executor will provide; that value must be at least `order.amountIn`. Reverts: `Expired` (past `validUntil`), `SettlerNotApproved`, `NotExecutor` (caller is not the settler's bound executor), `InvalidNonce`, `InvalidSignature` (signer is not the vault admin / CE approval key), and `InsufficientAmountFromExecutor` (callback returned less than the CE-approved minimum).\n\n*Approved settlers.* Only settlers registered on the vault may execute settlements, and each settler is bound to an executor address:\n\n```solidity\nfunction getApprovedSettlers() external view returns (bytes32[] memory);\nfunction addSettlerExecutor(bytes32 settler, address executor) external;\nfunction removeSettler(bytes32 settler) external;\n```\n\n---\n\n**Deposits**\n\nA deposit credits the user's Tplus inventory once the chain adapter ingests the onchain event:\n\n```solidity\nfunction deposit(bytes32 user, address tokenAddress, uint256 amount) external;\n```\n\nAn overload additionally registers the user's multisig signer configuration on first deposit (`SignerConfig[]`, weight thresholds, and a master-config signature). Deposits emit `Deposited` / `DepositedWithConfig` and bump the per-user `depositCounts` nonce. A `canDeposit` allow-list currently gates depositors and is intended to be removed before production.\n\n---\n\n**Withdrawals**\n\nA withdrawal returns vault funds to the user and requires a quorum of CE / administrator approval signatures bound to an approval epoch:\n\n```solidity\nstruct Withdrawal { address tokenAddress; uint256 amount; uint64 nonce; }\n\nfunction withdraw(\n    Withdrawal memory withdrawal,\n    bytes32 user, address target, uint256 validUntil,\n    bytes32 epochHash, bytes[] memory signatures\n) external;\n```\n\nThe withdrawal approval uses the same custom flat digest scheme with `keccak256(\"WithdrawalApprovalV1\")` as its type tag; it is not standard EIP-712. `signatures` must meet the vault's `withdrawalQuorum`; `epochHash` pins the approval to a specific approval epoch; `withdrawalCounts[user]` is the per-user nonce. Offchain, clients initiate via `POST /withdrawal/init`, track via `GET /withdrawal/queue/{user_id}`, and abort via `POST /withdrawal/cancel`."}]}