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

> ## Agent Instructions
> Money is always a decimal string at the currency's precision (for example "29.00"), paired with an ISO 4217 currency field. Never use minor units.
> Build against the sandbox first: base URL https://sandbox-api.bachs.io with sk_sandbox_ keys. Production is https://api.bachs.io with sk_live_ keys; going live is a key swap.
> Treat webhooks (for example collection.succeeded) as the source of truth for fulfilment, never client-side events or redirects.
> Subscriptions are created by completing a checkout for a recurring product. There is no direct create-subscription endpoint.
> IDs carry resource prefixes (cust_, prod_, sub_, chk_, inv_, ref_) and timestamps are ISO 8601 UTC.

# Withdraw to your bank abroad

> Move money from your Bachs balance to your own bank account in the US, UK, Europe, Canada, Kenya, Ghana, Uganda or Tanzania.

In this guide you save a bank account outside Nigeria as a payout destination, then withdraw your balance to it. By the end you will have a saved destination you can reuse for future withdrawals, a withdrawal on its way to your bank, and a webhook that tells you when the money has arrived.

Multi-currency withdrawals are available to all Bachs users. You can save multiple bank accounts belonging to you and choose a destination for each withdrawal. The API calls withdrawals **payouts**, so its endpoints and webhook events keep that name.

***

## What you can withdraw to

| Currency | Country | Withdrawal method (`scheme`) | Details you send | Destination approval in live mode |
| - | - | - | - | - |
| `USD` | United States | `ach`, `wire` or `rtp` | Routing number, account number, bank address | Straight away |
| `GBP` | United Kingdom | Faster Payments (no `scheme` needed) | Sort code, account number | After review |
| `EUR` | Eurozone (SEPA) | SEPA (no `scheme` needed) | IBAN, SWIFT/BIC | After review |
| `CAD` | Canada | `eft`, `interac_account` or `interac_email` | Institution, transit and account number, or an Interac email | After review |
| `KES`, `GHS`, `UGX`, `TZS` | Kenya, Ghana, Uganda, Tanzania | Local bank transfer | Bank code, account number | After review |

International SWIFT transfers are not offered. This guide covers bank accounts outside Nigeria; see [Withdraw using the API](/guides/payouts/payout-using-api) for your Nigerian bank account. Other currencies are not supported for bank withdrawals.

***

## Prerequisites

* An API key with `payouts:write` to create destinations and withdrawals, and `payouts:read` to retrieve their status. See [Authentication Overview](/authentication).
* Your organization enabled for payouts. Without it, payout requests return `PAYOUTS_NOT_ENABLED`.
* Available USD funds sufficient for the withdrawal and fee. These bank routes use your USD balance; an NGN balance cannot fund them. Check [Get Balances](/api-reference/accounts/get-balances). USD-to-USD needs no conversion quote; another destination currency needs a quote (step 3).
* Complete account identity information, including the account name, contact email and address. This is separate from the bank branch address below.

<Note>
  Sandbox payouts are simulated. They move through the same statuses as live payouts without moving real money, so you can build the whole flow against `https://sandbox-api.bachs.io` first. Sandbox destinations are automatically approved; this does not establish live approval or access. The requests and responses below are illustrative examples, not recorded API results. Replace the sample bank details, identifiers and amounts with your own.
</Note>

***

## Steps

