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

# ach_debit to end user wallet 

> Create an ach_debit funded transaction to a custodial, or self custody wallet.

## Introduction

This document is a guide to help your team evaluate and implement the ach\_debit transfer type. It covers what the ach\_debit functionalities are, how to get set up, and the key steps to go from initial testing to production. Use it as a reference while you integrate.

When making `POST` requests Brale requires an Idempotency-Key header to be included and used on retries. Read more about [idempotency keys in Brale](https://docs.brale.xyz/key-concepts/idempotency.md#what-is-an-idempotency-key). Using ach-debits requires your business to be fully onboarded, and branded ach\_debits are a premium feature.

* **Testnet Environment**
  * If users want to first test the below endpoints on testnet, they will need to create API credentials that are scoped to testnet - please follow the below link for more information:
    * [Sandbox and Testnet walkthrough](https://docs.brale.xyz/overview/sandbox-and-testnet.md)
* **Production Environment**
  * Partners can leverage mainnet APIs by using their mainnet API keys generated in the Brale Portal.

## End Customer Account Creation

* [`Here is an example of how to get your account_id`](https://docs.brale.xyz/guides/how-to/get-your-account-id)

  ```tsx theme={null}
  curl --request GET \
    --url https://api.brale.xyz/accounts \
    --header "Authorization: Bearer ${ACCESS_TOKEN}"
  ```
* Managed account model: where Partner’s end users can create an account under Partner account, where Partner will have access to those accounts. This will allow end users to view their account and transaction history for all onramp operations executed. The Partner will be able to execute onramp operations on behalf of the end users as well.

## **Create Plaid link token**

* The following request is an example of how to create a Plaid Token that you will need to use in the next section when setting up addresses for funding your transactions. Specifically, you will want to store the `link_token` as this is the plaid token you will need to use in subsequent requests.
* **Native mobile app considerations (Plaid Link):**

  Because Brale manages the Plaid client configuration, mobile Plaid OAuth is **not fully self-serve**. You must submit your platform-specific values to Brale so they can be whitelisted before mobile Plaid OAuth will work.

  Your backend should create the `link_token`. Your mobile app uses the `link_token` to initialize Plaid Link.

  **iOS:**

  * Provide Brale with your iOS **redirect URI**. This must be an HTTPS universal link (e.g., `https://example.com/plaid-redirect`). Custom URL schemes are not supported for Plaid OAuth on iOS.
  * Your app must be configured for [Universal Links](https://developer.apple.com/documentation/xcode/allowing-apps-and-websites-to-link-to-your-content) so the redirect returns the user to your app after they complete the bank login.

  **Android:**

  * Provide Brale with your **Android package name** (`android_package_name`, i.e., the `applicationId` from your app's build configuration).
  * The package name you submit must exactly match your app's configuration. A mismatch will prevent Plaid from redirecting back to your app.

  > [!WARNING]
  >
  > Brale must whitelist your iOS redirect URI and/or Android package name before mobile Plaid OAuth will function. Contact Brale to submit these values.

  **Troubleshooting: user completes bank login but is not returned to the app**

  If the user finishes the Plaid/bank login flow but lands in Safari (iOS) or is not redirected back to your app (Android), check the following:

  * **Brale whitelist not completed** — confirm Brale has approved your redirect URI or package name.
  * **iOS Universal Link misconfigured** — verify your app's Associated Domains entitlement and `apple-app-site-association` file are set up correctly.
  * **Android package name mismatch** — ensure the `android_package_name` submitted to Brale matches your app's `applicationId`.
  * **App configuration does not match the submitted value** — double-check that the value registered with Brale exactly matches what your app is using at runtime.

```bash theme={null}
curl --request POST \
  --url https://api.brale.xyz/accounts/{account_id}/plaid/link_token \
  --header 'Authorization: Bearer <token>' \
  --header 'Content-Type: application/json' \
  --data '
{
  "legal_name": "John Doe",
  "email_address": "user@example.com",
  "phone_number": "+1234567890",
  "date_of_birth": "1990-01-01",
  "client_name": "Rhythmic"
}
'
```

Response Body Example:

```json theme={null}
{
  "link_token": "link-sandbox-123456789",
  "expiration": "2024-01-01T12:00:00Z",
  "callback_url": "https://customer.com/webhooks/plaid-updates"
}
```

* Your backend should call the endpoint indicated by `callback_url` (which is the Brale “register account” step) and pass the `public_token` (and your webhook URL, if applicable). This registers the bank account inside Brale and returns an `address_id` for subsequent transfers.

### Customize the application name

Pass the optional `client_name` field in the link token request body to set the application name that users see in Plaid Link.

* `client_name` is a string with a maximum length of 30 characters.
* If you omit `client_name`, Plaid Link displays `Brale`.
* Values longer than 30 characters return a validation error.

```json theme={null}
{
  "legal_name": "John Doe",
  "email_address": "user@example.com",
  "phone_number": "+1234567890",
  "date_of_birth": "1990-01-01",
  "client_name": "Rhythmic"
}
```

> [!NOTE]
>
> `client_name` changes the displayed application name only. It does not change logos, colors, or ACH bank statement descriptors. Logos and colors are configured separately through Plaid Link customizations, which are selected with `link_customization_name`. You cannot pass a logo URL through `client_name`, and customer-specific logo setup is not self-service.

## Setup addresses (Funding)

* If the user wants to connect a financial institution to fund their stablecoin with, they will need to create an external address for it. The same goes for funding stablecoins with existing digital assets from an external wallet. When transferring to an external address (on-chain wallet or off-chain bank account), use:
* **`POST /accounts/{account_id}/addresses/external`**
* Include an **`Idempotency-Key`** header and a JSON body. Examples: **On-chain wallet (e.g., Solana) request body example:**

  ```json theme={null}
  {
    "name": "User Solana Wallet",
    "address": "7xKXtg...",
    "transfer_types": ["solana"]
  }
  ```

  **Off-chain bank account request example:**

  * Partner will need to create a form that requests this information from end users to provide the below data
  * Once the below data has been recorded, Brale will generate an “Address ID” that corresponds to the financial institution OR the external wallet, and for any future debit operations we will reference the “Address ID”. [Create off-chain external address](https://docs.brale.xyz/api-reference/brale/create-address.md):

  ```bash theme={null}
  curl --request POST \
    --url https://api.brale.xyz/accounts/{account_id}/addresses/external \
    --header 'Authorization: Bearer <token>' \
    --header 'Content-Type: application/json' \
    --header 'Idempotency-Key: <idempotency-key>' \
    --data '
  {
    "name": "Operating Account (ACH Debit)",
    "transfer_types": [
      "ach_debit"
    ],
    "plaid_token": "PUBLIC_TOKEN_EXCHANGED_WITH_BRALE"
  }
  '
  ```

## Check the bank balance (optional)

After you register the bank account through Plaid and before you submit the transfer, you can check the linked account's USD balance. Pass `transfer_type=ach_debit` and `value_type=USD`.

```bash theme={null}
curl --request GET \
  --url "https://api.brale.xyz/accounts/${ACCOUNT_ID}/addresses/${ADDRESS_ID}/balance?transfer_type=ach_debit&value_type=USD" \
  --header "Authorization: Bearer ${AUTH_TOKEN}"
```

```json theme={null}
{
  "address": {
    "id": "34yknnMShvDbSW6tQg4HLlN41QO",
    "address": null,
    "account_number": "****1234"
  },
  "balance": {
    "value": "1250.42",
    "currency": "USD"
  },
  "value_type": "USD",
  "transfer_type": "ach_debit"
}
```

* The balance is a point-in-time snapshot and may be cached for up to 30 seconds. It does not guarantee that the ACH debit will settle.
* Brale still validates the balance when you submit the transfer. If the balance is insufficient, `POST /transfers` returns HTTP `422` with `type: insufficient_balance`. See [Idempotency](https://docs.brale.xyz/key-concepts/idempotency.md) for how to retry.
* If the balance request returns `503` with `type: service_unavailable`, balance data is temporarily unavailable. This does not mean the balance is zero or insufficient.
* If it returns `400` with `type: plaid_error` and `code: plaid_item_login_required`, have the user relink the bank account through Plaid.

> [!TIP]
>
> If ACH debits fail for one linked bank account while the user's other accounts work, the Plaid connection for that account may have expired. Call the balance endpoint for that `address_id`. If it returns `plaid_item_login_required`, have the user relink the bank account through Plaid, then use the `address_id` returned by registration for new debits.

### ACH debit errors

When you submit an `ach_debit` transfer, Brale can reject it with HTTP `422` if the source bank account cannot be debited. Brale does not create the transfer or submit the debit.

| `type` | Cause | Fix |
| - | - | - |
| `bank_account_not_linked` | The bank account is not linked through Plaid. | Link the bank account through Plaid before using it for an ACH debit. |
| `bank_account_relink_required` | The bank account's Plaid link has expired. | Have the user relink the bank account through Plaid, then retry. |
| `bank_balance_unavailable` | Brale could not verify the bank account's balance. | Retry. If the error continues, have the user relink the bank account. |
| `insufficient_balance` | The transfer amount exceeds the bank account's available balance. | Have the user fund the bank account, then retry. |

Brale does not cache `422` responses for idempotency, so you can retry with the same `Idempotency-Key` and body. See [Idempotency](https://docs.brale.xyz/key-concepts/idempotency.md).

See [Get A Balance](https://docs.brale.xyz/guides/how-to/get-a-balance.md#plaid-linked-bank-balance) for details and [Get an address balance](https://docs.brale.xyz/api-reference/brale/get-address-balance.md#errors) for all errors.

## Requesting Mint/On-ramping

* At this stage your end users’ accounts should already be connected, where Brale will execute debits and mint to desired stablecoins from the process below:
* You will need to call the following below endpoints with the relevant data to be able to execute a `swap` or a `transfer`.

  * Transfer - Fiat to Stablecoin Transfer scenarios:
  * [`Transfer scenarios: understanding value_type and transfer_type`](https://docs.brale.xyz/key-concepts/transfers#transfer-scenarios-understanding-value_type-and-transfer_type)

  **POST** **`https://api.brale.xyz/accounts/account_id/transfers`**

  * `USDC` to `SBC` request body example:

  ```json theme={null}
  {
    "amount": {
      "value": "100",
      "currency": "USD"
    },
    "source": {
      "address_id": "2VcUIonJeVQzFoBuC7LdFT0dRe4",
      "value_type": "USDC",
      "transfer_type": "Solana"
    },
    "destination": {
      "address_id": "2VcUIonJeVQzFoBuC7LdFT0dRe4",
      "value_type": "SBC",
      "transfer_type": "Solana"
    }
  }
  ```

  * [`USD to Stablecoin request body example`](https://docs.brale.xyz/key-concepts/transfers#usd-to-stablecoin-ach-debit):

  ```json theme={null}
  {
    "amount": {
      "value": "1",
      "currency": "USD"
    },
    "source": {
      "address_id": "2VcUIonJeVQzFoBuC7LdFT0dRe4", // address_id is the primitive, even for onchain sources
      "value_type": "USD",
      "transfer_type": "ach_debit"
    },
    "destination": {
      "address_id": "34yGFQf7tP1HJCPAWNGaN4rh4nX",
      "value_type": "SBC",
      "transfer_type": "polygon"
    },
    "brand": { "account_id": "34lCJZ2bxbPAzB3ou67Md01veUo" }
  }
  ```
