Skip to main content
POST
Submit withdrawal
The response body shown here is a static example, not live data. After you click Send, your live result appears in a separate panel headed 200 OK. The panel under the status-code tabs is a fixed sample from the spec — its field values (fees, prices, sizes, IDs, timestamps) are placeholders. Use Send, or call the endpoint, for current values.
Submit a withdrawal of collateral to the chain. The optional spotAssetId field selects what is withdrawn:
  • USDG (default, spotAssetId omitted or 0): quote-currency collateral, amount in quote quantums — the original v0 behavior, byte-for-byte.
  • Spot collateral asset (spotAssetId > 0): an asset from GET /v1/spotAssets (e.g. 1 = SPY-USD-SPOT), amount in the ASSET’S base quantums. Wallet-signed requests sign the dedicated EIP-712 type (SpotAssetWithdraw, see below); API-key-signed requests include the conditional sa key in the WithdrawV1 canonical payload (see below).
Two mutually exclusive authentication modes are supported, selected by the presence of the X-API-Key header:
  • Wallet-signed (default): the request is self-authenticated by a secp256k1 EIP-712 typed-data signature in the body’s signature field — no X-API-Key / X-Signature headers. This is the original v0 mode, unchanged.
  • API-key-signed: the request carries the X-API-Key / X-Timestamp / X-Signature header triple with an Ed25519 signature over the ordersign WithdrawV1 typed canonical payload (see below); the body signature field is ignored. The key must be bound to exactly the requested (ethereumAddress, accountIndex) and must carry the opt-in withdraw permission (grantable only through the operator’s provisioning flow — keys from the public POST /createApiKey are always trade-only and are rejected with HTTP 403). This mode exists for contract-owned accounts (e.g. vaults), which cannot produce wallet signatures. Spot-asset withdrawals (spotAssetId > 0) are supported under the same withdraw permission: the canonical payload’s conditional sa key binds the asset id, so a captured USDG signature can never be replayed as a spot withdrawal or vice versa.
In v0 only withdraw-to-self is supported on both modes: the recipient is always the wallet that owns the account (ethereumAddress), so there is no to field — an API key can only move funds to the account owner’s own address.

Response behavior

Asynchronous. A 202 Accepted means the withdrawal passed validation and was queued to the matching engine with status: PENDING. The definitive outcome (applied, or rejected for insufficient free collateral) is delivered asynchronously — poll GET /v1/accountTransferUpdates or subscribe to the account transfer update WebSocket channel and correlate on withdrawalId. Spot-specific rejections: REJECTED_INSUFFICIENT_SPOT_BALANCE (holds less than amount), REJECTED_ACCOUNT_IN_WIND_DOWN (account is a frozen estate under liquidation), REJECTED_INVALID_MARKET, and REJECTED_INSUFFICIENT_COLLATERAL (removing the collateral would leave an outstanding loan under-collateralized).

Signing the request (USDG)

Withdrawals use EIP-712 typed data (eth_signTypedData_v4), so wallets render each field and institutional / MPC signers can whitelist on the domain. Sign this typed data with the wallet that owns ethereumAddress, then split the 65-byte result into {r, s, v} for the signature field. The gateway recovers the signer and rejects mismatches with HTTP 401. Domain
Types
Message
amount is the integer collateral quote-quantum amount (same value as the amount request field). Per-environment domain parameters Complete eth_signTypedData_v4 call (ethers v6 / viem)
The v component must be 0x1b (27) or 0x1c (28); wallets that return 0 / 1 should have 27 added before sending.

Signing the request (spot asset, spotAssetId > 0)

Spot-asset withdrawals sign the SpotAssetWithdraw primary type under a domain dedicated to spot withdrawals — a captured USDG Withdraw signature can never authorize a spot withdrawal, and vice versa. spotAssetId (in both the signed message and HTTP body) and amount are both signed, so neither can be swapped after signing. Domain — same chainId / verifyingContract (BridgeVault) as the USDG domain above; only the name differs:
Types / message

Signing the request (API-key mode)

