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

# Margin Accounts

> Fund and inspect parent margin accounts that back perp risk buckets

```typescript theme={null}
import { MonacoSDK } from "@0xmonaco/core";

// Initialize SDK with wallet client (see Initialization guide)

// Cursor paged: follow nextPageToken until it comes back empty.
await sdk.marginAccounts.listMarginAccounts({ pageSize: 20 });
await sdk.marginAccounts.getParentMarginAccountSummary();
await sdk.marginAccounts.getParentMarginAccountSummary({ tradingPairId });

// Funding
await sdk.marginAccounts.transferCollateralToParentMarginAccount({ asset, amount });
await sdk.marginAccounts.transferCollateralToRiskBucket({ asset, amount, tradingPairId, strategyKey? });
await sdk.marginAccounts.transferCollateralFromParentMarginAccount({ asset, amount });
await sdk.marginAccounts.getAvailableCollateral({ asset: "USDC" });

// Sub-accounts
await sdk.marginAccounts.createMarginSubAccount({ subaccountKey, label? });
await sdk.marginAccounts.transferMarginCollateral({ asset, amount, from, to });
await sdk.marginAccounts.transferCollateralToSubAccount(marginAccountId, amount);
await sdk.marginAccounts.transferCollateralFromSubAccount(marginAccountId, amount);
await sdk.marginAccounts.getMarginAccountPnl(marginAccountId);
await sdk.marginAccounts.closeMarginSubAccount(marginAccountId);

// Activity
await sdk.marginAccounts.getParentMarginAccountMovements(params?);

// Pre-trade check
await sdk.marginAccounts.simulateParentMarginOrderRisk(request);
await sdk.marginAccounts.simulateRiskBucketOrderRisk(request);
```