<Steps>
  <Step title="Save your bank account as a destination">
    Call [Create Destination](/api-reference/payouts/create-payout-destination) with the currency and your bank details. The fields depend on the currency. Pick your currency below.

    `account_name` is the name on the bank account. We send it to your bank exactly as you type it, and we cannot look it up for these banks. If it does not match the name your bank holds, the bank can refuse the payment and return it.

    <CodeGroup>
      ```bash USD (ACH) theme={"dark"}
      curl -X POST https://sandbox-api.bachs.io/v1/payouts/destinations \
        -H "Authorization: Bearer sk_sandbox_abc123xyz..." \
        -H "Content-Type: application/json" \
        -d '{
          "currency": "USD",
          "name": "Operating account",
          "scheme": "ach",
          "account_name": "Ada Okafor Ventures LLC",
          "bank_name": "Bank of America",
          "routing_number": "026009593",
          "account_number": "3010001234567",
          "bank_address": {
            "line1": "100 North Tryon Street",
            "city": "Charlotte",
            "state": "NC",
            "postal_code": "28255",
            "country": "US"
          }
        }'
      ```

      ```bash GBP theme={"dark"}
      curl -X POST https://sandbox-api.bachs.io/v1/payouts/destinations \
        -H "Authorization: Bearer sk_sandbox_abc123xyz..." \
        -H "Content-Type: application/json" \
        -d '{
          "currency": "GBP",
          "name": "UK account",
          "account_name": "Ada Okafor Ventures Ltd",
          "bank_name": "Barclays",
          "sort_code": "20-00-00",
          "account_number": "55779911"
        }'
      ```

      ```bash EUR theme={"dark"}
      curl -X POST https://sandbox-api.bachs.io/v1/payouts/destinations \
        -H "Authorization: Bearer sk_sandbox_abc123xyz..." \
        -H "Content-Type: application/json" \
        -d '{
          "currency": "EUR",
          "name": "Euro account",
          "account_name": "Ada Okafor Ventures GmbH",
          "bank_name": "Deutsche Bank",
          "iban": "DE89 3704 0044 0532 0130 00",
          "swift_bic": "DEUTDEFF"
        }'
      ```

      ```bash CAD (Interac email) theme={"dark"}
      curl -X POST https://sandbox-api.bachs.io/v1/payouts/destinations \
        -H "Authorization: Bearer sk_sandbox_abc123xyz..." \
        -H "Content-Type: application/json" \
        -d '{
          "currency": "CAD",
          "name": "Canada account",
          "scheme": "interac_email",
          "account_name": "Ada Okafor",
          "interac_email": "ada@example.com"
        }'
      ```

      ```bash KES theme={"dark"}
      curl -X POST https://sandbox-api.bachs.io/v1/payouts/destinations \
        -H "Authorization: Bearer sk_sandbox_abc123xyz..." \
        -H "Content-Type: application/json" \
        -d '{
          "currency": "KES",
          "type": "bank_account",
          "name": "Nairobi account",
          "bank_code": "01",
          "account_number": "1234567890"
        }'
      ```
    </CodeGroup>

    The response is the destination. Keep its `id`:

    ```json Response (USD) theme={"dark"}
    {
      "id": "pd_3Fh8wQz1LmN5rT2v",
      "name": "Operating account",
      "type": "bank_account",
      "currency": "USD",
      "status": "approved",
      "status_reason": null,
      "is_usable": true,
      "is_default": true,
      "account_number": "3010001234567",
      "account_name": "Ada Okafor Ventures LLC",
      "bank_code": null,
      "bank_name": "Bank of America",
      "phone_number": null,
      "mobile_provider": null,
      "wallet_address": null,
      "network": null,
      "sort_code": null,
      "iban": null,
      "swift_bic": null,
      "routing_number": "026009593",
      "bank_address": {
        "line1": "100 North Tryon Street",
        "city": "Charlotte",
        "state": "NC",
        "postal_code": "28255",
        "country": "US"
      },
      "institution_number": null,
      "transit_number": null,
      "interac_email": null,
      "scheme": "ach",
      "reviewed_at": "2026-10-07T09:14:02Z",
      "created_at": "2026-10-07T09:14:02Z",
      "updated_at": "2026-10-07T09:14:02Z"
    }
    ```

    #### Fields required for each currency

    | Currency and `scheme` | Required fields |
    | - | - |
    | `USD` with `ach`, `wire` or `rtp` | `scheme`, `account_name`, `bank_name`, `routing_number` (9 digits), `account_number`, `bank_address` with `line1`, `city`, `state`, `postal_code` and `country`. The bank's address must be in the US (`"country": "US"`). |
    | `GBP` | `account_name`, `bank_name`, `sort_code` (6 digits), `account_number` |
    | `EUR` | `account_name`, `bank_name`, `iban`, `swift_bic` (8 or 11 characters) |
    | `CAD` with `eft` or `interac_account` | `scheme`, `account_name`, `bank_name`, `institution_number` (3 digits), `transit_number` (5 digits), `account_number` |
    | `CAD` with `interac_email` | `scheme`, `account_name`, `interac_email` |
    | `KES`, `GHS`, `UGX`, `TZS` | `bank_code`, `account_number`. Get the bank code from [List Banks](/api-reference/reference/list-banks) with `country` set to `KE`, `GH`, `UG` or `TZ`. |

    `bank_address` is the address of your bank's branch, not your own address. You can paste sort codes, IBANs and account numbers with spaces or dashes, as your bank statement prints them. We remove them before we check the format.

    <Note>
      `scheme` is required only where a currency can be paid more than one way: `USD` and `CAD`. `GBP` and `EUR` have one way each, so you leave `scheme` out and it comes back `null`.
    </Note>
  </Step>

  <Step title="Wait for review, if your currency needs it">
    In live mode, a `USD` bank destination is automatically approved when you save it: `status` is `approved` and `is_usable` is `true`.

    Other bank destinations in the table above initially come back in live mode with `status` set to `pending_review` and `is_usable` set to `false`. Our team checks the details before any money can go to the account. Check the destination with [Get Destination](/api-reference/payouts/get-payout-destination) and continue when `is_usable` is `true`. If we cannot approve it, `status` becomes `rejected` and `status_reason` says why.

    ```bash Check the destination theme={"dark"}
    curl https://sandbox-api.bachs.io/v1/payouts/destinations/pd_3Fh8wQz1LmN5rT2v \
      -H "Authorization: Bearer sk_sandbox_abc123xyz..."
    ```

    <Warning>
      Changing the account details of a saved destination (account number, sort code, IBAN, routing number, holder name or bank name) sends it back to `pending_review`, even for a `USD` bank. Its approval was for the old details. See [Update Destination](/guides/payouts/update-destination).
    </Warning>
  </Step>

  <Step title="Get a quote for a destination currency other than USD">
    For a withdrawal from your USD balance to your USD bank account, skip this step.

    To withdraw USD as another currency, for example to your GBP bank account, call [Create Payout Quote](/api-reference/payouts/create-payout-quote) first. `amount` is how much leaves your balance, before the fee. Send `payout_method` as `BANK_TRANSFER`.

    <CodeGroup>
      ```bash Request theme={"dark"}
      curl -X POST https://sandbox-api.bachs.io/v1/payouts/quotes \
        -H "Authorization: Bearer sk_sandbox_abc123xyz..." \
        -H "Content-Type: application/json" \
        -d '{
          "from_currency": "USD",
          "to_currency": "GBP",
          "amount": "500.00",
          "payout_method": "BANK_TRANSFER"
        }'
      ```

      ```json Response theme={"dark"}
      {
        "quote_id": "pqt_8c1f2e9a4b7d4e0f9a3c6b2d1e5f7a90",
        "from_currency": "USD",
        "to_currency": "GBP",
        "from_amount": "500.00",
        "to_amount": "371.50",
        "exchange_rate": "0.743",
        "expires_at": "2026-10-07T09:20:30+00:00"
      }
      ```
    </CodeGroup>

    `to_amount` is what reaches your bank. A quote lasts 30 seconds, so create it right before the payout and send the payout as soon as it returns.
  </Step>

  <Step title="Send the withdrawal">
    Call [Create Payout](/api-reference/payouts/create-payout) with the destination. For a same-currency withdrawal, send `amount`. With a quote, send `quote_id` and leave `amount` out, because the quote already fixes both amounts. Always send an `Idempotency-Key`.

    <CodeGroup>
      ```bash Same currency (USD to USD) theme={"dark"}
      curl -X POST https://sandbox-api.bachs.io/v1/payouts \
        -H "Authorization: Bearer sk_sandbox_abc123xyz..." \
        -H "Content-Type: application/json" \
        -H "Idempotency-Key: withdraw-2026-10-07-usd" \
        -d '{
          "destination": "pd_3Fh8wQz1LmN5rT2v",
          "amount": "500.00",
          "reference": "WD-2026-10-07-USD"
        }'
      ```

      ```bash With a quote (USD to GBP) theme={"dark"}
      curl -X POST https://sandbox-api.bachs.io/v1/payouts \
        -H "Authorization: Bearer sk_sandbox_abc123xyz..." \
        -H "Content-Type: application/json" \
        -H "Idempotency-Key: withdraw-2026-10-07-gbp" \
        -d '{
          "destination": "pd_9Gx2kLp7QwE4nB6s",
          "quote_id": "pqt_8c1f2e9a4b7d4e0f9a3c6b2d1e5f7a90",
          "reference": "WD-2026-10-07-GBP"
        }'
      ```

      ```json Response (with a quote) theme={"dark"}
      {
        "id": "pay_Lm4Qw8Zt2RkV6nHx",
        "status": "pending",
        "amount": "371.50",
        "currency": "GBP",
        "source_currency": "USD",
        "fee": "5.00",
        "total_debited": "505.00",
        "destination": "pd_9Gx2kLp7QwE4nB6s",
        "reference": "WD-2026-10-07-GBP",
        "failure_reason": null,
        "created_at": "2026-10-07T09:20:12Z",
        "completed_at": null
      }
      ```
    </CodeGroup>

    `amount` is in the bank's currency. `fee` and `total_debited` are in `source_currency`, the balance the money left. The fee is charged on top, so a `500.00` USD withdrawal debits more than `500.00` USD. Reconcile against `total_debited`.

    <Warning>
      A `500`, `502`, `503` or `504`, or a request that times out, does not mean the withdrawal failed. The money may already have left your balance. Check with [Get Payout](/api-reference/payouts/get-payout) or [List Payouts](/api-reference/payouts/list-payouts) before you try again, and retry with the same `Idempotency-Key` so you cannot pay twice.
    </Warning>
  </Step>

  <Step title="Confirm the money arrived">
    A successful response means we accepted the withdrawal and took the money from your balance. It does not mean your bank has it. The `status` moves from `pending` to `processing`, then to `completed` or `failed`.

    Listen for [`payout.paid`](/guides/webhooks/events/payout-paid) and [`payout.failed`](/guides/webhooks/events/payout-failed), or poll [Get Payout](/api-reference/payouts/get-payout):

    ```bash Check the withdrawal theme={"dark"}
    curl https://sandbox-api.bachs.io/v1/payouts/pay_Lm4Qw8Zt2RkV6nHx \
      -H "Authorization: Bearer sk_sandbox_abc123xyz..."
    ```

    On `failed`, `failure_reason` says why. The amount and the fee both go back to your available balance.
  </Step>
