Before you start: the OMS API is in early access. Every endpoint, including the ones in this guide, requires an early-access API key. Request access before you begin.Authenticate by exchanging your API key and secret for a bearer token at
POST /auth/token, then send it as Authorization: Bearer {accessToken} on every request. Every mutating request (POST and PATCH) also requires an Idempotency-Key header. See Get started for the full flow.source is always an OMS wallet or a card, so there is no way to fund a quote with incoming fiat. Instead, fiat enters through a fiat-in product that auto-creates a transaction once the money arrives:
- Cash-in: a customer deposits physical cash at a retail location, and OMS delivers USDC to the destination wallet. The auto-created transaction has direction
cashToCrypto. - Virtual accounts: each customer gets a dedicated bank account number; an ACH, wire, or SWIFT deposit auto-converts to USDC. The auto-created transaction has direction
fiatAccountToCrypto.
POST /quotes for fiat-in.
The exception is debit card funding: a registered card is a valid quote source, so pull-from-card funding uses the standard quote and transaction flow rather than a fiat-in product. See Debit cards for the card source shape and settlementType.
Cash-in
Cash-in is a fiat-to-crypto path in the OMS API. You create a cash-in record that names the customer, the destination wallet, and the cash location where the customer will deposit. OMS returns estimated pricing upfront and finalizes it once the customer deposits cash at the counter.Create a cash-in
customerId: the customer making the deposit. Needs theusdendorsement to beACTIVE.cash.locationIdandcash.locationReference: the deposit location, taken from thelocIdandcashLocationReferencefields of theGET /cash-locationsresponse.source.asset: alwaysusd.source.indicatedAmount: the expected deposit amount, used for upfront pricing estimates. Optional; the actual amount is whatever the customer deposits at the counter.destination: the crypto destination. Setassetandnetwork, and identify the OMS wallet withwallet.id.sponsorGas: whentrue, OMS absorbs the gas cost of delivering crypto. At launch gas is always sponsored.
pricing object. When the customer deposits cash, OMS recalculates pricing on the actual amount, creates the cashToCrypto transaction, and delivers USDC to the destination wallet, firing cashIn.completed and the transaction.fiatToCrypto.* events along the way.
For the full lifecycle, pricing fields, location lookup, and webhook events, see the Cash-in guide.
Virtual accounts
Virtual accounts give each customer a dedicated bank account number. When the customer sends an ACH, wire, or SWIFT transfer to that number, OMS auto-converts the deposit to USDC and creates thefiatAccountToCrypto transaction. This suits recurring funding, where a customer tops up the same account repeatedly rather than deposits cash. Account provisioning requires the customer’s usd endorsement to be ACTIVE, and virtual accounts must be enabled for your project (contact us to enable them).
Create a virtual account
Create the account withPOST /virtual-accounts, naming the customer, the fiat source, and the wallet that receives the converted crypto:
source: the fiat side;assetmust beusdandnetworkmust beusBanktoday.destination: eitherwalletOmswith the customer’s walletid,asset, andnetwork, orwalletExternalwith theidof a registered external wallet account (ext_wlt_...) plusassetandnetwork. Raw blockchain addresses are not accepted.accountHolder: must becustomer.type: must bebankUs.bankMemo: an optional memo the customer can include on the transfer.
201 response carries the va_ ID with bankDetails set to null: OMS provisions the underlying deposit account asynchronously. Subscribe to the virtualAccount.provisioned event, or poll GET /virtual-accounts/{virtualAccountId} until status is active and bankDetails carries the domestic and SWIFT deposit instructions to share with your customer. When a transfer arrives, OMS credits the destination wallet and creates the transaction automatically, with no additional call from you; track it through the virtualAccount.deposit.* and transaction.fiatToCrypto.* events.
Test in sandbox
In sandbox you can rehearse the full flow without a real bank transfer. Simulate an inbound deposit withPOST /virtual-accounts/{virtualAccountId}/simulate, setting rail to ach_in, wire_in, or swift_in:
404 in production. It drives the same auto-created transaction and webhook flow (transaction.fiatToCrypto.*) a real deposit would, so you can test reconciliation before going live.
For the full lifecycle, statuses, updates, and deletion, see the Virtual accounts guide.
Key points
- Fiat-in never uses a quote. A quote’s source is always an OMS wallet or a card, so incoming fiat cannot fund a quote. Use cash-in or virtual accounts, which auto-create the transaction for you.
- The destination is a crypto wallet. Cash-in identifies it with
destination.wallet.id; a virtual account uses the side shape, withdestination.typeset towalletOmsorwalletExternaland the wallet ID indestination.details.id. - Direction is inferred. Cash-in produces a
cashToCryptotransaction; a virtual account produces afiatAccountToCryptotransaction. - The
usdendorsement gates fiat-in. Both cash-in and virtual accounts require the customer’susdendorsement to beACTIVE.