> ## Documentation Index
> Fetch the complete documentation index at: https://developer.uphold.com/llms.txt
> Use this file to discover all available pages before exploring further.

# Crypto withdrawal via the REST API

> Send crypto withdrawals with the Uphold REST API: collect destination details, create a quote, handle requirements, and monitor for settlement.

This guide walks you through supporting crypto withdrawals using the REST API.

## Prerequisites

* The user has [completed onboarding](/developer-guides/user-onboarding/overview) and has the required capabilities enabled.

<Warning>
  If the destination network requires an **additional reference** (e.g., destination tag, memo), strongly encourage your users to provide it. Missing references often lead to loss of funds or lengthy recovery.
</Warning>

## Walkthrough

```mermaid theme={null}
sequenceDiagram
  autonumber
  participant Usr as User
  participant U as Your App
  participant B as Your Backend
  participant A as Uphold
  participant N as Blockchain Network

  Usr->>U: Start crypto withdrawal
  U->>B: List accounts
  B->>A: GET /core/accounts
  A-->>B: { accounts }
  B-->>U: { accounts }
  Usr->>U: Choose source account and amount
  U->>B: Request quote
  B->>A: Create quote
  A-->>B: { quote }
  B-->>U: { quote }
  Usr->>U: Confirm quote
  U->>B: Create transaction
  B->>A: Create transaction
  A-->>B: { transaction }
  A-->>B: webhook: transaction.created (processing)
  A->>N: Broadcast withdrawal
  N-->>A: Confirmations reached
  A-->>B: webhook: transaction.status-changed (completed/failed)
  B-->>Usr: Notify the user
```

## Check available rails

Call [List rails](/rest-apis/core-api/assets/list-rails) to confirm the asset and network the user wants to withdraw to, and verify the `withdraw` feature is enabled.

```http theme={null}
GET /core/rails?type=crypto&asset=BTC
```

A successful response lists all rails for the asset. Each rail includes a `features` array — only proceed with networks that include `"withdraw"`. For assets supported by multiple networks, require an explicit network selection from the user. Use the network's `reference` field to determine whether to show a destination tag or memo input in your UI.

```json theme={null}
{
  "rails": [
    {
      "type": "crypto",
      "network": "bitcoin",
      "method": "crypto-transaction",
      "asset": "BTC",
      "decimals": 8,
      "features": [
        "deposit",
        "withdraw"
      ]
    },
    {
      "type": "crypto",
      "network": "lightning",
      "method": "crypto-transaction",
      "asset": "BTC",
      "decimals": 8,
      "features": []
    }
  ]
}
```

## Select source account

Crypto withdrawals can be sourced from any account. If the selected account is not in the withdrawal asset, the balance will be converted at the time of the transaction using Uphold's prevailing rate. Make sure the origin asset has the necessary [features enabled](/rest-apis/core-api/assets/introduction#features-and-deposits-/-withdrawals).

Call [List accounts](/rest-apis/core-api/accounts/list-accounts) to retrieve the user's accounts and let them choose one with sufficient balance for the withdrawal.

```http theme={null}
GET /core/accounts?currency=BTC
```

```json theme={null}
{
  "accounts": [
    {
      "id": "a00507fe-628c-4f27-ae81-e1c40b2a8fb8",
      "ownerId": "e4ce04dc-67b7-4e9f-af91-482cb6f9fc4a",
      "label": "My BTC account",
      "asset": "BTC",
      "balance": {
        "total": "0.05",
        "available": "0.05"
      }
    }
  ]
}
```

## Select a destination

Crypto withdrawals support two ways of specifying the destination: a raw `crypto-address` (address entered on every transaction) or a saved `external-account` of type `crypto` (address registered once via [Create external account](/rest-apis/core-api/external-accounts/create-external-account), then reused across withdrawals).

<Tabs>
  <Tab title="Raw crypto address">
    Collect the destination `asset`, `network` and `address` from the user on each withdrawal.
  </Tab>

  <Tab title="Saved crypto external account">
    Call [List external accounts](/rest-apis/core-api/external-accounts/list-external-accounts) filtering for `type: "crypto"` to let the user pick a previously registered address.

    ```http theme={null}
    GET /core/external-accounts?type=crypto
    ```

    Present the accounts to the user and make sure the selected one has `status: "ok"` and `"withdraw"` in `features`.

    ```json theme={null}
    {
      "externalAccounts": [
        {
          "id": "d0686449-36c7-c62e-b81f-de844bbc79d4",
          "type": "crypto",
          "status": "ok",
          "label": "My XRP Address",
          "asset": "XRP",
          "network": "xrp-ledger",
          "features": [
            "withdraw"
          ],
          "details": {
            "address": "rN7n7otQDd6FczFgLdlqtyMVrn3WD6CJYy",
            "reference": "67809592"
          }
        }
      ]
    }
    ```
  </Tab>