The X-Signature header carries a lowercase-hex Ed25519 signature over the ordersign WithdrawV1 typed canonical payload — the same compact key-sorted-JSON convention as placeOrder. The payload is built from parsed request values (never raw body bytes):
Keys are strictly alphabetical (ad, ai, ct, n, op, q, [sa,] v) with no whitespace. ct must equal the X-Timestamp header value (nanosecond epoch), binding the signature to the drift window; op is fixed at 5 (withdraw — an order signature can never authorize a withdrawal); q is the integer quantum amount (same value as the amount field, unquoted): quote quantums for USDG, the asset’s base quantums for a spot asset; n is the body nonce signed verbatim. The sa key is conditional: present if and only if the body’s spotAssetId is > 0 (it then binds the asset id into the signature). For USDG withdrawals (spotAssetId omitted or 0) the payload must NOT contain an sa key at all — "sa":0 is not a valid encoding and fails verification. USDG payload bytes are therefore identical to clients built before spot support existed. Example (USDG payload → signature):
Example (spot-asset payload, spotAssetId: 1):
Replay protection is two-layered on this mode: the X-Timestamp drift window bounds how long a captured signature stays valid, and the nonce is single-use per (ethereumAddress, accountIndex) — plus spotAssetId for spot assets — exactly as on the wallet-signed mode (409 on reuse).

Authorizations

X-API-Key
string
header
required

Hex-encoded Ed25519 public key (64 chars). The public key IS the API key — register it via POST /createApiKey. Required on every authenticated request, both read-only and signed.

X-Timestamp
string
header
required

Unix time in nanoseconds as a decimal string (e.g. "1713825891591000000"). Millisecond or second epochs are rejected with 401 Unauthorized. Must be within ±30,000 ms of server wall-clock, or the request is rejected with 401 Unauthorized. Required on all mutating / credential-creating endpoints. This same value must appear as the ct field in the ordersign typed canonical payload (single-order endpoints) or in each element's ct field (batch endpoints).

X-Signature
string
header
required

Lowercase hex-encoded Ed25519 signature (128 chars).

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

ct must equal the X-Timestamp header value. Keys in brackets are conditional (omitted when empty). op values: 1=place, 2=cancel, 3=modify. See the ordersign package for field definitions and reference signing code.

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

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

Read endpoints are authenticated by ?address= (and optionally X-API-Key) only — no signature is required, except for the signed affiliate reads (see the Referral tag), which require the full header triple; with no body their signing message is X-Timestamp + ACTION. canonicalJSON(body) is the JSON body with object keys sorted lexicographically at every level and no whitespace; the server canonicalizes the received body before verifying, so only the bytes signed over must be canonical. Required on all mutating / credential-creating endpoints.

Body

application/json
ethereumAddress
string
required

Master EVM address initiating the withdrawal. In v0 this is also the on-chain recipient (withdraw-to-self) — there is no separate destination field.

Pattern: ^(0x|0X)?[0-9a-fA-F]{40}$
Example:

"0x742d35cc6634c0532925a3b844bc9e7595f2bd18"

amount
string
required

Amount to withdraw, as a base-10 integer string of quantums. The unit follows spotAssetId — USDG (spotAssetId omitted/0): quote quantums (1e9 = $1; for example, 1000000000 represents $1); requests below the minimum withdrawal size, or above the maximum single-withdrawal size ($1,000,000 / 1000000000000000 quote quantums), are rejected with HTTP 400 (e.g. validation error on field 'amount': must be at least 1000000000 quote quantums, or must be at most 1000000000000000 quote quantums), and the amount must be exactly representable in collateral base units. Spot asset (spotAssetId > 0): the ASSET'S base quantums — see stepSize on the asset's spotAssets entry (e.g. SPY inherits 1e-7 SPY from SPY-USD, so 70000000 = 7 shares); the engine rejects asynchronously with REJECTED_INSUFFICIENT_SPOT_BALANCE if the account holds less, REJECTED_ACCOUNT_IN_WIND_DOWN while the account is a frozen estate, or REJECTED_INSUFFICIENT_COLLATERAL if removing the collateral would leave the loan under-collateralized. In both cases amounts must be positive and fit in a signed 64-bit integer; float-style decimals are rejected.

Pattern: ^[0-9]+$
Example:

