> For the complete documentation index, see [llms.txt](https://titan-exchange.gitbook.io/titan/llms.txt). Markdown versions of documentation pages are available by appending `.md` to page URLs; this page is available as [Markdown](https://titan-exchange.gitbook.io/titan/developer-doc/special-order-types/guides/withdrawals.md).

# Withdrawals

Return funds from a user's manager to their external wallet — wallet-level and per-order.

Funds in a user's manager always come back to that user's own external wallet — the manager's policy allows nothing else. There are two ways to move them, both following the same intent → sign → confirm pattern as order creation.

## Wallet-level withdrawals

These pull a chosen token out of the manager regardless of any order. Use them to power a "withdraw available balance" action.

Check what's available first:

```typescript
const { data } = await callTitanDca('/me/balance?hideZero=true', { sub });
// each balance row: totalBalance, lockedForFutureTxns, withdrawalPending, availableToWithdraw
```

`availableToWithdraw` is `max(total − locked − withdrawalPending, 0)` — the source of truth for a "withdraw max" button. Active orders lock their unspent input, so it won't be available until the order ends: for a DCA order or a running Slice Order that's `totalAmount − amountSpent`, and for a trigger order it's the full unspent `amount` while it's armed. Output mints are not locked, with one exception: tokens bought by a [conditional entry](/titan/developer-doc/special-order-types/order-types/otoco.md) stay locked from the moment its swap is sent until they become the bracket's `amount`.

`lockedBreakdown` on each row attributes the lock to specific orders, with `orderType` being any of `dca`, `stop_loss`, `take_profit`, `oco`, or `slice` — enough to tell the user *which* order is holding their balance.

Build the withdrawal tx, omitting `amount` to withdraw the max:

```typescript
const intent = await callTitanDca('/withdraw/transaction', {
  method: 'POST',
  sub,
  body: {
    userPubkey,                                            // fee payer & destination
    tokenMint: 'EPjFWdd5AufqSSqeM2qN1xzybapC8G4wEGGkZwyTDt1v',
    amount: '400000',                                      // optional; omit for max
  },
});

// user signs intent.data.transaction, then:
await callTitanDca('/withdraw/confirm', {
  method: 'POST', sub,
  body: { signedTransaction },
});
```

Titan re-runs the lock-aware check at confirm time using the `(mint, amount)` from the unsigned transaction it returned — so if that transaction goes stale during its 5-minute window (e.g. another withdrawal landed in the meantime), it's rejected before broadcast rather than overdrawing.

| HTTP | `error.code`             | When                                                                                                             |
| ---- | ------------------------ | ---------------------------------------------------------------------------------------------------------------- |
| 400  | `INSUFFICIENT_AVAILABLE` | The whole balance is locked, or the wallet is empty.                                                             |
| 400  | `FUNDS_LOCKED`           | `requested > available` — some balance is locked by orders or a pending withdrawal. `details` has the breakdown. |
| 400  | `INSUFFICIENT_BALANCE`   | `requested > chainBalance` — the wallet genuinely doesn't hold that much.                                        |
| 409  | `ONBOARDING_INCOMPLETE`  | Manager not fully provisioned. Re-call onboard, then retry.                                                      |

## Order-level withdrawals

These return the funds tied to a single terminal order (`completed` / `cancelled` / `failed` / `expired`) back to the external wallet.

{% hint style="info" %}
**`failed` orders — and `expired` trigger orders — return automatically.** Titan sends their unspent input back without any call from you (see [Lifecycle](/titan/developer-doc/special-order-types/guides/lifecycle.md#automatic-input-return)), so calling withdraw on one usually reports `NOTHING_TO_WITHDRAW` or `ALREADY_WITHDRAWN`. Order-level withdrawal is for `completed` and `cancelled` orders — plus `expired` **DCA** orders, which are the one expiry case that isn't auto-returned.
{% endhint %}

A `completed` trigger order or Slice Order is also a no-op here: its fills consumed the whole deposit and the output was delivered at execution time — or belongs to a bracket order — so it returns `400 NOTHING_TO_WITHDRAW`. For trigger orders, order-level withdrawal really only matters after a cancel. Slice Orders cannot be cancelled, so it matters only in the brief window before a failed one is auto-returned.

To exit a bracket early, cancel the bracket order with `withdraw: true` — its withdrawal returns the bought tokens. See [Conditional Entry & Bracket](/titan/developer-doc/special-order-types/order-types/otoco.md#lifecycle).

```typescript
const intent = await callTitanDca(`/orders/${orderId}/withdraw`, { method: 'POST', sub });
// intent.data.withdrawalAmounts → [{ mint, amount }, …] — preview before the user signs

await callTitanDca(`/orders/${orderId}/withdraw/confirm`, {
  method: 'POST', sub,
  body: { signedTransaction },
});
```

`POST /orders/{orderId}/withdraw` is safe to retry — each call rebuilds a fresh tx (so the user can re-prompt their wallet) and resets the inactivity timer on the automatic recovery process. `withdrawalAmounts` enumerates the per-mint amounts the transaction will move, one entry per non-zero mint, which is what you show the user before they sign.

| HTTP | `error.code`          | When                                                                                                                 |
| ---- | --------------------- | -------------------------------------------------------------------------------------------------------------------- |
| 400  | `NOTHING_TO_WITHDRAW` | No order-owned funds remain (the user likely drained them via a wallet-level withdrawal).                            |
| 400  | `INVALID_STATE`       | Order isn't `completed` / `cancelled` / `failed` / `expired`.                                                        |
| 400  | `ALREADY_WITHDRAWN`   | A prior withdrawal already completed — including the automatic return on a failed order or an expired trigger order. |
| 409  | `EXECUTION_IN_FLIGHT` | A swap attempt is still being reconciled. Self-resolving — retry shortly.                                            |
| 503  | `RPC_UNAVAILABLE`     | Couldn't fetch on-chain balance. Retry shortly.                                                                      |

### Releasing a stuck withdrawal lock

If the user dismisses the wallet prompt from `POST /orders/{orderId}/withdraw`, the order sits in `withdrawalStatus: pending`. Call `POST /orders/{orderId}/withdraw/abandon` to make the "Withdraw" button usable again immediately — it's idempotent. Without it the lock clears on its own, but not quickly — expect up to **8 minutes**. That's long enough for a user to notice, which is why abandon exists.

| HTTP | `error.code`           | When                                                   |
| ---- | ---------------------- | ------------------------------------------------------ |
| 400  | `ALREADY_WITHDRAWN`    | The withdrawal already completed — nothing to abandon. |
| 404  | `NOT_FOUND`            | Order missing or not yours.                            |
| 409  | `WITHDRAWAL_IN_FLIGHT` | Lost the race to `/withdraw/confirm`. Retry briefly.   |

## Related pages

* [Managing Orders](/titan/developer-doc/special-order-types/guides/managing-orders.md) — cancel-with-withdraw builds an order-level withdrawal in one call
* [Lifecycle & Polling](/titan/developer-doc/special-order-types/guides/lifecycle.md) — `withdrawalStatus` transitions and automatic input return
* [Error Codes](/titan/developer-doc/special-order-types/reference/error-codes.md) — the full withdrawal error catalog