For high-level concepts, see [Perps Collateral](/developers/perps-collateral) for collateral, [Order Management](/developers/trade#risk-buckets-and-pre-trade-simulation) for risk simulation, and [Positions](/developers/positions#parent-accounts-and-risk-buckets) for account state.

***

## Lifecycle

Parent margin account creation is handled internally. Use `transferCollateralToParentMarginAccount` for first-time margin funding; it does not take a `tradingPairId` and does not open a risk bucket. Orders and risk simulation can use the accountless methods below without storing a `marginAccountId`.

Automatic parent creation and cross risk bucket provisioning return retryable
`503` / gRPC `UNAVAILABLE` if the sequencer refuses provisioning because its
lane is full. Back off before retrying; a transport timeout still requires
reconciling the enclosing operation's outcome. See [Error Handling](/sdk/typescript/error-handling).

<ResponseField name="listMarginAccounts" type="(params?: ListMarginAccountsParams) => Promise<ListMarginAccountsResponse>">
  Paginated list of the user's margin accounts. The result includes one row per margin account — the default (parent) margin account and every sub-account, closed sub-accounts included with `marginAccountState: "CLOSED"` — plus a separate row per risk bucket (risk bucket rows carry `riskBucketId`, `marginMode`, and `tradingPairId`). Every row carries its margin account's `subaccountKey` and `marginAccountState`.

  Cursor-paginated; there is no page-number (offset) mode.

  **Parameters:**

  * `params?`: `ListMarginAccountsParams`
    * `pageSize?`: number — 1 to 1000, defaults to 20; a larger value is rejected rather than clamped
    * `pageToken?`: string — pagination cursor: omitted or `""` starts a walk from the lowest margin account id; a previous `nextPageToken` resumes it. There is no `page`: the SDK rejects one with a `ValidationError` before sending, and a raw REST request carrying it gets `400`. The walk seeks on `(marginAccountId, tradingPairId, riskBucketId, strategyKey)` — the parent row ahead of its own bucket rows — so a bucket that opens or closes mid-walk no longer shifts the rows behind it across the resume point. The strategy key is part of the key because an auto risk bucket the engine has not materialized yet carries no `riskBucketId`, and one account and pair can hold several strategies; such a row sorts under the id its bucket will carry once the engine projects it, so provisioning completing mid-walk cannot serve the same risk bucket twice. The filters must match the request that minted the token
    * `state?`: string — keep only risk-bucket rows whose `riskBucketState` matches; the parent row has no state, so any filter excludes it
    * `tradingPairId?`: string — filter to bucket rows scoped to a specific trading pair

  **Returns:** `ListMarginAccountsResponse`:

  * `accounts`: `MarginAccountSummary[]`
  * `total`: number — exact; a user's accounts and buckets are a small bounded set
  * `pageSize`: number
  * `nextPageToken`: string — cursor for the next page, `""` once the walk is exhausted. Each page over-fetches one row, so an empty token is definitive
</ResponseField>

<ResponseField name="getParentMarginAccountSummary" type="(params?: GetMarginAccountSummaryParams) => Promise<MarginAccountSummary>">
  Live state of the authenticated wallet's parent margin account. Monaco resolves the parent account from the wallet and application scope; no `marginAccountId` lookup is required. Passing `tradingPairId` returns the summary for that pair's risk bucket under the parent — the risk bucket fields (`riskBucketId`, `marginMode`, `tradingPairId`) are populated on risk bucket summaries and left unset on the parent.

  **Parameters:**

  * `params?`: `GetMarginAccountSummaryParams`
    * `tradingPairId?`: string — return the bucket summary scoped to this trading pair

  **Returns:** `MarginAccountSummary`:

  * `marginAccountId`: string
  * `label?`: string
  * `riskBucketId?`: string — set on risk bucket summaries; unset on the parent
  * `marginMode?`: `"ISOLATED" | "CROSS"` — set on risk bucket summaries; unset on the parent
  * `tradingPairId?`: string — trading pair this risk bucket is scoped to, if set
  * `selectedTradingPairIds?`: string\[] — on the cross risk-bucket row only: the bucket's recorded scope. A pair joins when a cross order on it succeeds. After its positions close, removal is lazy: it remains listed until a later successful cross order on a different pair reconciles the bucket. Pairs declared before scope became derived follow the same delayed pruning after they trade and close. Omitted on the parent row and isolated rows; it may also be omitted when a cross bucket has no recorded scope
  * `strategyKey?`: string — strategy tag, if set
  * `subaccountKey?`: string — the margin account's sub-account key; `"default"` is the default (parent) margin account. On a risk bucket row, the key of the margin account that owns the bucket
  * `marginAccountState?`: string — the margin account's state: `"ACTIVE"`, or `"CLOSED"` for a closed sub-account
  * `riskBucketState?`: string — on risk bucket summaries only: `"NORMAL"` | `"PENDING_LIQUIDATION"` | `"LIQUIDATING"` | `"BAD_DEBT"` | `"CLOSED"`. Unset on the parent — a margin account has no liquidation lifecycle of its own; liquidation and bad debt live in its risk buckets
  * `equity`: string
  * `initialMarginRequired`: string
  * `maintenanceMarginRequired`: string
  * `freeCollateral`: string — trading-margin headroom for existing cross exposure
  * `withdrawableCollateral`: string — the largest amount the withdrawal gate will accept without lowering a position's posted margin (on the parent margin account: deposits plus settled realized PnL and funding, less risk bucket allocations, and never more than `freeCollateral`; on a risk bucket: its allocation plus realized PnL, less funding paid, unrealized losses and the margin its positions and resting orders require, and zero unless the risk bucket is `NORMAL`; an unrealized gain never raises it). Size transfers — and new isolated/first-use positions, which allocate principal through the same gate — from this field, not `freeCollateral`. Rounded toward zero at 6 decimal places, so a transfer of exactly this amount is accepted.
  * `totalPositionNotional`: string
  * `unrealizedPnl`: string
  * `realizedPnl`: string — on a risk bucket row, the risk bucket's cash balance (realized closes, funding, and transfers between a copy account's risk buckets land here), not trading profit; read trading PnL from position history and the portfolio PnL endpoints
  * `updatedAt`: string — ISO 8601
  * `maxRemovableCollateral?`: string — on a risk-bucket row, the largest removal the risk engine accepts when it may also lower the posted margin of the bucket's one open position: `withdrawableCollateral` plus that position's margin above its initial margin at the mark, capped by what the parent accepts. Everything past `withdrawableCollateral` comes out of the position's margin, raising its leverage and moving its liquidation price toward the mark, so use it to cap an isolated "remove margin" control and `withdrawableCollateral` for the part that leaves the position untouched. Equal to `withdrawableCollateral` on a cross bucket and on an isolated one with no open position or several. Present only on risk-bucket rows served from a live risk engine snapshot (absent on parent rows and fallback reads). Rounded toward zero at 6 decimal places.
  * `availableOrderCollateral?`: string — the collateral still available to open **new** orders in this risk bucket: the largest increase in the bucket's worst-case order reserve the risk engine's admission gate accepts, automatic top-up from the parent's unallocated capacity included. Unlike `withdrawableCollateral` it credits an unrealized gain in full, and it already contains the bucket's live free collateral — don't add `freeCollateral` or `unrealizedPnl` on top (it can legitimately differ from this row's `freeCollateral`, a persisted reconstruction that can't see resting-order reserves). **Not additive across rows**: every bucket's figure includes the same parent headroom. Below the bucket's initial margin it is the parent headroom alone: a new order's own margin and fee are drawn in full and the bucket's existing gap stays (the parent never fills it). A requirement budget, not an order size — a reducing, reduce-only, or opposite-side order sheltered by a larger resting ladder needs less or none of it (and can be admitted when this is zero). Collateral, not notional (divide by the market's initial margin rate). Present only on risk-bucket rows served from a live risk engine snapshot — absent on parent rows and fallback reads, so handle `undefined`; the per-order authority remains `simulateRiskBucketOrderRisk`. Rounded toward zero at 6 decimal places, like `withdrawableCollateral`; it bounds order admission, not transfers.
  * `copyFollowId?`: string — present only on a copy risk bucket row (a follow's copy account cross risk bucket and its isolated children): the id of the follow that funds it, one of your own follows. Omitted, never `null`, on every other row
  * `copyLeaderHandle?`: string — on a copy risk bucket row, the followed leader's custom TraderCode handle, when they still hold one; omitted otherwise

  **Example:**

  ```typescript theme={null}
  const s = await sdk.marginAccounts.getParentMarginAccountSummary();
  console.log(`Equity: ${s.equity}, Free: ${s.freeCollateral}`);

  // Risk bucket summary for one trading pair under the parent:
  const bucket = await sdk.marginAccounts.getParentMarginAccountSummary({
    tradingPairId: btcPerpTradingPairId,
  });
  ```
</ResponseField>

<ResponseField name="getMarginAccountSummary" type="(marginAccountId: string, params?: GetMarginAccountSummaryParams) => Promise<MarginAccountSummary>">
  Legacy compatibility method for callers that already store a `marginAccountId`. Prefer `getParentMarginAccountSummary` for new integrations.
</ResponseField>

***

## Funding

Margin collateral must be a USDC-family asset in the engine's configured
registry. The gateway supplies matching raw and normalized amounts, and the
engine validates them against the registry's token decimals and symbol.
Transfers cannot reset a live margin account or risk bucket's lifecycle state.

<ResponseField name="transferCollateralToMarginAccount" type="(marginAccountId: string, request: TransferCollateralRequest) => Promise<TransferCollateralResponse>" deprecated>
  **Deprecated.** This endpoint overloads two unrelated operations: with `tradingPairId` it allocates into that pair's isolated risk bucket (crediting the sole open position's stored margin), without it it performs a wallet-debiting deposit to the parent. Use `transferCollateralToRiskBucket` for bucket funding — which now applies the same position-margin credit — and `transferCollateralToParentMarginAccount` for wallet-to-margin deposits. Kept for backward compatibility; no removal date scheduled.

  When `tradingPairId` is omitted the transfer is scoped to the parent account, even if an active cross-risk bucket exists.

  **Parameters:**

  * `marginAccountId`: string
  * `request`: `TransferCollateralRequest`
    * `asset`: string — e.g. `"USDC"`
    * `amount`: string — decimal string in collateral units
    * `tradingPairId?`: string — target a specific bucket under the parent; omit to target the parent
    * `strategyKey?`: string — strategy tag for the bucket

  **Returns:** `TransferCollateralResponse`:

  * `movementId`: string
  * `marginAccountId`: string
  * `strategyKey?`: string — the bucket's strategy tag, if set
  * `asset`: string
  * `amount`: string
  * `status`: string
  * `newEquity`: string — equity after the transfer
  * `newTotalCollateralValue`: string — total collateral value in the account after the transfer
  * `newWithdrawableCollateral`: string — amount available to withdraw after the transfer, rounded toward zero at 6 decimal places

  Use `newTotalCollateralValue` and `newWithdrawableCollateral` to update the UI immediately without a separate read.
