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 agents with policy-based spending limits
A single product can combine them: custodial for fiat flows and compliance, non-custodial for user-controlled on-chain 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, with no end-user signing and no wallet SDKs required. All operations are server-to-server API calls.
Wallet
├── id: "wlt_..."
├── object: "wallet"
├── customerId: "cst_..."
├── asset: "usdc"
├── chain: "polygon"
├── address: "0x..."              (on-chain address)
├── blockchainAsset: { ... }      (protocol / chainId / tokenId)
├── type: "internal"              (internal = OMS-managed)
└── status: "active"              (active → suspended → closed)
Each custodial wallet holds a single asset on one chain and has an on-chain address. The type field is internal for OMS-managed wallets. blockchainAsset resolves the on-chain identity of the asset (protocol, chain ID, and token ID). Creating a wallet: POST /customers/{customerId}/wallets with asset and chain. Returns the created wallet record. Listing wallets: GET /customers/{customerId}/wallets returns the customer’s wallets, one per asset and chain. Reading a balance: GET /wallets/{walletId}/balance returns the wallet’s current balance for its asset and chain, along with an estimated value in the requested currency. Funding a wallet: Send crypto directly to the wallet’s address, or use the on-ramp flows (cash-in, virtual accounts, deposit addresses) to convert fiat. Multiple wallets: A customer can have more than one wallet, for different currencies, use cases, or segregation needs.
At launch, OMS custodial wallets are internal (OMS-managed). Embedded wallets, provisioned through the same customer endpoint, are planned for a later release.

Virtual accounts

A bank account number wired to a customer’s wallet. Fiat deposits auto-convert to crypto.
Virtual Account
├── id: "va_..."
├── customerId: "cst_..."
├── destination: { ... }          (the wallet that receives the converted crypto)
└── bankDetails: {
     domestic: { accountNumber, routingNumber, ... },
     swift: { accountNumber, bic, memo, ... }
   }
Give your customers the bankDetails to display in your UI (null until the underlying deposit account is provisioned). A transfer to that account number delivers crypto to the configured wallet. When funds arrive, OMS creates a fiatAccountToCrypto transaction directly in processing. Virtual accounts are managed through the API: create with POST /virtual-accounts, list with GET /virtual-accounts, read and update with GET/PATCH /virtual-accounts/{id}. Deletion is asynchronous: DELETE /virtual-accounts/{id} returns 202 and initiates closure of the underlying deposit account, and the status finalizes to deleted once it closes. A sandbox POST /virtual-accounts/{id}/simulate endpoint exercises the inbound flow. See Virtual accounts.

Deposit addresses

A reusable on-chain address wired to a fiat destination. On-chain deposits auto-create a transaction.
Deposit Address
├── id: "da_..."
├── customerId: "cst_..."
├── destination: { ... }
└── depositInstructions: {
     address: "0x...",
     asset: "usdc",
     network: "polygon"
   }
Deposit addresses carry routing configuration, so any deposit to the inlet address is converted and delivered to the configured fiat destination. They are the pattern for “accept any amount from any sender” on-ramp flows. When funds arrive, OMS creates a cryptoToFiatAccount transaction directly in processing. Deposit addresses are managed through the API: create with POST /deposit-addresses (the destination is a registered bank external account), list with GET /deposit-addresses, read and update with GET/PATCH /deposit-addresses/{id}. There is no delete. depositInstructions is null until provisioning populates the OMS-owned inlet address, and re-pointing the destination to a healthy external account recovers an inactiveActionRequired address to active. Deposit addresses must be enabled for your project: contact us to enable them. A sandbox POST /deposit-addresses/{id}/simulate endpoint exercises the inbound flow. See Deposit addresses.

External accounts

Off-platform bank accounts, external wallets, and debit cards, referenced on a quote by their ID. A quote’s source is always an OMS wallet or a card, so banks are destinations. Each external account is referenced by an ID whose prefix identifies the account type:
PrefixType
ext_card_Debit card
ext_bankUs_US bank account (ACH, same-day ACH, wire, RTP)
ext_bankIban_IBAN bank account (international, SWIFT)
ext_bankCa_Canadian bank account
ext_wlt_External wallet
Register external accounts with POST /external-accounts. The body specifies an owner (a customer, or one of the customer’s counterparties), a type (bankUs, bankIban, bankCanada, card, or walletExternal), and exactly one per-type details object matching the type; debit cards register through this same endpoint. List with GET /external-accounts (requires customerId, with an optional counterpartyId filter), update label and metadata with PATCH, and remove with DELETE. When delivering to a raw on-chain wallet, a quote can also pass a blockchainAddress directly instead of an ext_wlt_ ID.
External accounts are not OMS-managed balances. OMS never holds funds in an external account; it is only a source or destination reference on a transaction.

Forward-looking

Embedded custodial wallets, provisioned alongside the internal wallets available today, are planned for an upcoming release.