---
title: "Initiate a withdrawal from a tenant to an external destination address"
method: POST
path: "/v1/domains/{domainId}/omnibus/{omnibusId}/tenants/{tenantId}/withdrawals"
tags: ["Withdrawals"]
---

# Initiate a withdrawal from a tenant to an external destination address

`POST /v1/domains/{domainId}/omnibus/{omnibusId}/tenants/{tenantId}/withdrawals`

## Request

- Base URL: `https://docs.ripple.com` (the document declares no server; this is the origin it was published from)
- URL: `https://docs.ripple.com/v1/domains/{domainId}/omnibus/{omnibusId}/tenants/{tenantId}/withdrawals`
- Auth: none declared. Required headers: `Idempotency-Key`

Validates balance, reserves funds, dry-runs a CreateTransactionOrder intent on Custody, and returns an unsigned intent payload. The frontend signs the payload and submits it directly to Custody POST /v1/intents. The backend tracks confirmation asynchronously via a Temporal workflow keyed by the generated transactionOrderId.

## Path parameters

- `domainId` string, uuid, required
- `omnibusId` string, uuid, required
- `tenantId` string, uuid, required

## Headers

- `Idempotency-Key` string, required

## Request body

- OmnibusCreateWithdrawalRequest
  - `amount` string, required: Positive integer in the ticker's smallest unit (e.g., wei for ETH, satoshis for BTC, drops for XRP). No leading zeros; zero is not allowed.
  - `tickerId` string, uuid, required: Identifier of the ticker to withdraw
  - `ledgerId` string, required: Identifier of the blockchain ledger to broadcast on
  - `destAddress` string, required: External destination address. Must be outside this omnibus.

## Response `201`

Withdrawal initiated or idempotent replay. Fresh create: reservation taken and unsigned intent returned. Pending replay (same key, still in progress): original operation and its unsigned intent returned, no new reservation. Terminal replay (operation already COMPLETED/FAILED/CANCELLED): operation status only, no unsigned intent and no reservation.

- OmnibusCreateWithdrawalResponse: unsignedIntent is present on a fresh create and on an idempotent replay of a still-pending withdrawal; it is omitted when replaying a terminal (COMPLETED/FAILED/CANCELLED) withdrawal, where there is nothing left to sign.
  - `withdrawal` OmnibusWithdrawalResponse, required
    - `operationId` string, uuid, required: Operation ID for the withdrawal
    - `transferId` string, uuid, required: Transfer ID for the on-chain movement
    - `status` 'PENDING' | 'IN_PROGRESS' | 'COMPLETED' | 'FAILED' | 'CANCELLED', required
    - `amount` string, required: Positive integer in the ticker's smallest unit
    - `tickerId` string, uuid, required
    - `ledgerId` string, required
    - `destAddress` string, required: External destination address
    - `estimatedFee` string: Estimated on-chain fee captured at dry-run time
    - `custodyTransactionOrderId` string, uuid: Custody TransactionOrder ID generated for this withdrawal
    - `createdAt` string, date-time, required: Timestamp of the underlying operation row creation
    - `updatedAt` string, date-time, required: Timestamp of the last status change on the underlying operation
  - `unsignedIntent` OmnibusUnsignedIntentPayload: Complete Propose object ready to be signed and submitted to Custody via POST /v1/intents. The frontend signs this object and sends it as the "request" field in ProposeIntentBody.
    - `type` 'Propose', required: Intent type, always "Propose"
    - `id` string, uuid, required: Unique identifier for the intent
    - `author` OmnibusUnsignedIntentPayloadAuthor, required: The user proposing the intent
      - `domainId` string, uuid, required
      - `id` string, uuid, required
    - `expiryAt` string, date-time, required: Expiry time for the intent
    - `targetDomainId` string, uuid, required: Domain where the intent will be executed
    - `payload` unknown, required
    - `description` string: Description of the intent
    - `customProperties` object, required: Intent-level custom properties

## Other responses

- `400`: Missing or blank Idempotency-Key, intent dry-run validation failed, or invalid request
- `404`: Domain, omnibus, or tenant not found
- `409`: Omnibus is not operational (ACTIVE and UNLOCKED) or tenant is not UNLOCKED, or insufficient available balance

---

[API](https://skmtc.dev/ripple/apis/ripple-custody-api.md) - [All operations](https://skmtc.dev/ripple/apis/ripple-custody-api/llms.txt) - [OpenAPI document](https://skmtc.dev/ripple/apis/ripple-custody-api/revisions/c6f0ad6b70c0?raw)
