Skip to main content
Withdrawals are asynchronous. A withdrawal may involve unwinding yield positions, bridging across chains, and waiting for customer approval — all orchestrated automatically.

Withdrawal lifecycle

  1. Preview — check available balance and get a sourcing estimate.
  2. Initiate — create the withdrawal. The system reserves liquidity and begins sourcing.
  3. Track — poll or subscribe to webhooks as the withdrawal progresses.
  4. Complete — the withdrawal reaches completed when all payout legs are terminal.

Preview a withdrawal

curl -X POST "$BASE_URL/v2/portfolio-wallets/$WALLET_ID/withdrawal-preview" \
  -H "Authorization: Bearer $BRAID_API_TOKEN" \
  -H "Content-Type: application/json" \
  -d '{
    "destinationChain": "polygon",
    "destinationToken": "usdc",
    "withdrawalAmount": "65000"
  }'
{
  "amountUsd": "65000.00",
  "feeUsd": "0.00",
  "availableUsd": "82500.00",
  "estimatedCompletionTime": "PT2H"
}
FieldDescription
amountUsdRequested withdrawal amount
feeUsdFee charged (currently "0.00")
availableUsdUnreserved balance in the relevant bridge domain(s)
estimatedCompletionTimeBest-effort ISO 8601 duration for end-to-end completion

Initiate a withdrawal

curl -X POST "$BASE_URL/v2/portfolio-wallets/$WALLET_ID/withdraw" \
  -H "Authorization: Bearer $BRAID_API_TOKEN" \
  -H "Content-Type: application/json" \
  -d '{
    "requestId": "df8b7be6-e110-4f6d-9b2d-7c44a5b1f0b0",
    "chain": "ethereum",
    "token": "usdc",
    "withdrawalAmount": "65000",
    "destinationAddress": "0x742d35Cc6634C0532925a3b844Bc454e4438f44e"
  }'
FieldRequiredDescription
requestIdYesUUID v4 idempotency key
chainYesDestination chain
tokenYesDestination token
withdrawalAmountYesAmount as a string (e.g. "65000", "100.50")
destinationAddressYesRecipient address on the destination chain

Field naming

The preview endpoint uses destinationChain / destinationToken (read-only query describing where you’d send). The initiate endpoint uses chain / token (action parameters). This is intentional — the preview scopes availability; the initiate commits the withdrawal.

Amount limits

  • Must be a positive number.
  • Max 6 decimal places for USDC (e.g. "100.123456" is valid, "100.1234567" returns precision_overflow).
  • Cannot exceed the wallet’s unreserved balance. Use the preview endpoint to check availability.
  • There is no minimum withdrawal amount, but very small withdrawals may not be economically efficient due to gas and bridging costs.

Sandbox notes

  • chain: "ethereum" refers to Ethereum Sepolia in sandbox.
  • Solana is part of the CCTP unified bridge domain for sourcing liquidity, but direct Solana withdrawal destinations are not yet supported. The endpoint currently expects an EVM destination address.

Withdrawal response

{
  "id": "11b17950-1f5c-4d36-8f0d-0f3d1d0c6a45",
  "requestId": "df8b7be6-e110-4f6d-9b2d-7c44a5b1f0b0",
  "walletId": "9d1a1c83-3a1c-4c14-9c5a-0c9a57a4a7db",
  "amountUsd": "65000.00",
  "feeUsd": "0.00",
  "destinationChain": "ethereum",
  "destinationToken": "usdc",
  "destinationAddress": "0x742d35Cc6634C0532925a3b844Bc454e4438f44e",
  "status": "processing",
  "txHash": null,
  "failureReason": null,
  "payouts": [
    {
      "id": "0c7c6fcb-3c49-4f6d-9a8d-1d2b8d1ef7b0",
      "legKey": "pos_usdz_001",
      "status": "processing",
      "amount": "65000.00",
      "token": "usdc",
      "chain": "ethereum",
      "destinationAddress": "0x742d35Cc6634C0532925a3b844Bc454e4438f44e",
      "txHash": null
    }
  ],
  "sourceLegs": [
    {
      "id": "a1b2c3d4-e5f6-4a7b-8c9d-0e1f2a3b4c5d",
      "positionKey": "usdz",
      "sourceChain": "arbitrum",
      "requestedUsdcUnits": "35000000000",
      "status": "processing"
    },
    {
      "id": "b2c3d4e5-f6a7-4b8c-9d0e-1f2a3b4c5d6e",
      "positionKey": "cash_usdc:cctp",
      "sourceChain": "arbitrum",
      "requestedUsdcUnits": "30000000000",
      "status": "completed"
    }
  ],
  "createdAt": "2026-02-05T11:00:00.000Z",
  "updatedAt": "2026-02-05T11:05:00.000Z",
  "completedAt": null
}