"5000000000000"

nonce
string
required

Replay-protection nonce, single-use per (ethereumAddress, accountIndex) — plus spotAssetId for spot-asset withdrawals — a repeated nonce is rejected with 409. Send the current time as nanoseconds since the Unix epoch (a plain base-10 integer, e.g. 1730000000000000000); the server accepts a timestamp up to 48 hours in the past by default (covers slow multi-approver / MPC custody signing) and up to 24 hours in the future by default (clock skew only) — both bounds are configurable per environment, so treat these as defaults, not guarantees. A legacy opaque nonce (e.g. a UUID) is still accepted for backward compatibility but is deprecated — prefer the nanosecond timestamp form. At most 64 characters.

Required string length: 1 - 64
Example:

"1730000000000000000"

accountIndex
integer

Trading account to debit. Defaults to 0 when omitted.

Required range: 0 <= x <= 9
spotAssetId
integer

What to withdraw. Omitted or 0 = USDG (quote currency, the default). A value > 0 names a spot collateral asset to withdraw instead (e.g. 1 = SPY-USD-SPOT — see GET /v1/spotAssets), which switches the amount unit to the asset's base quantums, the signed EIP-712 type to SpotAssetWithdraw (wallet-signed mode), and adds the conditional sa key to the WithdrawV1 canonical payload (API-key mode).

Example:

1

signature
object

secp256k1 EIP-712 typed-data signature (r, s, v) produced with eth_signTypedData_v4 by the wallet that owns ethereumAddress. The primary type follows spotAssetId: the Withdraw payload for USDG, the SpotAssetWithdraw payload (which additionally binds spotAssetId) when spotAssetId > 0 — see the POST /v1/withdraw endpoint description for the exact domains, types, and messages. Requests whose recovered signer does not match are rejected with HTTP 401. Conditionally required: mandatory on the default wallet-signed path (its absence is a 400); ignored on the API-key path, where the request instead carries the X-API-Key / X-Timestamp / X-Signature headers and the Ed25519 WithdrawV1 proof lives in the X-Signature header.

Response

Withdrawal accepted and queued to the matching engine. The body carries status: PENDING and a withdrawalId; the terminal outcome is delivered on the account transfer update stream.

withdrawalId
string
required

Server-generated identifier (UUID) correlating this withdrawal with the matching AccountTransferUpdate on the account transfer update stream.

Example:

"b3f1c2d4-5e6f-7a8b-9c0d-1e2f3a4b5c6d"

ethereumAddress
string
required

20-byte EVM address as hex: optional 0x or 0X prefix and exactly 40 hexadecimal digits. API responses normalize to lowercase a–f after 0x.

Pattern: ^(0x|0X)?[0-9a-fA-F]{40}$
accountIndex
integer
required

Account index (account index, 0–9). Identifies the account for orders, positions, fills, and API keys.

Required range: 0 <= x <= 9
amount
string
required

Withdrawal amount echoed back as a human-readable decimal in the withdrawn asset's units — quote currency (quote resolution 1e9) for USDG, the asset's own units for a spot asset (a spot request amount of "98795" base quantums at stepSize 0.0000001 echoes "0.0098795"), matching how the amount is reported on the account transfer update feed.

Examples:

"5000"

"1.5"

status
enum<string>
required

Synchronous status of a submitted withdrawal. Always PENDING on the 202 response — the withdrawal has been queued to the matching engine but not yet applied. The terminal outcome (APPLIED, REJECTED_INSUFFICIENT_COLLATERAL, ...) is delivered asynchronously on the account transfer update stream (see AccountTransferUpdate).

Available options:
PENDING
submittedAt
integer<int64>
required

Server receive timestamp (epoch microseconds).

spotAssetId
integer

Spot asset id being withdrawn. Present only on spot-asset withdrawals (request had spotAssetId > 0) — USDG responses omit both spotAssetId and spotAssetDisplayName, keeping their shape identical to before the field existed.

Example:

1

spotAssetDisplayName
string

Display name for spotAssetId (mirrors spotAssetDisplayName on GET /v1/spotAssets). Present only on spot-asset withdrawals.

Example:

"SPY-USD-SPOT"