</Steps>

***

## Errors

| `error_code` | When it happens | What to do |
| - | - | - |
| `BANK_ROUTING_DETAILS_INVALID` (400) | A required field is missing, or a value has the wrong format: a routing number that fails its check digit, a sort code that is not 6 digits, an IBAN that is not valid, a bank address outside the US. `detail` names every problem, for example `ach payouts need routing_number, bank_address`. | Fix each field `detail` names and send the request again. |
| `BANK_ROUTING_DETAILS_INVALID` (400) with `detail` such as `scheme is required for USD: one of ach, wire, rtp` | You saved a `USD` or `CAD` bank without `scheme`, or sent a `scheme` the currency does not have. | Send one of the schemes listed in `detail`. |
| `BANK_ROUTING_DETAILS_INVALID` (400) with `detail` such as `CHF bank payouts are not supported` | The currency is not in the table above. | Withdraw in a supported currency. |
| `PAYOUT_CURRENCY_NOT_ENABLED` (400) | The requested bank route is disabled for the account. | If this occurs for a supported route, contact support with the error details. |
| `PAYOUT_SOURCE_CURRENCY_UNSUPPORTED` (400) | The source balance cannot fund this bank route. | Use your available USD balance. |
| `PAYOUT_RECIPIENT_INCOMPLETE` (400) | Required account identity information is missing. | Complete your account name, contact email and address. |
| `AMOUNT_REQUIRED` (400) | A payout with neither `amount` nor `quote_id`. | Send `amount` for a same-currency withdrawal, or a `quote_id`. |
| `AMOUNT_NOT_ALLOWED_WITH_QUOTE` (400) | A payout with both `amount` and `quote_id`. | Leave `amount` out. The quote already fixes it. |
| `QUOTE_EXPIRED` (400) | More than 30 seconds passed between the quote and the payout. | Create a new quote and send the payout straight away. |
| `BAD_REQUEST` (400), `Quote payment method mismatch` | The quote was for a different `payout_method` than the destination uses. | Quote again with `payout_method` set to `BANK_TRANSFER`. |
| `BAD_REQUEST` (400), `Quote is not required for same-currency payout` | You asked for a quote from a balance in the same currency as the bank. | Skip the quote and send `amount` on the payout. |

See [Errors](/errors) for the shape of every error response.

***

## Next steps

* [Withdrawals overview](/guides/payouts/overview): supported routes and the shared withdrawal flow.
* [Multi-currency withdrawals](/for-you/multi-currency-withdrawals): supported methods and funding requirements.
* [Payout schedules](/guides/payouts/payout-schedules): withdraw to your default destination on a daily, weekly or monthly schedule.
* [Update Destination](/guides/payouts/update-destination): rename a destination or make it the default for its currency.
* [The payout destination object](/api-reference/payout-destinations/object): every field on a destination.


This documentation is built and hosted on [Mintlify](https://mintlify.com), a developer documentation platform.