Overview
Brale is a flexible stablecoin rail that works with any self-custody wallet. Pairing Brale with a self-custody wallet provider gives you everything you need to run stablecoin-powered money movement—while keeping wallet choice and key control inside your product. This guide describes a provider-agnostic pattern you can reuse across current or future wallet partners.What “self-custody” means in Brale
In Brale, wallets and bank endpoints are represented as Addresses (address_id). Addresses come in two types:
type=internal— Brale-custodied addresses generated for an Accounttype=external— an address managed outside Brale (e.g., a user-owned wallet, external bank account, or Canton party)
type=external Addresses.
Why Brale + a self-custody wallet
Using a self-custody wallet provider for the wallet layer and Brale for rails gives you:- Full rails + orchestration: onramps, offramps, transfers, swaps using one set of core primitives.
- Wallet optionality: standardize once, then add/swap wallet providers without rewriting the rails.
- End-user trust + control: users control keys (or an MPC equivalent); Brale never holds end-user keys.
- A clean integration boundary: wallet provider handles key mgmt + signing; Brale handles regulated money movement.
Core model (what maps to the Brale API)
Brale is built around a small set of resources:- Accounts (
account_id) represent KYB’d business entities. Your application has its ownaccount_id, and you can create additionalaccount_idvalues for business clients. - Addresses (
address_id) are the universal source/destination primitive for transfers. - Transfers (
transfer_id) move value across fiat rails and blockchains and always referenceaddress_idfor source + destination.
Identifiers: transfer_type and value_type
Brale is strict about canonical identifiers:
transfer_type= the rail or chain (e.g.,wire,ach_debit,base,solana)value_type= the currency/token (e.g.,usd,USDC,SBC) and is case-sensitive
Offchain USD is
value_type=usd (lowercase). Many onchain stablecoins are uppercase (e.g., USDC, SBC). Always use Coverage as the source of truth.Integration pattern (provider-agnostic)
1) Authenticate to Brale
Create API credentials in the Brale dashboard, then exchange them for a bearer token.Authorization: Bearer ${access_token} for subsequent calls. Tokens are short-lived—refresh using expires_in.
2) Get your account_id
- your primary
account_idfor your own program operations - a managed customer
account_idwhen moving funds for an end business/customer
3) Get your internal custodial wallets (type=internal)
Brale automatically generates internal custodial addresses on supported chains for an onboarded Account.
You can filter addresses using query parameters on GET /accounts/{account_id}/addresses, including:
type=internal|externaltransfer_type=...(repeatable)
address_id whose transfer_types include the chain(s) you need.
Query an internal balance (custodial only)
Balances require bothtransfer_type and value_type and work only for type=internal.
4) Register the user’s self-custody wallet as an external Address (type=external)
Your wallet provider provisions a wallet and returns an onchain address. Register it with Brale so it can be used as a transfer source/destination.
id as the user’s Brale address_id.
Notes:
- If the same user has wallets on multiple non-EVM chains (e.g., Solana + Stellar), create separate external Addresses (you’ll get distinct
address_idvalues). - You cannot query balances for external addresses—only internal custodial ones.
- For more chain-specific examples, see Add an External Destination.
5) Create transfers using address_id
Transfers are how you do onramps/offramps, payouts, and swaps. Every transfer references address_id for source + destination and includes both transfer_type and value_type.
Idempotency rules
- Always send
Idempotency-Keyon create POSTs; never on GETs. - Reuse the same key only when retrying the same logical request; never reuse with a different payload or URI.
Example A — Stablecoin payout (internal → external wallet)
Example B — ach_debit funded mint to a self-custody wallet
Brale supportsach_debit flows that mint to a custodial or self-custody wallet.
This assumes you’ve already created a funding Address for the end user’s bank account (typically via Plaid) and have its address_id.
Example C — Stablecoin swap / cross-chain move (1:1, no slippage)
Stablecoins can be swapped 1:1 across chains; Brale burns on the source chain and mints on the destination chain.Offramping (stablecoin → USD) with self-custody
To offramp to USD, stablecoins must be held in a Brale custodial address (type=internal).
For self-custody users, the common pattern is:
- User sends stablecoins from their wallet → your Brale custodial deposit address
- You initiate a stablecoin→USD offramp to the customer’s bank Address
Provider optionality (recommended abstraction)
To keep wallet choice flexible, model wallet providers behind a small interface.user_idwallet_providerwallet_address(chain-format address)transfer_typesenabled for this wallet (e.g.,["base","ethereum"]or["solana"])- Corresponding Brale
address_id created_at,updated_at
Troubleshooting & known pitfalls
- Account is pending: you may need to wait for KYB to complete before creating/linking resources for that Account.
- Internal vs external confusion: use
type=internalfor Brale-custodied;type=externalfor user wallets/bank accounts. - Case sensitivity: use canonical identifiers from Coverage; they are case-sensitive.
- Environment mismatch: don’t mix testnet and mainnet credentials/resources (common cause of
network_not_supported). - Branding:
brandapplies to ACH only (not wire/RTP).