Top-level fields

FieldDescription
idUnique withdrawal identifier
requestIdYour idempotency key
walletIdParent wallet ID
amountUsdWithdrawal amount as a USD string
feeUsdFee charged (currently "0.00")
destinationChainTarget chain
destinationTokenTarget token
statusCurrent status (see Withdrawal statuses)
txHashPrimary delivery tx hash (populated when payout is broadcast)
failureReasonMachine-readable reason when status is failed (see Failure reasons)
createdAt / updatedAtTimestamps
completedAtWhen the withdrawal reached terminal state (null if in progress)

payouts[] — delivery legs

Each withdrawal has one or more payout legs tracking chain-level delivery.
FieldDescription
idPayout leg identifier
legKeyUnique key in the format pos_{positionKey}_{attempt} (e.g. pos_usdz_001). The attempt suffix is zero-padded and increments on retry.
statusPayout leg status (see Payout leg statuses)
amountAmount as a USD string
tokenToken being delivered
chainChain for delivery
destinationAddressRecipient address
txHashOnchain tx hash (populated after broadcast)

sourceLegs[] — liquidity sourcing

Source legs show where funds are being unwound from to fulfill the withdrawal.
FieldDescription
idSource leg identifier
positionKeyWhich position is being sourced (e.g. usdz, cash_usdc:cctp)
sourceChainChain where the position is held
requestedUsdcUnitsAmount in USDC base units (6 decimals). "35000000000" = 35,000 USDC. Divide by 1,000,000 to get USDC.
statusSource leg status

Withdrawal statuses

StatusTerminalMeaning
pendingNoAccepted and reserved; sourcing hasn’t started
processingNoUnwinding, bridging, or delivering in progress
completedYesAll payout legs finished successfully
partially_completedYesSome payout legs completed, others failed or were cancelled
failedYesWithdrawal failed
cancelledYesWithdrawal was cancelled before any payouts completed

Payout leg statuses

StatusTerminalMeaning
planningNoLeg is being planned
waiting_for_prior_approvalNoBlocked behind a prior approval on the same wallet+chain
pending_customer_approvalNoTurnkey approval required
pending_broadcastNoSigned, waiting to broadcast
broadcastedNoBroadcast submitted, waiting for finality
processingNoIn progress
completedYesDelivered successfully
failedYesFailed
cancelledYesCancelled
skippedYesLeg not needed (e.g. no unwind required)

Failure reasons

When a withdrawal or payout leg fails, failureReason contains a machine-readable code:
ReasonMeaning
cancelledCancelled by system
cancelled_by_customerCancelled by customer request
usdc_authorization_expiredUSDC transfer authorization expired before broadcast
onchain_revertedEVM transaction reverted onchain
onchain_failedSolana transaction failed onchain
turnkey_activity_failedTurnkey signing activity failed
cash_leg_unfulfillable_stale_ledgerCash source insufficient (stale balance data)

Customer approvals (head-of-line gating)

Only one customer approval can be outstanding per walletId + chain. If you submit multiple withdrawals quickly, later ones queue behind the earlier approval. Withdrawal responses include:
  • customerApprovalState: "required" | "queued" | "not_required"
  • blockedBy: describes the blocking payout leg when queued
For the full Turnkey approval lifecycle, see Transaction Approvals.

List withdrawals

curl "$BASE_URL/v2/portfolio-wallets/$WALLET_ID/withdrawals?limit=25" \
  -H "Authorization: Bearer $BRAID_API_TOKEN"
ParamDefaultDescription
limit25Items per page (1–100)
cursorOpaque cursor from nextCursor
sortcreatedAtSort field
orderdescasc or desc
statusRepeatable filter (e.g. status=processing&status=completed)
{
  "data": [
    {
      "id": "11b17950-1f5c-4d36-8f0d-0f3d1d0c6a45",
      "requestId": "df8b7be6-e110-4f6d-9b2d-7c44a5b1f0b0",
      "walletId": "9d1a1c83-3a1c-4c14-9c5a-0c9a57a4a7db",
      "amountUsd": "65000.00",
      "feeUsd": "0.00",
      "destinationChain": "ethereum",
      "destinationToken": "usdc",
      "status": "completed",
      "createdAt": "2026-02-05T11:00:00.000Z",
      "completedAt": "2026-02-05T12:30:00.000Z"
    }
  ],
  "nextCursor": null
}