</ResponseField>

<ResponseField name="transferCollateralFromMarginAccount" type="(marginAccountId: string, request: TransferCollateralRequest) => Promise<TransferCollateralResponse>">
  Release collateral from a risk bucket, or move it from a known margin account back to the wallet.

  Which of those happens depends on how the bucket is named:

  * `tradingPairId` — releases that pair's **isolated** bucket. The collateral returns to the **parent margin account**, where it can be redeployed; it does not reach the wallet.
  * `marginMode: "CROSS"` — releases the account's **cross** bucket, which has no trading pair and cannot be named any other way. Also returns to the parent. The two are mutually exclusive.
  * neither — a parent-scoped transfer to the **wallet**. Prefer `transferCollateralFromParentMarginAccount` for new integrations.

  Constrained by `withdrawableCollateral`, or for an isolated bucket with one open position by `maxRemovableCollateral` (the part past `withdrawableCollateral` comes out of the position's margin). Trying to withdraw more will fail with a 400.

  **Parameters:**

  * `marginAccountId`: string
  * `request`: `TransferCollateralRequest`
    * `asset`: string — e.g. `"USDC"`
    * `amount`: string — decimal string in collateral units
    * `tradingPairId?`: string — release that pair's isolated risk bucket to the parent; omit to target the parent
    * `marginMode?`: `"ISOLATED" | "CROSS"` — pass `"CROSS"` to select the account's cross risk bucket, which has no trading pair and cannot be named by `tradingPairId`; the released collateral returns to the parent margin account. Mutually exclusive with `tradingPairId` — passing both is rejected client-side
    * `strategyKey?`: string — strategy tag for the bucket

  Same response shape as `transferCollateralToMarginAccount`, including `newTotalCollateralValue` and `newWithdrawableCollateral`.
</ResponseField>

<ResponseField name="transferCollateralToParentMarginAccount" type="(request: TransferCollateralToParentMarginAccountRequest) => Promise<TransferCollateralResponse>">
  Move collateral from the user's wallet into the parent margin account without specifying a `marginAccountId`. This endpoint does not accept `tradingPairId` and does not open a risk bucket.

  **Parameters:**

  * `request`: `TransferCollateralToParentMarginAccountRequest`
    * `asset`: string — e.g. `"USDC"`
    * `amount`: string — decimal string

  **Returns:** Same `TransferCollateralResponse` shape as `transferCollateralToMarginAccount`.

  **Example:**

  ```typescript theme={null}
  await sdk.marginAccounts.transferCollateralToParentMarginAccount({
    asset: "USDC",
    amount: "500",
  });
  ```
</ResponseField>

<ResponseField name="transferCollateralToRiskBucket" type="(request: TransferCollateralToRiskBucketRequest) => Promise<TransferCollateralResponse>">
  Move collateral into a specific risk bucket without specifying a `marginAccountId`. Monaco resolves the parent account and ensures the risk bucket exists automatically.

  What the money becomes when the isolated bucket holds an open position is stated by `applyToPositionMargin` — the two intents want opposite fates for the same dollars, and either way the liquidation cushion improves identically:

  * **Omitted or `true`** (the default is the human "Adjust Margin" gesture): the amount is **committed to the position's stored margin**. Effective leverage drops and stays down, initial margin required rises by exactly the amount, priced at the current mark (the stored position margin rises by `amount × entry price ÷ mark`), and free collateral is unchanged — new orders cannot spend it.
  * **`false`**: a **plain allocation**. Free collateral rises and the next order can spend it. Programmatic funding loops that top a bucket up to a free-collateral target **must pass `false`** — under the default, each top-up converts into requirement and the loop never converges.

  A flat isolated bucket has nothing to credit (both values are a plain allocation). The cross form does not take the field at all — cross risk is pooled at bucket equity, so `TransferCollateralToCrossRiskBucketRequest` types it `never` and the request validator rejects it even as `false`; omit it entirely on a cross transfer. (The raw REST and gRPC bodies are laxer, rejecting only an explicit `true`.) The response's `positionMarginCredited` reports what actually happened: `true` — the credit landed; `false` — it degraded to a plain allocation because the persisted position row was stale (close/reopen race; re-issue the Adjust Margin once the new row lands); absent — no credit was in play.

  **Parameters:**

  * `request`: `TransferCollateralToRiskBucketRequest` — a union on `marginMode`:
    * **Isolated** (`marginMode` omitted or `"ISOLATED"`):
      * `asset`: string — e.g. `"USDC"`
      * `amount`: string — decimal string
      * `tradingPairId`: string — UUID of the trading pair
      * `strategyKey?`: string — omit to target the default risk bucket for the pair
      * `marginAccountId?`: string — the margin account whose risk bucket is funded: omit (or pass the default margin account's id) for the default margin account, or name one of your open sub-accounts, which funds its own risk buckets from its own collateral
      * `applyToPositionMargin?`: boolean — see above; omitted means `true` (commit to the open position's stored margin), `false` keeps the amount as spendable free collateral
    * **Cross** (`marginMode: "CROSS"`):
      * `asset`: string
      * `amount`: string
      * `marginAccountId?`: string — as for isolated: omit for the default margin account, or name one of your open sub-accounts to fund that sub-account's cross risk bucket
      * nothing else. A cross transfer names no pairs: the cross bucket's scope is **derived from trading** — a pair joins the bucket when a cross order on it succeeds — so funding has nothing to declare. After that pair's positions close, removal is lazy: it remains listed until a later successful cross order on a different pair reconciles the bucket. Read the current scope from the cross bucket's row in `listMarginAccounts()` (`selectedTradingPairIds`) or from this call's response; the parent summary row carries no scope. Pairs declared before scope became derived follow the same delayed pruning after they trade and close. Because scope no longer needs declaring, `simulateParentMarginOrderRisk` and the pair-filtered summaries resolve a never-traded pair to the account's cross bucket instead of refusing it.

  <Warning>
    `selectedTradingPairIds` is no longer part of the cross request. It used to declare — and on every top-up **replace** — the bucket's scope, which meant a top-up that restated a partial list silently evicted every flat pair it forgot. The raw REST and gRPC bodies still accept the field for pre-deprecation clients and ignore it; the SDK types it `never` and its validator rejects it so the mistake is caught at compile time rather than believed to have taken effect.
  </Warning>

  **Returns:** Same `TransferCollateralResponse` shape as `transferCollateralToMarginAccount`.

  **Example:**

  ```typescript theme={null}
  await sdk.marginAccounts.transferCollateralToRiskBucket({
    asset: "USDC",
    amount: "500",
    tradingPairId: btcPerpTradingPairId,
    strategyKey: "btc-mm-strategy-a",
  });

  // Cross: fund the account's one cross bucket; scope follows your trading.
  await sdk.marginAccounts.transferCollateralToRiskBucket({
    asset: "USDC",
    amount: "500",
    marginMode: "CROSS",
  });
  ```
</ResponseField>

<ResponseField name="transferCollateralFromParentMarginAccount" type="(request: TransferCollateralFromParentMarginAccountRequest) => Promise<TransferCollateralResponse>">
  Move collateral from the parent margin account back to the user's wallet without specifying a `marginAccountId`.

  **Parameters:**

  * `request`: `TransferCollateralFromParentMarginAccountRequest`
    * `asset`: string — e.g. `"USDC"`
    * `amount`: string — decimal string

  **Returns:** Same `TransferCollateralResponse` shape as `transferCollateralFromMarginAccount`.
</ResponseField>

<ResponseField name="getAvailableCollateral" type="(params?: GetAvailableCollateralParams) => Promise<GetAvailableCollateralResponse>">
  Check what's actually movable from the user's wallet into a margin account, and what can come back out. The two directions are separate fields: `marginTransferable` is wallet-to-margin capacity, `marginAvailableCollateral` is the largest amount that can currently leave the parent margin account.

  **Parameters:**

  * `params?`: `GetAvailableCollateralParams`
    * `asset?`: string — defaults to USDC

  **Returns:** `GetAvailableCollateralResponse`:

  * `asset`: string
  * `walletAvailable`: string — unlocked spot balance
  * `walletLocked`: string — locked by spot orders / pending withdrawals
  * `marginTransferable?`: string — eligible to transfer in
  * `marginAvailableCollateral?`: string — the largest amount that can currently leave the parent margin account, matching `MarginAccountSummary.withdrawableCollateral`. Not trading headroom for existing cross exposure: it is bounded by deposits plus settled realized PnL and funding, less risk bucket allocations, as well as by margin requirements, and an unrealized gain does not raise it. The summary's `freeCollateral` remains the trading-margin headroom for existing cross exposure — but a **new** isolated or first-use position allocates principal through the same gate this field measures, so size those from this field (or `withdrawableCollateral`), not from `freeCollateral`.
</ResponseField>

***

## Sub-accounts

A wallet can hold several margin accounts per application, one per sub-account key. The key `"default"` is the default (parent) margin account — the only one that deposits, withdrawals and wallet transfers touch. Every sub-account holds its own collateral and its own risk buckets. The risk-bucket methods above (`transferCollateralToRiskBucket`, `simulateRiskBucketOrderRisk`) take an optional `marginAccountId` to act on a sub-account's own risk buckets; a copy-trading sub-account's risk buckets belong to its follow and are refused there. Sub-account management is denied to delegated-agent sessions. See [Perps Collateral](/developers/perps-collateral#margin-sub-accounts) for the end-to-end flow.

<ResponseField name="createMarginSubAccount" type="(request: CreateMarginSubAccountRequest) => Promise<CreateMarginSubAccountResponse>">
  Open a margin sub-account keyed by `subaccountKey`. The default margin account must already exist (deposit or transfer collateral in first). Re-creating the key of an open sub-account returns it unchanged; re-creating the key of a closed sub-account re-opens the same account.

  An owner may hold a limited number of open sub-accounts, set by their weighted 14-day volume tier — 10 at the first tier, rising with the fee-tier floors to 50 — or an admin-set override. A create past the cap is refused. Copy-trading sub-accounts do not count toward it.

  **Parameters:**

  * `request`: `CreateMarginSubAccountRequest`
    * `subaccountKey`: string — up to 64 letters, digits, `_`, `-`, `.` or `:`. Keys starting with `copy:` are reserved, and `"default"` names the default margin account, so both are rejected
    * `label?`: string — optional display label

  **Returns:** `CreateMarginSubAccountResponse`:

  * `marginAccountId`: string
  * `subaccountKey`: string
  * `marginAccountState`: string — `"ACTIVE"`
  * `label?`: string

  **Example:**

  ```typescript theme={null}
  const hedge = await sdk.marginAccounts.createMarginSubAccount({ subaccountKey: "hedge", label: "Hedge book" });
  ```
</ResponseField>

<ResponseField name="transferMarginCollateral" type="(request: TransferMarginCollateralRequest) => Promise<TransferMarginCollateralResponse>">
  Move collateral between the spot wallet and your margin accounts. Supported moves: wallet to the default margin account and back, and margin account to margin account (one atomic step, no wallet movement). A sub-account cannot exchange collateral with the wallet directly — route it through the default margin account. Risk-bucket endpoints are refused on this method; fund a risk bucket with `transferCollateralToRiskBucket`.

  **Parameters:**

  * `request`: `TransferMarginCollateralRequest`
    * `asset`: string — e.g. `"USDC"`; required for a wallet move, accepted and ignored on a margin-account-to-margin-account move, which stays in USD
    * `amount`: string — positive decimal string
    * `from`: `CollateralTransferEndpoint` — `{ wallet: true }` or `{ marginAccountId, riskBucketId? }`
    * `to`: `CollateralTransferEndpoint` — same shape; `from` and `to` must be different margin accounts, and at least one side must be a margin account

  **Returns:** `TransferMarginCollateralResponse`:

  * `movementId`: string
  * `amount`: string
  * `fromMarginAccountId?`: string — the source margin account, when the source is one
  * `toMarginAccountId?`: string — the destination margin account, when the destination is one
  * `toRiskBucketId?`: string — the destination risk bucket, when the destination is one

  **Example:**

  ```typescript theme={null}
  const parent = await sdk.marginAccounts.getParentMarginAccountSummary();
  await sdk.marginAccounts.transferMarginCollateral({
    asset: "USDC",
    amount: "250",
    from: { marginAccountId: parent.marginAccountId },
    to: { marginAccountId: hedge.marginAccountId },
  });
  ```
</ResponseField>

<ResponseField name="transferCollateralToSubAccount" type="(marginAccountId: string, amount: string) => Promise<TransferMarginCollateralResponse>">
  Move collateral from your default margin account to one of your sub-accounts, in one atomic step. The same move as `transferMarginCollateral` from the default margin account to the sub-account. The default margin account itself is refused; fund it with `transferCollateralToParentMarginAccount`.

  **Parameters:**

  * `marginAccountId`: string — UUID of one of your open sub-accounts
  * `amount`: string — positive decimal string

  **Returns:** `TransferMarginCollateralResponse` with `fromMarginAccountId` the default margin account and `toMarginAccountId` the sub-account.

  **Example:**

  ```typescript theme={null}
  await sdk.marginAccounts.transferCollateralToSubAccount(hedge.marginAccountId, "250");
  ```
</ResponseField>

<ResponseField name="transferCollateralFromSubAccount" type="(marginAccountId: string, amount: string) => Promise<TransferMarginCollateralResponse>">
  Move collateral from one of your sub-accounts back to your default margin account, in one atomic step. The amount must fit the sub-account's free collateral.

  **Parameters:**

  * `marginAccountId`: string — UUID of one of your open sub-accounts
  * `amount`: string — positive decimal string

  **Returns:** `TransferMarginCollateralResponse` with `fromMarginAccountId` the sub-account and `toMarginAccountId` the default margin account.
</ResponseField>

<ResponseField name="getMarginAccountPnl" type="(marginAccountId: string) => Promise<MarginAccountPnl>">
  One of your margin accounts' own PnL — the default margin account or a sub-account. Transfers between your accounts are flows, not PnL, so the figures of all your margin accounts sum to the owner-wide figure.

  **Parameters:**

  * `marginAccountId`: string — UUID of one of your margin accounts

  **Returns:** `MarginAccountPnl`:

  * `marginAccountId`: string
  * `realizedPnl`: string — settled on the account plus still held in its open risk buckets
  * `unrealizedPnl`: string — the account's open positions repriced at the latest risk marks
</ResponseField>

<ResponseField name="closeMarginSubAccount" type="(marginAccountId: string) => Promise<CloseMarginSubAccountResponse>">
  Close a flat sub-account in one call. The sub-account must hold no position and no resting order, conditional order or TWAP. Its flat, solvent risk buckets close in the same step, and all of its remaining collateral moves to the default margin account. A risk bucket in liquidation or bad debt refuses the close, and nothing moves. The default margin account cannot be closed, and a copy-trading sub-account is closed by stopping its follow.

  **Parameters:**

  * `marginAccountId`: string — UUID of one of your open sub-accounts

  **Returns:** `CloseMarginSubAccountResponse`:

  * `marginAccountId`: string — the closed sub-account
  * `defaultMarginAccountId`: string — the default margin account that received the remaining collateral
  * `amount`: string — collateral moved to the default margin account
</ResponseField>

***

## Activity

<ResponseField name="getParentMarginAccountMovements" type="(params?: GetMarginAccountMovementsParams) => Promise<GetMarginAccountMovementsResponse>">
  Collateral movements (transfers in / out) recorded against the authenticated wallet's parent margin account. PnL, funding, and fees are tracked through other endpoints (`getPaginatedUserMovements` and `GET /api/v1/accounts/funding-payments`).

  **Parameters:**

  * `params?`: `GetMarginAccountMovementsParams`
    * `movementType?`: `"TRANSFER_IN"` | `"TRANSFER_OUT"`
    * `pageSize?`: number — 1 to 1000, defaults to 20
    * `pageToken?`: string — cursor pagination. Omitted or `""` starts the walk at the newest row; otherwise pass the previous response's `nextPageToken`. There is no `page`: the SDK rejects one with a `ValidationError` before sending, and a raw REST request carrying it gets `400`. The filters must match the request that minted the token.

  **Returns:** `GetMarginAccountMovementsResponse`:

  * `movements`: `MarginAccountMovement[]` — each with `movementId`, `movementType`, `asset`, `amount`, `createdAt`; newest-first
  * `pageSize`: number
  * `nextPageToken`: string — cursor for the next page, `""` once the walk is exhausted. Each page over-fetches one row, so an empty token is definitive: follow a non-empty token, stop on an empty one

  No item count: the underlying ledger is too large to count behind this filter.
</ResponseField>

<ResponseField name="getMarginAccountMovements" type="(marginAccountId: string, params?: GetMarginAccountMovementsParams) => Promise<GetMarginAccountMovementsResponse>">
  Legacy compatibility method for callers that already store a `marginAccountId`. Prefer `getParentMarginAccountMovements` for new integrations.
</ResponseField>

***

## Pre-trade simulation

<ResponseField name="simulateParentMarginOrderRisk" type="(request: SimulateOrderRiskRequest) => Promise<SimulateOrderRiskResponse>">
  Preflight a perp order against the authenticated wallet's parent margin account without placing it. Monaco resolves the parent account from the wallet and application scope.

  **Parameters:**

  * `request`: `SimulateOrderRiskRequest`
    * `tradingPairId`: string
    * `side`: `"BUY" | "SELL"`
    * `positionSide?`: `"LONG" | "SHORT" | "NONE"` — deprecated compatibility field; if supplied, it must agree with `side`
    * `orderType`: `"LIMIT" | "MARKET"`
    * `price?`: string — required for LIMIT
    * `quantity`: string
    * `leverage`: string — decimal string, e.g. `"10"`; required unless `reduceOnly` is `true`
    * `reduceOnly?`: boolean
    * `slippageToleranceBps?`: number — MARKET only, `0`–`1000`, the rule the server applies to a placed MARKET order (a LIMIT preview carrying it is rejected with the placement's message). Tightens the previewed walk to the band the real order will run under, so `expectedMatchResult` and `estimatedFee` price under it. Omit for the placement default; it never widens the band. To carry a previewed value into placement, note that `placeMarketOrder` and the batch helpers take `slippageTolerance` as a **ratio** (`0.05` = 500 bps): pass `slippageToleranceBps / 10_000` — a `slippageToleranceBps` key on those calls is not forwarded. On a first-use isolated preview the value is also forwarded to the bucket's auto-funding for parity with placement; that path floors it at 1,000 bps, so today it does not change the funded figure.

  **Returns:** `SimulateOrderRiskResponse`:

  * `accepted`: boolean
  * `rejectReason?`: string — human-readable when `accepted: false`
  * `rejectCode?`: string — the structured code of the refusal, the same code placing the order would return in the error envelope (e.g. `MARGIN_MODE_CONFLICT` when the margin account holds the pair in its other margin mode; `INVALID_ORDER` for the catch-all; `INSUFFICIENT_LIQUIDITY` (`rejectReason` `Insufficient liquidity`) for an `IOC` order that would not fill — an empty book, or not enough at its limit). Present for client-class refusals; absent when the preview failed on a server-side fault (`rejectReason` is then a generic retry message)
  * `marginAccountId`: string — the resolved margin account the simulation ran against
  * `strategyKey?`: string — the bucket's strategy tag, if set
  * `marginMode?`: `"ISOLATED" | "CROSS"` — the resolved risk-bucket mode, if set
  * `riskBucketId?`: string — the risk bucket the simulation resolved against, if any
  * `selectedTradingPairIds?`: string\[] — cross previews only, never a caller's list: from `simulateRiskBucketOrderRisk` the derived scope the preview ran against (the cross bucket's recorded pairs — including any still listed after their positions closed — every pair with an open position in it, and the previewed pair; the previewed pair alone before the first cross order); from `simulateOrderRisk` and `simulateParentMarginOrderRisk`, the recorded pairs plus the previewed pair
  * `equityAfter`: string
  * `initialMarginRequiredAfter`: string
  * `maintenanceMarginRequiredAfter`: string
  * `freeCollateralAfter`: string

  An order that closes an isolated position flat, when that close retires the risk bucket (nothing else resting in it and no live TWAP order holding it open), reports the risk bucket as closed, as placement leaves it: `equityAfter`, `initialMarginRequiredAfter`, `maintenanceMarginRequiredAfter` and `freeCollateralAfter` are all `"0"`. When the risk bucket is retained, use the values the preview returns.

  * `estimatedFee?`: string — what the order pays in fees once its whole size has executed: the taker rate (plus the application's additional taker fee) on the quantity that would cross immediately, the maker rate on the quantity that would rest. One figure whether the order fills, rests, or splits between the two. **Signed** — negative is a net rebate, which is what a resting order earns on a pair whose maker fee is negative. Populated even when `accepted: false`, and for reduce-only orders. Assumes the resting part eventually fills. Absent — never `0` — when the engine could not price the order; treat absence as unknown, not free.
  * `estimatedLiquidationPrice?`: string — isolated simulated position/risk-bucket threshold; cross conditional per-target-position estimate, not a whole-account scalar. Absent or blank means unavailable, never `0`.
  * `expectedMatchResult?`: MatchResult — the simulated match the engine ran to admit the order: the same `MatchResult` shape a placement's `matchResult` carries, from the same walk over the live book at the moment of the preview, under `slippageToleranceBps` merged tighter-wins with the market's protective band (1,000 bps by default; see [Perp price band](/trading-mechanics/perps#perp-price-band)) and your wallet's self-trade-prevention default (the preview runs under your wallet, as placement does). Not a client-side re-walk, so preflight and placement read the same numbers. `totalFilled` is what would execute; `remainingQuantity` what the band or the depth leaves unfilled — the partial-fill indicator, a MARKET preview with a non-zero remainder is a warning that the size does not clear the previewed book — placement runs against a later book, so read the placement's own status and quantities rather than treating this as the fill it will get; `averageFillPrice` and `executionPriceRange.worstPrice` are the fill levels (highest ask a BUY takes, lowest bid a SELL hits), `null` when nothing crosses; `status` is the status placement would end in — `FILLED` for a full fill, `CANCELLED` for a MARKET order the band or the depth cuts short (a MARKET remainder cannot rest, so the fills execute and the rest is cancelled; read `remainingQuantity` for the partial fill), `SUBMITTED` for a LIMIT that rests, `PARTIALLY_FILLED` for a LIMIT that crosses partly and rests the remainder; `actualSlippageBps` is the realized slippage against `referencePrice` — for a LIMIT or IOC preview, against the limit price, `null` when every fill improved on it; `maxSlippageBps` echoes your tolerance. A preview cannot carry `postOnly`, TP/SL legs or a per-order self-trade-prevention override, so a post-only order that would cross is refused by placement, not here. Present only when `accepted` is true; `null` on every refused preview (`accepted: false` with a `rejectReason`), including the post-match maker-risk rejection — treat absence as unknown, not as a zero fill.
  * `referencePrice?`: string — the touch on the taking side when a MARKET preview ran (best ask for a BUY, best bid for a SELL), which `expectedMatchResult` and its `actualSlippageBps` are measured from. Present only when `accepted` is true and the preview is a MARKET order; a LIMIT or IOC preview is measured against its own limit price and publishes nothing here.

  These price against the book **right now**. A take-profit or stop-loss closes later, on a different book, under the band a triggered market leg carries (1,200 bps by default; see [Perp price band](/trading-mechanics/perps#perp-price-band)) and anchored at trigger time — so to size a TP/SL target, preview the close as a reduce-only MARKET order of the position's size and apply `expectedMatchResult.actualSlippageBps` to your **trigger** price as an impact figure, rather than displaying this call's absolute `expectedMatchResult.averageFillPrice`. The fee tier the preview reads can lag the engine's own copy by one refresh interval, so `estimatedFee` and the fee settled moments later can differ by a tier step in that window.

  **Example:**

  ```typescript theme={null}
  const risk = await sdk.marginAccounts.simulateParentMarginOrderRisk({
    tradingPairId,
    side: "BUY",
    orderType: "LIMIT",
    price: "2000",
    quantity: "1.5",
    leverage: "10",
  });

  if (!risk.accepted) {
    showError(risk.rejectReason);
    return;
  }

  const estimatedLiquidationPrice = risk.estimatedLiquidationPrice?.trim();
  renderLiquidationPrice(
    estimatedLiquidationPrice
      ? risk.marginMode === "CROSS"
        ? `Conditional liquidation price: ${estimatedLiquidationPrice}`
        : estimatedLiquidationPrice
      : "unavailable",
  );
  ```

  Common pattern: run on every order-form change with debouncing, and disable the place-order button when `accepted === false`.
</ResponseField>

<ResponseField name="simulateOrderRisk" type="(marginAccountId: string, request: SimulateOrderRiskRequest) => Promise<SimulateOrderRiskResponse>">
  Legacy compatibility method for callers that already store a `marginAccountId`. Prefer `simulateParentMarginOrderRisk` for new integrations.
</ResponseField>

<ResponseField name="simulateRiskBucketOrderRisk" type="(request: SimulateRiskBucketOrderRiskRequest) => Promise<SimulateOrderRiskResponse>">
  Preflight an order against a risk bucket without storing a `marginAccountId`. Accepts both isolated and cross modes via a discriminated union on `marginMode`.

  **Isolated** (`marginMode?: "ISOLATED"`):

  * `tradingPairId`: string
  * `marginMode?`: `"ISOLATED"` — default when omitted
  * `strategyKey?`: string — omit for the default risk bucket
  * `marginAccountId?`: string — the margin account previewed: omit for the default margin account, or name one of your open sub-accounts to preview against that sub-account's own risk buckets
  * `side`, `orderType`, `price?`, `quantity`, `leverage?`, `reduceOnly?` — `leverage` may be omitted only when `reduceOnly` is `true`; otherwise the SDK raises `ValidationError` (a raw REST or gRPC caller gets `400` / `INVALID_ARGUMENT`)

  **Cross** (`marginMode: "CROSS"`):

  * `tradingPairId`: string — the pair being previewed
  * `marginMode`: `"CROSS"` — required
  * `marginAccountId?`: string — as for isolated: omit for the default margin account, or name one of your open sub-accounts
  * `side`, `orderType`, `price?`, `quantity`, `leverage?`, `reduceOnly?` — as for isolated, `leverage` is required unless `reduceOnly` is `true`
  * nothing else. A cross preview names no pairs: the cross bucket's scope is **derived from trading** — a pair joins the bucket when a cross order on it succeeds; after its positions close, removal is lazy: it remains listed until a later successful cross order on a different pair reconciles the bucket — so the previewed pair is in scope by construction and there is nothing to declare. The response's `selectedTradingPairIds` reports the derived scope the preview ran against: the active cross bucket's recorded pairs (including any still listed after their positions closed), every pair with an open position in it, and the previewed pair (the previewed pair alone before the first cross order).

  <Warning>
    `selectedTradingPairIds` is no longer part of the cross request. It used to be required (non-empty, containing `tradingPairId`, covering every pair with an open cross position) while the server never priced anything from it — the engine prices the positions it holds whatever the caller declared — so it only ever produced 400s. The raw REST and gRPC bodies still accept the field for pre-deprecation clients and ignore it; the SDK types it `never` and its validator rejects it so the mistake is caught at compile time rather than believed to have taken effect.
  </Warning>

  Isolated simulation works before that pair's risk bucket exists: the API models the first-use bucket virtually, created empty exactly as live placement creates it, and the simulated admission draws what the order needs from the parent margin account. Cross simulation likewise works before the user has placed their first cross order — the API resolves the deterministic cross risk bucket and simulates the same draw, matching live placement.

  For cross simulation, `estimatedLiquidationPrice` is conditional per target position, not a whole-account scalar: it varies only the target position's mark while all other marks in the cross risk bucket remain unchanged. Other position marks, funding, realized PnL, fees/reserves, and collateral can change it. Render it as a conditional liquidation price; an absent or blank value is unavailable, never `0`.

  **Returns:** Same `SimulateOrderRiskResponse` shape as `simulateOrderRisk`.

  **Isolated example:**

  ```typescript theme={null}
  const risk = await sdk.marginAccounts.simulateRiskBucketOrderRisk({
    tradingPairId: btcPerpTradingPairId,
    strategyKey: "btc-mm-strategy-a",
    side: "BUY",
    orderType: "LIMIT",
    price: "65000",
    quantity: "0.5",
    leverage: "10",
  });

  ```

  **Cross example:**

  ```typescript theme={null}
  const risk = await sdk.marginAccounts.simulateRiskBucketOrderRisk({
    tradingPairId: btcPerpTradingPairId,
    marginMode: "CROSS",
    side: "BUY",
    orderType: "LIMIT",
    price: "65000",
    quantity: "0.5",
    leverage: "10",
  });

  if (risk.accepted) {
    const estimatedLiquidationPrice = risk.estimatedLiquidationPrice?.trim();
    renderLiquidationPrice(
      estimatedLiquidationPrice
        ? `Conditional liquidation price: ${estimatedLiquidationPrice}`
        : "unavailable",
    );
  }
  ```
</ResponseField>

***

## Errors

* `400` on `transferCollateralFromParentMarginAccount` or `transferCollateralFromMarginAccount` when `amount` exceeds `withdrawableCollateral`
* `400` on `simulateParentMarginOrderRisk`, `simulateRiskBucketOrderRisk`, `simulateOrderRisk`, and order placement: `accepted: false` with `rejectReason` (e.g., "Requested leverage exceeds max leverage for trading pair") and `rejectCode` (e.g. `MARGIN_MODE_CONFLICT` when the margin account holds the pair in its other margin mode). Market-order simulations apply the same 1,000 bps price band as placement and can return a `rejectReason` for no liquidity inside the band or no usable reference price
* `400` on `simulateParentMarginOrderRisk`, `simulateRiskBucketOrderRisk` and `simulateOrderRisk` when `slippageToleranceBps` is sent on a non-MARKET preview or falls outside `0`–`1000` (it is an integer bps value; a negative tolerance is refused by the same schema as one above `1000`) — the SDK raises `ValidationError` before the request is sent; a raw REST or gRPC caller gets placement's own message (gRPC `INVALID_ARGUMENT`)
* `400` on `createMarginSubAccount` when the key is malformed, reserved (`copy:`) or `"default"` (the SDK raises `ValidationError` before the request is sent), when the default margin account does not exist yet, or when the owner is at their open sub-account cap
* `400` on `closeMarginSubAccount` when the account is the default margin account, a copy-trading sub-account, or already closed; when it still holds a position, resting order, conditional order or TWAP; or when one of its risk buckets is in liquidation, in bad debt, or insolvent
* `400` on `transferMarginCollateral` when an endpoint does not name exactly one of `wallet` and `marginAccountId`, both sides are the wallet, either side names a risk bucket, a wallet transfer names a sub-account rather than the default margin account, `from` and `to` are the same account, or either account is closed
* `400` on `transferCollateralToRiskBucket` and `simulateRiskBucketOrderRisk` when `marginAccountId` names a closed sub-account or a copy-trading sub-account
* `400` on `transferCollateralToSubAccount` and `transferCollateralFromSubAccount` when `marginAccountId` is the default margin account, the sub-account is closed, or the amount exceeds the source's free collateral
* `400` on `transferMarginCollateral`, `transferCollateralToSubAccount` and `transferCollateralFromSubAccount` when `amount` has more than 6 decimal places (`amount exceeds supported precision for USDC (6 decimals)`)
* `403` on `createMarginSubAccount`, `closeMarginSubAccount`, `transferMarginCollateral`, `transferCollateralToSubAccount` and `transferCollateralFromSubAccount` for delegated-agent sessions
* `403` when the account belongs to a different user
* `404` when the `marginAccountId` does not exist. The sub-account methods (`closeMarginSubAccount`, `getMarginAccountPnl`, `transferMarginCollateral`, `transferCollateralToSubAccount`, `transferCollateralFromSubAccount`) and the `marginAccountId` of `transferCollateralToRiskBucket` / `simulateRiskBucketOrderRisk` return the same `404` for another user's account as for an unknown one
* `429` on `createMarginSubAccount`, `closeMarginSubAccount`, `transferMarginCollateral`, `transferCollateralToSubAccount` and `transferCollateralFromSubAccount` — they draw from the per-user movement budget that withdrawals and collateral transfers share; `getMarginAccountPnl` draws from the read budget. REST carries the retry horizon in `details.retryAfter`; gRPC returns `RESOURCE_EXHAUSTED` with a `RetryInfo` detail
* `503` on `createMarginSubAccount` when no matching engine connection is configured or the new account has not been projected yet; retry with the same key, which is idempotent for an open sub-account. `closeMarginSubAccount` also returns `503` when no matching engine connection is configured
* `500` on `createMarginSubAccount` and `closeMarginSubAccount` when the call to the matching engine fails in transit; a create can be retried with the same key
* `429` on `simulateParentMarginOrderRisk`, `simulateRiskBucketOrderRisk` and `simulateOrderRisk` — every authenticated read draws one request from the account's read budget, and each preview also draws one from the account family's dedicated preview budget. Enforcement of each is gated per environment by its own flag (`read_rate_limit_enforced`, `order_risk_preview_rate_limit_enforced`), both **off by default**: until a flag is enabled its budget is measured and over-budget requests are admitted, so treat the `429` as a contract to handle rather than one you can rely on observing. When enforced, REST carries the retry horizon in `details.retryAfter` while gRPC returns `RESOURCE_EXHAUSTED` with a `google.rpc.RetryInfo` detail — the hint is transport-specific, so a gRPC caller reads `RetryInfo` rather than looking for a `retryAfter` field. See [Rate limits](/developers/rate-limits#reads)

For end-to-end usage and UI patterns, see [Perps Collateral](/developers/perps-collateral), [Order Management](/developers/trade#risk-buckets-and-pre-trade-simulation), and [Positions](/developers/positions).


This documentation is built and hosted on [Mintlify](https://mintlify.com), a developer documentation platform.