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

# Work with omnibus accounts

> Set up omnibus custody end-to-end: create an omnibus account, fund it, read chain-agnostic balances, and move funds off-chain and on-chain.

## Goal

Set up a customer with an omnibus account and use it end-to-end: create the account, fund it, read its balance, and move funds both within your platform (off-chain) and to external wallets (on-chain).

If you're not familiar with the two custody models, read [Crypto custody](/guides/concepts/custody#account-types) first.

## How omnibus custody works

With omnibus accounts, customer funds are pooled in a Bastion custody vault. Bastion tracks each account's balance on a secure double-entry ledger. This differs from segregated accounts, where each customer holds funds at their own dedicated on-chain addresses.

| Characteristic       | Omnibus (`OMNIBUS`)                                                                  |
| -------------------- | ------------------------------------------------------------------------------------ |
| How assets are held  | Pooled in a custody vault; per-account balances tracked on Bastion's internal ledger |
| Balance model        | Chain-agnostic: one USDC balance regardless of chain                                 |
| Off-chain transfers  | Instant, fee-free transfers between omnibus accounts on the same platform            |
| On-chain withdrawals | Supported on Solana and EVM chains, with gas sponsored by Bastion                    |

Both account types are fully custodial under Bastion's US regulated licensing, and your platform uses the same APIs either way.

## Key entities

| Entity        | Description                                                                                |
| ------------- | ------------------------------------------------------------------------------------------ |
| Your platform | The product integrating Bastion's APIs                                                     |
| Customer      | An individual or business your platform has onboarded (KYC/KYB-verified)                   |
| Identity      | The customer's record in Bastion, used for compliance and permissions                      |
| Account       | An omnibus account linked to the identity. Its balance lives on Bastion's internal ledger. |

## Flow overview

1. Onboard the customer and wait for their identity to become `ACTIVE`.
2. Create an account with `type: OMNIBUS`.
3. Fund the account via on-ramp, crypto deposit, or off-chain transfer.
4. Check the chain-agnostic balance.
5. Move funds off-chain (within your platform) or on-chain (external withdrawals).

***

## Prerequisites

* You have access to your dashboard
* You've created an API key
* You've registered a webhook

***

## Step 1 – onboard the customer

Omnibus accounts follow the same onboarding flow as any other account. Create an identity, submit KYC or KYB data, and track status until the identity is `ACTIVE`:

* [Onboard an individual customer](/guides/quickstarts/onboard-an-individual-customer)
* [Onboard a business customer](/guides/quickstarts/onboard-a-business-customer)

Accounts cannot be created until the identity's `status` is `ACTIVE`.

***

## Step 2 – create an omnibus account

Call [`POST /v2/accounts`](/v2/api-reference/accounts/create-account) with `type` set to `OMNIBUS`.

<Warning>
  The `type` field defaults to `SEGREGATED`. You must set `type: "OMNIBUS"` explicitly to create an omnibus account.
</Warning>

Use a unique `request_id` (UUID) as an idempotency key. Reuse the same `request_id` on retries to avoid duplicate accounts.

**Request**

```http theme={"theme":{"light":"vitesse-light","dark":"vitesse-dark"}}
POST /v2/accounts
Content-Type: application/json
Authorization: Bearer YOUR_API_KEY
```

<CodeGroup>
  ```json theme={"theme":{"light":"vitesse-light","dark":"vitesse-dark"}}
  {
    "request_id": "f47ac10b-58cc-4372-a567-0e02b2c3d479",
    "identity_id": "01HZX8QK9VM5T9JY8ZQXQZQXQZ",
    "type": "OMNIBUS"
  }
  ```
</CodeGroup>

**Response**

<CodeGroup>
  ```json theme={"theme":{"light":"vitesse-light","dark":"vitesse-dark"}}
  {
    "request_id": "f47ac10b-58cc-4372-a567-0e02b2c3d479",
    "identity_type": "INDIVIDUAL",
    "addresses": [
      {
        "address": "0x742d35Cc6634C0532925a3b844Bc9e7595f0bEb",
        "chains": ["ETHEREUM_MAINNET", "BASE_MAINNET"]
      },
      {
        "address": "DYw8jCTfwHNRJhhmFcbXvVDTqWMEVFBX6ZKUmG5CNSKK",
        "chains": ["SOLANA_MAINNET"]
      }
    ],
    "identity_id": "01HZX8QK9VM5T9JY8ZQXQZQXQZ",
    "id": "01HZXBANKACCOUNTIDEXAMPLE0",
    "type": "OMNIBUS",
    "frozen": false,
    "identity_status": "ACTIVE",
    "identity_sub_status": "IDENTITY_SUB_STATUS_UNSPECIFIED",
    "account_status": "ACTIVE"
  }
  ```