Fetch a withdrawal

curl "$BASE_URL/v2/portfolio-wallets/$WALLET_ID/withdrawals/$WITHDRAWAL_ID" \
  -H "Authorization: Bearer $BRAID_API_TOKEN"

Cancel a withdrawal

curl -X POST "$BASE_URL/v2/portfolio-wallets/$WALLET_ID/withdrawals/$WITHDRAWAL_ID/cancel" \
  -H "Authorization: Bearer $BRAID_API_TOKEN"
Cancellation is best-effort. If some payout legs have already completed, the withdrawal transitions to partially_completed rather than cancelled. You can also cancel or retry individual payout legs:
# Cancel a specific payout leg
curl -X POST "$BASE_URL/v2/portfolio-wallets/$WALLET_ID/withdrawals/$WITHDRAWAL_ID/payouts/$PAYOUT_ID/cancel" \
  -H "Authorization: Bearer $BRAID_API_TOKEN"

# Retry a failed payout leg
curl -X POST "$BASE_URL/v2/portfolio-wallets/$WALLET_ID/withdrawals/$WITHDRAWAL_ID/payouts/$PAYOUT_ID/retry" \
  -H "Authorization: Bearer $BRAID_API_TOKEN"

Timing expectations

PositionUnwind timeNotes
Cash (USDC)InstantNo unwind needed
usdzInstant (PT0H)Convert to USDC on Arbitrum
syrupUsdcInstant (PT0H)ERC-4626 redemption on Ethereum
rlpUp to 24h (PT24H)Async Resolv exit window
ustbUp to 48h (P2D)Superstate redemption window
Cross-chain delivery via CCTP adds time on top of the unwind. Typical CCTP bridging is ~1h (p50), up to ~3h (max). For per-position estimates to a specific destination, use POST /v2/portfolio-wallets/withdrawal-time-estimates — see Estimate withdrawal times.

Estimate withdrawal times

Estimate end-to-end withdrawal times for a set of positions and destination:
curl -X POST "$BASE_URL/v2/portfolio-wallets/withdrawal-time-estimates" \
  -H "Authorization: Bearer $BRAID_API_TOKEN" \
  -H "Content-Type: application/json" \
  -d '{
    "destinationToken": "usdc",
    "destinationChain": "ethereum",
    "positions": [
      { "positionKey": "usdz", "targetWeightBps": 6000 },
      { "positionKey": "rlp", "targetWeightBps": 4000 }
    ]
  }'
FieldRequiredDescription
destinationTokenYesDestination token (only usdc today)
destinationChainYesethereum, arbitrum, base, polygon, or optimism
positionsYesPositions array (same shape as wallet creation)
Each estimate breaks the withdrawal into steps (unwind_liquidity, convert_to_usdc, bridge_cctp, payout_transfer_and_finality) with p50, p90, and max durations. fullSettlementTime aggregates across steps. "p90": "PT1H30M" means “90th percentile is about 1 hour 30 minutes”. These are best-effort estimates, not an SLA.

Withdrawal webhooks

Subscribe to these events for real-time tracking:
  • portfolio_wallet.withdrawal.status_changed
  • portfolio_wallet.withdrawal.payout.status_changed
See Webhooks for registration and payload details.

Bridge domains reference

A bridge domain is a hard boundary for where liquidity can come from. The withdrawal engine never crosses bridge domain boundaries.

USDC unified (CCTP)

USDC on CCTP-supported chains forms a single usdc:unified domain: Ethereum, Arbitrum, Base, Polygon, Optimism, Solana. A withdrawal to any of these chains can source USDC from any other chain in the domain (via CCTP bridging).

BSC (always isolated)

BSC USDC is isolated: usdc:bsc. Cannot be bridged to or from CCTP chains.

How cash fits in

Within a bridge domain, cash is always the first source of liquidity. Only if cash is insufficient will the system unwind yield positions. The sourceLegs[] array shows the sourcing order.

Next steps