OMS supports flexible custody models. Pick the model that fits your product; the rest of the OMS API (transactions, quotes, compliance, on-ramps and off-ramps) works the same across all three.
| Model | Key custody | Built for |
|---|
| Custodial | OMS holds the keys | Regulated fintech, neobanks, and remittance products |
| Non-custodial | The user holds the keys | Consumer apps where users control their own funds |
| Agentic | The agent holds scoped keys | Autonomous AI agents with policy-based spending limits |
A single product can combine them: custodial for fiat flows and compliance, non-custodial for user-controlled onchain assets, agentic for autonomous payments.
The remainder of this page covers the account types used by the custodial model. For non-custodial and agentic wallets, see OMS wallets.
Custodial wallets
The primary balance store. OMS manages private keys, no end-user signing, no wallet SDKs required. All operations are server-to-server API calls.
Wallet
├── id: "wlt_..."
├── customerId: "cst_..."
├── address: "0x..." (onchain address, read-only)
├── asset: "usdc"
├── chain: "polygon"
└── status: "active"
Each wallet holds a single asset on one chain. Read its current balance with GET /wallets/{id}/balance.
Creating a wallet: POST /customers/{id}/wallets with the customer in the path and a body specifying asset and chain. Returns immediately. The wallet is active and ready to receive.
Funding a wallet: Send crypto directly to the wallet’s address, or use the on-ramp flows (cash-in today; virtual accounts and deposit addresses in early access) to convert fiat.
Multiple wallets: A customer can have more than one wallet, for different currencies, use cases, or segregation needs.
Virtual accounts
A bank account number wired to a customer’s wallet. Fiat deposits auto-convert to USDC.
Virtual Account
├── id: "va_..."
├── customerId: "cst_..."
├── destinationWalletId: "wlt_..."
└── depositInstructions: {
accountNumber: "...",
routingNumber: "...",
rail: "ach"
}
Give your customers the depositInstructions to display in your UI. Any ACH transfer to that account number delivers USDC to the linked wallet. No polling required: the transaction.fiatToCrypto.completed webhook fires on each successful deposit.
Virtual accounts are OMS-managed and not yet available in the OMS API. They are provisioned through early access.
Deposit addresses
A blockchain address wired to a wallet. Onchain deposits auto-create a transaction.
Deposit Address
├── id: "da_..."
├── customerId: "cst_..."
├── destinationWalletId: "wlt_..."
└── depositInstructions: {
address: "0x...",
network: "polygon",
asset: "usdc"
}
Unlike regular wallet addresses, deposit addresses carry routing configuration. A deposit to a wallet’s raw address is detected and fires wallet.depositDetected: but OMS does not auto-convert or route it. Deposit addresses are the correct pattern for “accept any amount from any sender” flows.
Deposit addresses are OMS-managed and not yet available in the OMS API. They are provisioned through early access.
External accounts
Customer-owned bank accounts and saved addresses, referenced on a transaction by their ID as an off-ramp destination. A quote’s source is always an OMS wallet, so external accounts are destinations, not sources. The ID prefix identifies the account type:
| Prefix | Type |
|---|
pmCard_ | Debit card |
cpUsBank_ | US bank account (ACH/wire) |
cpIntlBank_ | International bank account |
cpBcAddr_ | Blockchain address |
External account IDs are provisioned through early access; there is not yet an endpoint to register them.
External accounts are customer-owned, not OMS-managed. OMS never holds funds in an external account, they are used only as a source or destination reference on a transaction.