> ## 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.

# ACH bank withdrawal via the REST API

> Send USD ACH bank withdrawals with the Uphold REST API: create an external bank account, generate a quote, submit the payout, and monitor settlement.

This guide walks you through the steps to support ACH bank withdrawals using the REST API — from creating an external account to monitoring for settlement.

## Prerequisites

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

## 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 F as Bank

  Usr->>U: Start withdrawal
  U->>B: Find bank account
  B->>A: GET /core/external-accounts
  A-->>B: { externalAccounts }
  B-->>U: { externalAccounts }
  Usr->>U: Select or link bank account
  opt No existing bank account
    U->>B: Link bank account
    B->>A: Create external account
    A-->>B: { externalAccount }
  end
  Usr->>U: Choose source account and amount
  U->>B: Request quote
  B->>A: Create quote (account -> external account)
  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->>F: Submit bank transfer
  F-->>A: Settlement confirmed
  A-->>B: webhook: transaction.status-changed (completed/failed)
  B-->>Usr: Notify the user
```

***

## Check available rails

Before creating a quote, verify the ACH rail is available for withdrawals. Call [List Rails](/rest-apis/core-api/assets/list-rails) for `USD` to confirm the `ach` network has the `withdraw` feature.

```http theme={null}
GET /core/rails?type=bank&network=ach&asset=USD
```

```json theme={null}
{
  "rails": [
    {
      "type": "bank",
      "network": "ach",
      "method": "bank-transfer",
      "asset": "USD",
      "decimals": 2,
      "features": [
        "deposit",
        "withdraw"
      ]
    }
  ]
}
```

## Select source account

ACH withdrawals can be sourced from any account. If the selected account is not in USD, 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 pick the one to withdraw from.

```http theme={null}
GET /core/accounts
```

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

## Select a bank account

ACH bank accounts are not linked automatically. Users must provide their routing and account number to add one. Select an existing linked account or add a new one.

### Find an existing bank account

Call [List external accounts](/rest-apis/core-api/external-accounts/list-external-accounts) and filter for accounts with `type: "bank"`. If no USD bank account exists, proceed to link one.

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

Make sure the selected account has `status: "ok"` and `"withdraw"` in `features`.

```json theme={null}
{
  "externalAccounts": [
    {
      "id": "bb7f7fab-9e84-5a8e-9389-1458f56ac79",
      "type": "bank",
      "status": "ok",
      "network": "ach",
      "label": "My USD Checking Account",
      "features": [
        "withdraw"
      ],
      "secondaryNetworks": ["fednow"]
    }
  ]
}
```

### Link a new bank account

Create a USD bank account by calling [Create external account](/rest-apis/core-api/external-accounts/create-external-account) with the user's bank details.

```http theme={null}
POST /core/external-accounts
{
  "type": "bank",
  "asset": "USD",
  "network": "ach",
  "label": "My USD Checking Account",
  "details": {
    "routingNumber": "121000248",
    "accountNumber": "9876543210",
    "accountType": "checking",
    "address": {
      "country": "US",
      "subdivision": "US-CA",
      "city": "San Francisco",
      "line1": "123 Main Street",
      "postalCode": "94102"
    }
  }
}
```

```json theme={null}
{
  "externalAccount": {
    "id": "bb7f7fab-9e84-5a8e-9389-1458f56ac79",
    "type": "bank",
    "status": "ok",
    "network": "ach",
    "label": "My USD Checking Account",
    "features": [
      "withdraw"
    ],
    "secondaryNetworks": ["fednow"]
  }
}
```

<Note>The `secondaryNetworks` field is present only when the destination bank supports FedNow. If it appears, you may use `"fednow"` as the `network` in the quote to settle funds faster.</Note>

## Create a quote

Create a quote with [Create quote](/rest-apis/core-api/transactions/create-quote). Pass `network: "ach"` on the destination node to route the transfer via ACH, or leave it out to route via the external account's default network. If the external account has `secondaryNetworks`, the user can choose which one to use for the transfer. The quote must be created with the selected network to ensure accurate fees and expiration time.

```http theme={null}
POST /core/transactions/quote
{
  "origin": {
    "type": "account",
    "id": "a00507fe-628c-4f27-ae81-e1c40b2a8fb8"
  },
  "destination": {
    "type": "external-account",
    "id": "bb7f7fab-9e84-5a8e-9389-1458f56ac79",
    "network": "ach"
  },
  "denomination": {
    "asset": "USD",
    "amount": "500.00",
    "target": "origin"
  }
}
```

A successful response returns a `quote` object with fees and expiration.

```json [expandable] theme={null}
{
  "quote": {
    "id": "734111d9-ace0-5b3c-bb4e-7b7b55b8a7b1",
    "origin": {
      "amount": "500.00",
      "asset": "USD",
      "node": {
        "type": "account",
        "id": "a00507fe-628c-4f27-ae81-e1c40b2a8fb8",
        "ownerId": "e4ce04dc-67b7-4e9f-af91-482cb6f9fc4a"
      },
      "rate": "1"
    },
    "destination": {
      "amount": "500.00",
      "asset": "USD",
      "node": {
        "type": "external-account",
        "id": "bb7f7fab-9e84-5a8e-9389-1458f56ac79",
        "ownerId": "e4ce04dc-67b7-4e9f-af91-482cb6f9fc4a",
        "network": "ach"
      },
      "rate": "1"
    },
    "denomination": {
      "asset": "USD",
      "amount": "500.00",
      "target": "origin",
      "rate": "1"
    },
    "fees": [],
    "expiresAt": "2024-07-24T15:22:39Z"
  }
}
```

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

## Confirm and create transaction

After the user confirms the withdrawal, call [Create transaction](/rest-apis/core-api/transactions/create-transaction) with the quote ID to execute the transfer.

```http theme={null}
POST /core/transactions
{
  "quoteId": "734111d9-ace0-5b3c-bb4e-7b7b55b8a7b1"
}
```

In a successful ACH withdrawal, the origin is the user's `account` and the destination is the `external-account` representing the user's bank. The transaction status is initially `processing` and updates to `completed` once the transfer settles.

```json [expandable] theme={null}
{
  "transaction": {
    "id": "e4f5a6b7-2c3d-4e6f-a09b-8c7d6e5f4a3c",
    "origin": {
      "asset": "USD",
      "amount": "500.00",
      "node": {
        "type": "account",
        "id": "a00507fe-628c-4f27-ae81-e1c40b2a8fb8",
        "ownerId": "e4ce04dc-67b7-4e9f-af91-482cb6f9fc4a"
      }
    },
    "destination": {
      "asset": "USD",
      "amount": "500.00",
      "node": {
        "type": "external-account",
        "id": "bb7f7fab-9e84-5a8e-9389-1458f56ac79",
        "ownerId": "e4ce04dc-67b7-4e9f-af91-482cb6f9fc4a",
        "network": "ach"
      }
    },
    "status": "processing",
    "quotedAt": "2025-01-10T14:22:15Z",
    "createdAt": "2025-01-10T14:22:45Z",
    "updatedAt": "2025-01-10T14:22:45Z",
    "denomination": {
      "asset": "USD",
      "amount": "500.00",
      "target": "origin"
    }
  }
}
```

## 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` → payout submitted to the bank network
  * [core.transaction.status-changed](/rest-apis/core-api/transactions/webhooks/transaction-status-changed)
    * `status: completed` → funds delivered to the user's bank
    * `status: failed` → irrecoverable error
* 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 ACH bank transfer withdrawals via the REST API.</Check>