</CodeGroup>

The response returns the new account. Capture these fields:

| Field       | Meaning                                                                                                                                         |
| ----------- | ----------------------------------------------------------------------------------------------------------------------------------------------- |
| `id`        | The account ID. Store it alongside your internal customer record.                                                                               |
| `type`      | `OMNIBUS`, confirming the custody model                                                                                                         |
| `addresses` | The account's deposit addresses, one entry per address with the chains it accepts. Display these to the customer for receiving crypto deposits. |
| `status`    | The account's status, derived from its parent identity state (`ACTIVE` when ready)                                                              |

<Info>
  The `addresses` array contains the deposit addresses for the account. Funds sent to a deposit address are credited to the account's chain-agnostic ledger balance, regardless of which supported chain they arrive on. See [Receive crypto deposits](/guides/quickstarts/receive-crypto-deposits) for how to display deposit addresses and handle incoming deposits.
</Info>

If the identity has not passed compliance and jurisdiction checks, the request returns HTTP 422.

***

## Step 3 – fund the account

An omnibus account can receive funds in three ways:

| Funding method     | How it works                                                                                                                    |
| ------------------ | ------------------------------------------------------------------------------------------------------------------------------- |
| On-ramp conversion | Convert fiat to stablecoin into the account. See [On-ramp](/guides/quickstarts/on-ramp).                                        |
| Crypto deposit     | Receive crypto from an external wallet or exchange. See [Receive crypto deposits](/guides/quickstarts/receive-crypto-deposits). |
| Off-chain transfer | Receive an instant transfer from another omnibus account on your platform.                                                      |

In all cases, Bastion credits the balance and delivers a webhook when funds arrive. For deposits, that is the `deposit_v2` event. Off-chain deposits from another account on your platform arrive with `source_type: ACCOUNT` and `source_account_id` identifying the sender.

***

## Step 4 – check the balance

```http theme={"theme":{"light":"vitesse-light","dark":"vitesse-dark"}}
GET /v2/accounts/{account_id}/balances
Authorization: Bearer YOUR_API_KEY
```

**Response:**

<CodeGroup>
  ```json theme={"theme":{"light":"vitesse-light","dark":"vitesse-dark"}}
  {
    "balances": [
      {
        "symbol": "USDC",
        "total": "975000000",
        "available": "975000000",
        "decimals": 6,
        "type": "OMNIBUS",
        "chain": "CHAIN_UNSPECIFIED"
      }
    ]
  }
  ```
</CodeGroup>

Omnibus balances differ from segregated balances:

* Balances are chain-agnostic. The `type` field is `OMNIBUS` and `chain` is `CHAIN_UNSPECIFIED`. A customer holding USDC has one balance entry, regardless of which chains the funds arrived on. Segregated accounts instead return one entry per chain.
* `contract_address` is omitted.

Balances are returned in atomic units as strings. Use the `decimals` field to convert to a human-readable amount (e.g., `"975000000"` with `decimals: 6` = 975.00 USDC). The `available` field reflects the amount the customer can use for transfers or conversions. `total` may differ from `available` if funds are held for a pending operation.

***

## Step 5 – move funds

Both transfer modes use the same endpoint, [`POST /v2/crypto/transfers`](/v2/api-reference/cryptocurrency-transfers/submit-cryptocurrency-transfer). The destination field determines the mode.

### Off-chain: send to another account on your platform

Off-chain transfers move funds instantly on Bastion's internal ledger. There is no blockchain transaction, no gas fee, and no chain selection. Both sender and recipient must have omnibus accounts on the same platform.

<CodeGroup>
  ```json theme={"theme":{"light":"vitesse-light","dark":"vitesse-dark"}}
  {
    "request_id": "66f3b0b2-a25b-4e9d-a94b-fbcfa0720312",
    "account_id": "36hv3yZpsHt7pwgBZom5SXgURYz",
    "currency_symbol": "USDC",
    "amount": "25.00",
    "account_destination": {
      "account_id": "48jK9mLp2rSTuvW3xYzAaBbCcDd"
    }
  }
  ```
