POST /withdrawals
Submit a withdrawal
Submit a withdrawal.
operationId: withdraw · CCXT method: withdraw
Declared server:
https://api.testnet.nexus.xyz/v1. See Networks.
Authentication: a wallet signature carried in the request body. API-key headers and session tokens are ignored. The description below says what to sign. An unsigned call gets 401.
Withdraws USDX collateral from the venue back to the signing wallet.
The signature is the authentication, not an API key. This operation takes no X-API-Key and no session token: the caller signs an EIP-712 WithdrawIntent with the wallet's own key and the server recovers the signer from signature. The recovered address must equal wallet, or the request is refused with 401 SIGNER_MISMATCH. security is omitted rather than [] for that reason — the operation is not public, it is authorized by something this document has no scheme for, the same way POST /agents/register expresses it.
EIP-712 domain: { name: 'Nexus Exchange', version: '1', chainId: 20056 }. Unlike POST /agents/register, the chain id is not a request field here: the server always verifies against 20056, so a signature produced under any other domain simply does not recover wallet and is refused with 401 SIGNATURE_INVALID. Sign with that value; do not substitute a network chain id. Typed data type: WithdrawIntent { uint256 amount, address asset, uint64 nonce }, where asset is the USDX sentinel address 0xcccccccccccccccccccccccccccccccccccccccc. The venue is single-collateral and ignores the asset; the address exists only so the typed data has a stable binding.
The destination is implicit. Funds are credited to the signing wallet; the typed data carries no destination, so there is nothing to redirect. destination may be sent for clarity but must equal wallet — any other value is refused with 400 destination_locked.
nonce is single-use per wallet. Re-submitting an already-accepted (wallet, nonce) pair is refused with 401 NONCE_REPLAY and creates no second withdrawal, so a client that retries on an ambiguous network error cannot double-withdraw. This operation is not idempotent: a replay is rejected rather than answered with the original result.
amount is a whole smallest-unit count. It is a decimal string on the wire, but WithdrawIntent commits to a uint256, so a fractional value cannot be represented and is refused with 400 fractional_amount rather than rounded.
Exposure must be closed first. A withdrawal that would leave an open position under-collateralized is refused with 400 WITHDRAW_BLOCKED_BY_OPEN_POSITION; close positions and cancel resting orders before withdrawing.
Acceptance is not settlement. A 200 means the collateral was debited and the withdrawal was recorded with status pending; it does not mean funds have moved on-chain. Poll GET /withdrawals for the lifecycle status and for tx_hash, which stays null until the withdrawal is submitted on-chain.
A signed WithdrawIntent. The server recovers the signer from signature and credits that wallet; nothing here selects a destination.
Withdrawing wallet address (0x-prefixed, 20 bytes). Must equal the address recovered from signature.
Arbitrary-precision decimal serialized as a string (lossless). Parse with a decimal type, never a float.
Asset symbol; defaults to USDX. Recorded on the withdrawal record and not forwarded to the engine. The venue is single-collateral, so the signed asset address is always the USDX sentinel.
USDXSingle-use per-wallet nonce, included in the signed typed data. The current Unix timestamp in milliseconds is a safe value. Reusing an accepted nonce returns 401 NONCE_REPLAY and creates no second withdrawal.
EIP-712 signature over WithdrawIntent{amount, asset, nonce} from the wallet private key (0x-prefixed, 65 bytes, canonical low-S).
Optional, and locked to wallet. The signed intent carries no destination, so any other value is refused with 400 destination_locked. Omit it unless you are asserting the destination explicitly.
Withdrawal accepted and collateral debited. The withdrawal is recorded with status pending; this is an acceptance, not an on-chain settlement.
Withdrawal acceptance. Carries the authoritative post-withdrawal collateral balance from the engine, plus the identity of the ledger entry the acceptance created. The withdrawal is recorded with status pending and is also readable through GET /withdrawals; this response is not proof of on-chain settlement.
ENG-13528 added id, amount, status and timestamp. Before that this object was balance alone, so a caller could not follow its own withdrawal — it had to re-read GET /withdrawals and guess which row was its own. txid is deliberately absent: no on-chain transaction exists yet at acceptance, and it appears on the GET /withdrawals entry once the transfer is submitted.
Authoritative post-withdrawal collateral balance, as the engine reported it. null when the engine accepted the withdrawal but its body could not be read — the acceptance still stands, and the identity fields below are still served, so an unreadable balance is never reported as a fabricated number. Nullable in the schema and not only in this sentence: a generated client typed on a bare string would reject a legal response.
Identifier of the FundsEntry this acceptance recorded. The same id the entry carries on GET /withdrawals.
Arbitrary-precision decimal serialized as a string (lossless). Parse with a decimal type, never a float.
Status the entry was recorded with. Always pending at acceptance; the other values appear only on GET /withdrawals after an operator transition.
Unix epoch timestamp in milliseconds.
The withdrawal was refused before or by the engine. Branch on code:
WITHDRAW_BLOCKED_BY_OPEN_POSITION— the account has open exposure; close it first.INSUFFICIENT_BALANCE— the requested amount exceeds withdrawable collateral.INVALID_AMOUNT—amountis absent, non-numeric, or not greater than zero.fractional_amount—amounthas a fractional part;WithdrawIntentcarries auint256smallest-unit count.destination_locked—destinationwas supplied and does not equalwallet.BAD_WALLET—walletis not a 0x-prefixed 20-byte address.INVALID_JSON/BAD_REQUEST— the body could not be read or parsed.
An engine rejection with no dedicated classification is forwarded unchanged; it carries the same value under both error and code.
The signature did not authorize this withdrawal. Branch on code:
NONCE_REPLAY— this(wallet, nonce)pair was already accepted. No second withdrawal was created; sign a fresh nonce.SIGNER_MISMATCH— the address recovered fromsignatureis notwallet. The response echoes theclaimedandrecoveredaddresses.SIGNATURE_INVALID—signatureis malformed, not 65 bytes, non-canonical (high-S), or recovered no key.STORE_ERROR— the nonce could not be recorded, so the withdrawal was not attempted.
Withdrawals are refused for this wallet or this deployment. All three cases are decided before any signature work, so no nonce is consumed and a retry with the same signed intent is safe once the condition clears. Branch on code, or on error for the freeze envelope:
WITHDRAWALS_DISABLED(code) — the deployment-wide withdrawal kill switch is off.withdrawals_frozen(error) — the exchange is frozen; balances remain tradeable but new outflows are suspended.EARLY_ACCESS_REQUIRED(code) — the wallet is not on the early-access allowlist on a deployment that enforces one.
Jurisdiction controls do not appear here: withdrawal paths are exempt from them by design, because a restriction that traps funds is worse than no restriction.
Rate limit exceeded. Retryable — honour retry-after and back off; this is what separates a 429 from the permanent 403 jurisdiction refusals. Branch on the body's code, which is always RATE_LIMIT_EXCEEDED, and on bucket to learn WHICH budget bottlenecked — the buckets are independent, so a caller at its read ceiling can still submit orders and should not back off the whole client. Do not parse message: it is a diagnostic whose wording is not stable. Requests are charged by weight, so exhausting a budget takes fewer heavy calls than x-ratelimit-limit suggests. See “Rate limits” in the API description.
POST /v1/withdrawals HTTP/1.1
Host: api.testnet.nexus.xyz
Content-Type: application/json
Accept: */*
Content-Length: 135
{
"wallet": "0xAbCdEf0123456789AbCdEf0123456789AbCdEf01",
"amount": "500",
"asset": "USDX",
"nonce": 1779225381434,
"signature": "0xdeadbeef..."
}{
"balance": "9500.00",
"id": 42,
"amount": "500.00",
"status": "pending",
"timestamp": 1779225381434
}API version 0.9.131
Last updated

