Skip to main content
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.
ModelKey custodyBuilt for
CustodialOMS holds the keysRegulated fintech, neobanks, and remittance products
Non-custodialThe user holds the keysConsumer apps where users control their own funds
AgenticThe agent holds scoped keysAutonomous 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:
PrefixType
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.