</CodeGroup>

Off-chain transfers settle immediately, with a status lifecycle of `ACCEPTED` → `CONFIRMED` / `FAILED`. For the full walkthrough, including webhook payloads and status handling, see [Send crypto off-chain](/guides/quickstarts/send-crypto/off-chain).

### On-chain: withdraw to an external wallet

On-chain withdrawals send funds through the blockchain to any wallet address. Omnibus accounts support on-chain withdrawals on Solana and EVM chains, with gas sponsored by Bastion. Specify the destination address and chain:

<CodeGroup>
  ```json theme={"theme":{"light":"vitesse-light","dark":"vitesse-dark"}}
  {
    "request_id": "903c9e51-51af-4597-9174-0a93ca80dbac",
    "account_id": "36hv3yZpsHt7pwgBZom5SXgURYz",
    "currency_symbol": "USDC",
    "amount": "25.00",
    "address_destination": {
      "address": "7xKXtg2CW87d97TXJSDpbD5jBkheTqA83TZRuJosgAsU",
      "chain": "SOLANA_MAINNET"
    }
  }
  ```
</CodeGroup>

Because the omnibus balance is chain-agnostic, the customer's full `available` balance can be withdrawn to any supported chain. On-chain transfers settle after block confirmation, with a status lifecycle of `ACCEPTED` → `SUBMITTED` → `CONFIRMED` / `FAILED` / `CANCELED` / `DROPPED`. For the full walkthrough, see [Send crypto on-chain](/guides/quickstarts/send-crypto/on-chain).

***

## Step 6 – verify setup

Confirm the customer is fully ready:

1. **Check identity status** — [`GET /v2/identities/{identity_id}`](/v2/api-reference/identities/get-identity) returns `status: ACTIVE`
2. **Check the account** — [`GET /v2/accounts/{id}`](/v2/api-reference/accounts/get-account) returns `type: OMNIBUS` and `status: ACTIVE`
3. **Check balances** — [`GET /v2/accounts/{account_id}/balances`](/v2/api-reference/accounts/get-account-balances) shows the funded, chain-agnostic balance

***

## Implementation notes

| Recommendation                 | Details                                                                                                                                                  |
| ------------------------------ | -------------------------------------------------------------------------------------------------------------------------------------------------------- |
| Set `type` explicitly          | `POST /v2/accounts` defaults to `SEGREGATED`. Always pass `type: "OMNIBUS"` when creating omnibus accounts.                                              |
| Use idempotent request IDs     | Provide a unique `request_id` (UUID) per account creation and per transfer. Reuse it on retries to avoid duplicates.                                     |
| Display one balance per asset  | Omnibus balances are chain-agnostic. Do not build per-chain balance UI for omnibus accounts.                                                             |
| Check `available` before sends | Confirm sufficient `available` balance before submitting a transfer. Surface insufficient-funds errors clearly in your UI.                               |
| Validate off-chain eligibility | Off-chain transfers require omnibus accounts on both sides, on the same platform. Ineligible destinations return HTTP 400.                               |
| Keep records in sync           | Store `identity_id`, `account_id`, and `transfer_id` in your own system and reconcile via `submit_cryptocurrency_transfer_v2` and `deposit_v2` webhooks. |

## Related documentation

* [Crypto custody](/guides/concepts/custody)
* [Identities and accounts](/guides/concepts/identities-and-accounts)
* [Crypto transfers and deposits](/guides/concepts/crypto-transfers)
* [Send crypto off-chain](/guides/quickstarts/send-crypto/off-chain)
* [Send crypto on-chain](/guides/quickstarts/send-crypto/on-chain)
* [Receive crypto deposits](/guides/quickstarts/receive-crypto-deposits)


## Related topics

- [Crypto custody](/guides/concepts/custody.md)
- [Send crypto off-chain between omnibus accounts](/guides/quickstarts/send-crypto/off-chain.md)
- [Send crypto from a Bastion account](/guides/quickstarts/send-crypto.md)
- [Accounts API overview](/v2/api-reference/accounts/overview.md)
- [Redeem Bastion-issued stablecoins from the Dashboard](/guides/dashboard/redeem-bastion-stablecoin.md)