</Tabs>

## Create a quote

Create a quote for the withdrawal using the [Create quote](/rest-apis/core-api/transactions/create-quote) endpoint.

<Tabs>
  <Tab title="Raw crypto address">
    ```http theme={null}
    POST /core/transactions/quote
    {
      "origin": {
        "type": "account",
        "id": "a00507fe-628c-4f27-ae81-e1c40b2a8fb8"
      },
      "destination": {
        "type": "crypto-address",
        "asset": "BTC",
        "network": "bitcoin",
        "address": "1A1zP1eP5QGefi2DMPTfTL5SLmv7DivfNa"
      },
      "denomination": {
        "asset": "GBP",
        "amount": "100.00",
        "target": "origin"
      }
    }
    ```

    A successful response returns a `quote` object with details about the withdrawal, including fees and expiration.

    ```json [expandable] theme={null}
    {
      "quote": {
        "id": "623000c8-9bdf-4a2b-aa3d-6a6b44a7f6a0",
        "origin": {
          "amount": "0.00121023",
          "asset": "BTC",
          "rate": "0.00002629253259492961",
          "node": {
            "type": "account",
            "id": "a00507fe-628c-4f27-ae81-e1c40b2a8fb8",
            "ownerId": "e4ce04dc-67b7-4e9f-af91-482cb6f9fc4a"
          }
        },
        "destination": {
          "amount": "0.00121023",
          "asset": "BTC",
          "rate": "1",
          "node": {
            "type": "crypto-address",
            "address": "1A1zP1eP5QGefi2DMPTfTL5SLmv7DivfNa",
            "network": "bitcoin",
            "execution": {
              "mode": "onchain"
            }
          }
        },
        "denomination": {
          "amount": "100.00",
          "asset": "GBP",
          "target": "origin",
          "rate": "0.00001210225938333485"
        },
        "fees": [],
        "expiresAt": "2024-07-24T15:22:39Z"
      }
    }
    ```
  </Tab>

  <Tab title="Saved crypto external account">
    Reference the external account by `id` instead of providing raw address details.

    ```http theme={null}
    POST /core/transactions/quote
    {
      "origin": {
        "type": "account",
        "id": "a00507fe-628c-4f27-ae81-e1c40b2a8fb8"
      },
      "destination": {
        "type": "external-account",
        "id": "d0686449-36c7-c62e-b81f-de844bbc79d4"
      },
      "denomination": {
        "asset": "GBP",
        "amount": "100.00",
        "target": "origin"
      }
    }
    ```

    The response's `destination.node` identifies the external account used, tagged with `via: "crypto"`.

    ```json [expandable] theme={null}
    {
      "quote": {
        "id": "623000c8-9bdf-4a2b-aa3d-6a6b44a7f6a0",
        "origin": {
          "amount": "0.00121023",
          "asset": "BTC",
          "rate": "0.00002629253259492961",
          "node": {
            "type": "account",
            "id": "a00507fe-628c-4f27-ae81-e1c40b2a8fb8",
            "ownerId": "e4ce04dc-67b7-4e9f-af91-482cb6f9fc4a"
          }
        },
        "destination": {
          "amount": "0.00121023",
          "asset": "BTC",
          "rate": "1",
          "node": {
            "type": "external-account",
            "via": "crypto",
            "id": "d0686449-36c7-c62e-b81f-de844bbc79d4",
            "ownerId": "e4ce04dc-67b7-4e9f-af91-482cb6f9fc4a",
            "network": "xrp-ledger"
          }
        },
        "denomination": {
          "amount": "100.00",
          "asset": "GBP",
          "target": "origin",
          "rate": "0.00001210225938333485"
        },
        "fees": [],
        "expiresAt": "2024-07-24T15:22:39Z"
      }
    }
    ```

    If the account's rail doesn't have the `withdraw` feature, or the network you request doesn't match the account's own network, the quote is rejected with `transaction_node_invalid`:

    ```json theme={null}
    {
      "code": "transaction_node_invalid",
      "message": "The destination external account lacks 'withdraw' feature"
    }
    ```

    ```json theme={null}
    {
      "code": "transaction_node_invalid",
      "message": "The destination external account does not support the specified network"
    }
    ```
  </Tab>
</Tabs>

<Info>Quotes typically **expire** quickly. Prompt for user confirmation within the expiry window and regenerate if needed.</Info>

## Handle quote requirements

When the quote is returned, check the `requirements` array. If non-empty, resolve each requirement before creating the transaction.

```json theme={null}
{
  "quote": {
    "id": "623000c8-9bdf-4a2b-aa3d-6a6b44a7f6a0",
    "requirements": [
      "travel-rule"
    ],
    "expiresAt": "2024-07-24T15:22:39Z"
  }
}
```

