Skip to main content

Prerequisites

  • Brale account (KYB started/completed)
  • API client created for the right environment (testnet or mainnet)
  • curl (or HTTP client) and ability to Base64-encode client_id:client_secret

Quick path (copy/paste)

  1. Auth → access_token
  2. Get account_id
  3. Get custodial address_id (internal)
  4. POST transfer with Idempotency-Key
  5. Poll transfers for status

1. Sign up for Brale

Complete KYB to enable API access.

2. Create API credentials (mainnet vs testnet)

Create an API client in the Dashboard. Keys are shown once—store them securely.
Clients are environment-scoped; use testnet while integrating, mainnet when ready to move value.

3. Authenticate (client_credentials; short-lived tokens)

Tokens expire in ~60 minutes; refresh using expires_in before expiry.
curl --request POST \
  --url https://auth.brale.xyz/oauth2/token \
  --header 'Authorization: Basic ${BASE_64_OF(client_id:client_secret)}' \
  --header 'Content-Type: application/x-www-form-urlencoded' \
  --data grant_type=client_credentials
Expected fields: access_token, token_type (Bearer), expires_in.

4. Get your account_id (KSUID)

Use the bearer token from Step 3.
curl --request GET \
  --url https://api.brale.xyz/accounts \
  --header "Authorization: Bearer ${AUTH_TOKEN}"
Response (keep this ID for later):
{ "accounts": ["2Js1YFqlfxgNqC2KTPEjrWIwKU7"] }

5. Get your custodial address_id (internal)

Brale auto-creates internal wallets per account (EVM, Solana, Stellar). Use the bearer token and your ACCOUNT_ID.
ACCOUNT_ID="2Js1YFqlfxgNqC2KTPEjrWIwKU7"

curl --request GET \
  --url "https://api.brale.xyz/accounts/${ACCOUNT_ID}/addresses" \
  --header "Authorization: Bearer ${AUTH_TOKEN}"
Response (keep an internal address_id):
{
  "addresses": [
    {
      "id": "2MhCCIHulVdXrHiEuQDJvnKbSkl",
      "type": "internal",
      "transfer_types": ["base", "ethereum", "polygon", "solana", "stellar", "arbitrum", "optimism", "celo", "avalanche", "xrp_ledger", ...]
    }
  ]
}

6. Create your first transfer (Idempotency-Key required)

Example: offchain wire → onchain stablecoin on Base. Always send a unique Idempotency-Key per logical transfer and reuse it on retries. Headers: Authorization: Bearer, Content-Type: application/json, Idempotency-Key: $(uuidgen)
ADDRESS_ID="2MhCCIHulVdXrHiEuQDJvnKbSkl"

curl --request POST \
  --url "https://api.brale.xyz/accounts/${ACCOUNT_ID}/transfers" \
  --header "Content-Type: application/json" \
  --header "Authorization: Bearer ${AUTH_TOKEN}" \
  --header "Idempotency-Key: $(uuidgen)" \
  --data '{
    "amount": { "value": "10", "currency": "USD" },
    "source": { "value_type": "USD", "transfer_type": "wire" },
    "destination": {
      "address_id": "'"${ADDRESS_ID}"'",
      "value_type": "SBC",
      "transfer_type": "base"
    }
  }'
Expect: id, status (pendingprocessingcomplete), wire_instructions for funding.

7. Poll and reconcile

  • Poll GET /accounts/{account_id}/transfers with pagination tokens; store id, idempotency key, timestamps, status, references.
  • On 401, refresh the token and retry idempotently with the same Idempotency-Key.

Common errors

  • 403 network_not_supported: Using a testnet client/token on mainnet (or vice versa). Mint a token with a client for the target environment.
  • 404 compatible_address_not_found: Address doesn’t support the transfer_type, typo in address_id, or wrong account_id in the path.
  • 400 missing Idempotency-Key: All POST creates must include Idempotency-Key.

Next steps

  • See Guides for on/off-ramps, swaps, payouts.
  • For production hardening, review Troubleshooting and Coverage (transfer_types, value_types).