### Travel Rule

If the `requirements` array contains `travel-rule`, you must collect the required originator and beneficiary information before creating the transaction. For the full step-by-step implementation, see the [Travel Rule withdrawal](/developer-guides/travel-rule/withdrawal/via-api) guide.

## Create a transaction

Once the user confirms the quote, create the transaction using the [Create transaction](/rest-apis/core-api/transactions/create-transaction) endpoint.

```http theme={null}
POST /core/transactions
{
  "quoteId": "623000c8-9bdf-4a2b-aa3d-6a6b44a7f6a0"
}
```

In a successful crypto withdrawal, the origin is the source account and the destination is a `crypto-address` node reflecting the recipient's on-chain address.

```json [expandable] theme={null}
{
  "transaction": {
    "id": "223c24c5-76c6-4553-91bc-5af519441f03",
    "origin": {
      "amount": "0.00121023",
      "asset": "BTC",
      "rate": "0.00002629253259492961",
      "node": {
        "type": "account",
        "id": "a00507fe-628c-4f27-ae81-e1c40b2a8fb8",
        "ownerId": "e4ce04dc-67b7-4e9f-af91-482cb6f9fc4a"
      }
    },
    "destination": {
      "amount": "0.00121023",
      "asset": "BTC",
      "rate": "1",
      "node": {
        "type": "crypto-address",
        "address": "1A1zP1eP5QGefi2DMPTfTL5SLmv7DivfNa",
        "network": "bitcoin",
        "execution": {
          "mode": "onchain"
        }
      }
    },
    "fees": [],
    "status": "processing",
    "quotedAt": "2024-07-24T15:02:39Z",
    "createdAt": "2024-07-24T15:22:39Z",
    "updatedAt": "2024-07-24T15:22:39Z",
    "denomination": {
      "amount": "100.00",
      "asset": "GBP",
      "target": "origin",
      "rate": "0.00001210225938333485"
    }
  }
}
```

### Execution modes

Crypto withdrawals are executed in one of the following modes, indicated by `destination.node.execution.mode`:

<AccordionGroup>
  <Accordion title="On-chain" icon="link">
    The transaction is broadcast to the blockchain network. `destination.node.execution.mode` is `onchain`, and `destination.node.execution.transactionHash` is set once the transaction is completed.
  </Accordion>

  <Accordion title="Off-chain" icon="link-slash">
    When both the sender and recipient are Uphold users, the transfer is processed within Uphold's infrastructure instead of the blockchain. This avoids network fees and is faster than on-chain processing.

    Each side sees the transfer from its own perspective:

    | | Sender | Recipient |
    | - | - | - |
    | Seen as | Withdrawal | Deposit |
    | `origin.node.type` | `account` | `crypto-address` |
    | `destination.node.type` | `crypto-address` | `account` |

    When both users belong to the same organization, the execution also identifies the counterpart account: `destination.node.execution.accountOwnerId` and `destination.node.execution.accountId` for the sender, and `origin.node.execution.accountOwnerId` and `origin.node.execution.accountId` for the recipient.

    Because funds move between Uphold accounts, the assets may differ from an on-chain transfer:

    * **Sender:** `destination.asset` is the asset of the recipient account associated with the address, which may differ from the one in the quote.
    * **Recipient:** `origin.asset` is the asset of the sender's account, which may differ from the network's asset or even be one the network doesn't support, such as a fiat asset.
  </Accordion>

  <Accordion title="Simulated" icon="flask">
    Used in development environments for testing. The transaction appears processed without affecting blockchain state, although user balances are updated. `destination.node.execution.mode` is `simulated`.
  </Accordion>
</AccordionGroup>

<Info>If the destination was a saved `external-account`, `destination.node` keeps the same `{ type: "external-account", via: "crypto", id, ownerId, network }` shape shown in [Create a quote](#create-a-quote) above, with an `execution` object added once on-chain details are known.</Info>

## Monitor for settlement

Prefer **webhooks** for real-time updates, or fall back to **polling** if webhooks are not feasible.

* Webhook events (recommended):
  * [core.transaction.created](/rest-apis/core-api/transactions/webhooks/transaction-created)
    * `status: processing` → initiated but not yet broadcast
  * [core.transaction.status-changed](/rest-apis/core-api/transactions/webhooks/transaction-status-changed)
    * `status: completed` → broadcast and confirmed
    * `status: on-hold` → transaction checks paused (e.g., pending RFIs)
    * `status: failed` → transaction failed, check `statusDetails` for more info
* Polling (fallback): [Get transaction](/rest-apis/core-api/transactions/get-transaction)

## Notify the user

Display an in-app confirmation when the transaction is `completed`, and send an email if applicable.

<Check>You now support crypto withdrawals with the Enterprise API Suite.</Check>
