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

# Open Money Stack API

> How the Open Money Stack API works: core concepts, transaction types, and API structure.

## What Is the Open Money Stack API?

The Open Money Stack API (OMS) is a unified interface for moving money across fiat rails, blockchains, and currencies. One integration gives you access to bank transfers, card payments, cash networks, and multiple blockchains.

***

## Three Ways to Move Money

OMS provides three mechanisms for money movement, each suited to different use cases:

| Mechanism             | What It Does                                                                                                                                                                                                                              | When to Use                                                                                  |
| --------------------- | ----------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- | -------------------------------------------------------------------------------------------- |
| **Transactions**      | Move money between crypto and fiat. OMS infers the direction from the source and destination instruments. Two-step flow: Create a Quote → Create a Transaction. Also includes **Cash-In**: a code-based flow for in-person cash deposits. | Amount known upfront (standard transactions) or determined at the counter (cash-in).         |
| **Deposit Addresses** | Persistent deposit configurations for crypto sources that auto-create transactions when funds arrive, paying out to a registered bank account.                                                                                            | "Send any amount to this address" flows. Amount unknown until deposit.                       |
| **Virtual Accounts**  | Dedicated bank account numbers that auto-convert incoming fiat to crypto.                                                                                                                                                                 | Customers who need a persistent bank account number for recurring fiat-to-crypto conversion. |

***

## How a Transaction Works

1. **Create a Quote.** `POST /quotes` with source, destination, and amount. OMS locks pricing and returns the quote in `open` status with a full fee breakdown.
2. **Create a Transaction.** Review the pricing, then call `POST /transactions` with the quote ID. OMS begins execution and the transaction moves to `processing`.
3. **Track.** Poll `GET /transactions/{id}` or listen for webhook events.

The direction (`sourceToDestination`, for example `cryptoToCrypto`, `fiatAccountToCrypto`, or `cryptoToFiatAccount`) is inferred from the source and destination instruments. If pricing expires before transaction creation, create a new quote.

Every transaction records a `customerId` (the quote owner and sender) and carries counterparty identity inline on the source and destination `party` block. The `party` discriminator distinguishes an OMS customer from a saved external counterparty or an unrecognized external party, so two-sided flows such as remittances and B2B payouts can identify each side without extra reads. To scope a listing to a specific customer, pass `?customerId=…` to `GET /transactions`.

See the [Fiat to Crypto](/payments/guides/fiat-to-crypto) and [Crypto to Fiat](/payments/guides/crypto-to-fiat) guides for full walkthroughs with request/response payloads and webhook events.

### Cash-In

Cash-In is a specialized transaction flow for in-person cash deposits at retail locations. Instead of the two-step quote/transaction flow, the developer creates a cash-in that generates a deposit code. The customer takes that code to a retail location, deposits cash, and OMS automatically converts it to crypto and delivers it to the destination wallet.

1. **Create.** `POST /cash-ins` with a customer, a cash source (location), and a crypto destination. OMS returns a deposit code valid for 1 hour.
2. **Deposit.** The customer presents the code at the location and deposits any amount of cash.
3. **Convert.** OMS receives the deposit, converts to the destination asset, and delivers crypto. The cash-in status moves to `completed` and a transaction record is auto-created.

The code can be refreshed via `POST /cash-ins/{id}/refresh` if it expires before the customer deposits. No source amount is specified at creation: the customer decides how much to deposit at the counter. The creation response includes `pricing` estimates so the developer can show the customer the expected amounts before they visit the location.

See the [Cash-In](/api-reference/guide-cash-in) guide for a full walkthrough.

***

## Deposit Addresses

Deposit Addresses are long-lived deposit configurations for crypto sources. Instead of the two-step quote/transaction flow, a deposit address monitors an onchain deposit address for incoming crypto. When funds arrive, OMS automatically creates and executes a transaction that pays out to a registered bank account. Deposit Addresses do not expire: once provisioned, OMS keeps them active for reuse across deposits.

Create one with `POST /deposit-addresses`, passing the `customerId`, the expected inbound crypto (`expectedSourceAsset`, `expectedSourceNetwork`), and a registered bank external account as the `destination`. Manage with `GET /deposit-addresses` (the `customerId` filter is optional), `GET /deposit-addresses/{depositAddressId}`, and `PATCH /deposit-addresses/{depositAddressId}`; re-pointing the destination to a healthy external account recovers an address from `inactiveActionRequired` back to `active`. There is no delete operation.

| Feature               | POST /quotes + POST /transactions | Deposit Addresses                                           |
| --------------------- | --------------------------------- | ----------------------------------------------------------- |
| Amount known upfront? | Yes: locked at creation           | No: determined when funds arrive                            |
| Source                | OMS wallet or card                | Incoming crypto                                             |
| Destination           | Wallet, bank, card, or cash       | Registered bank external account                            |
| Lifecycle             | One-shot                          | Persistent: reusable across deposits                        |
| Fees and pricing      | Top-level `pricing` object        | Top-level `pricing` object on each auto-created transaction |

`depositInstructions` is null on the create response and populates with the onchain inlet address once provisioning completes; re-fetch the address to read it. In sandbox, `POST /deposit-addresses/{id}/simulate` simulates an inbound deposit so you can test the auto-created transaction flow.

<Note>
  Deposit addresses must be enabled for your project. [Contact us](https://info.polygon.technology/get-early-access?utm_source=docs\&utm_medium=card\&utm_campaign=oms_access) to enable them.
</Note>

See the [Deposit Addresses](/payments/guides/deposit-addresses) guide for a full walkthrough.

***

## Virtual Accounts

Virtual Accounts give each customer a dedicated bank account number. When fiat arrives via the assigned bank rail, OMS automatically creates a transaction that converts it to the specified crypto asset and delivers it to the configured wallet.

Create one with `POST /virtual-accounts`, passing the `customerId`, a `source` (`{ "asset": "usd", "network": ... }`), a wallet `destination` (`walletOms` or a registered `walletExternal`), `accountHolder: "customer"`, and `type: "bankUs"`. Manage with `GET /virtual-accounts` (the `customerId` filter is optional), `GET`/`PATCH /virtual-accounts/{virtualAccountId}`, and `DELETE /virtual-accounts/{virtualAccountId}`. Deletion is asynchronous: the delete returns `202`, `deletionRequestedAt` is set, and the status finalizes to `deleted` once the underlying account closes. `bankDetails` is null until the underlying deposit account is provisioned. In sandbox, `POST /virtual-accounts/{id}/simulate` simulates an inbound bank deposit.

| Feature         | Deposit Addresses          | Virtual Accounts                                  |
| --------------- | -------------------------- | ------------------------------------------------- |
| Sources         | Crypto (onchain deposits)  | Bank only (fiat rail)                             |
| Deposit details | Blockchain address         | Dedicated bank account per customer               |
| Use case        | Crypto in, bank payout out | Persistent bank account number for fiat-to-crypto |
| ID prefix       | `da_`                      | `va_`                                             |

See the [Virtual Accounts](/payments/guides/virtual-accounts) guide for a full walkthrough.

***

## Transaction Lifecycle

Every transaction moves through a predictable set of statuses. The top-level `status` field is designed for programmatic branching, while the optional `subStatus` field provides finer operational detail.

**Transaction statuses:**

| Status           | Meaning                                                                                                                                                       |
| ---------------- | ------------------------------------------------------------------------------------------------------------------------------------------------------------- |
| `processing`     | Transaction created. Funds being pulled, execution underway.                                                                                                  |
| `awaitingAction` | Non-terminal. Blocked on developer, upstream, or compliance action (see the `hold` object for the reason and deadline). Returns to `processing` once cleared. |
| `completed`      | Funds delivered to destination.                                                                                                                               |
| `failed`         | Terminal failure. The `error` object describes what went wrong.                                                                                               |

**Quote statuses:**

| Status     | Meaning                                                           |
| ---------- | ----------------------------------------------------------------- |
| `open`     | Quote created with locked pricing. Awaiting transaction creation. |
| `accepted` | Quote accepted. Transaction has been created from this quote.     |
| `expired`  | Pricing expired before transaction creation. Create a new quote.  |

**Auto-created transactions** from Deposit Addresses, Virtual Accounts, and Cash-Ins skip the Quote step: they go directly to `processing` since there is no developer review step.

**Sub-statuses** (`subStatus`) provide operational granularity as status-scoped strings namespaced by their parent (for example `processing.fundsPulled`, `processing.cashPickupReady`, or `completed.cashPickupExpired`). Cash off-ramp lifecycle events surface here rather than as a top-level status. Developers can safely ignore `subStatus` and branch only on `status`.

**Webhook events** fire on every meaningful state change. Payloads include the full object, so polling is rarely necessary. Subscribe to the event types you want in the OMS Dashboard or via the Webhooks endpoints (`POST`/`GET` `/webhooks` and `GET`/`PATCH`/`DELETE` `/webhooks/{id}`); omit the events list or pass `["*"]` to receive all events. The full list of event types and the delivery envelope are documented in the [webhook events catalog](/api-reference/webhook-events).

***

## Key Concepts

### Wallets

OMS provides both **custodial** and **non-custodial** wallets, so you can pick the custody model that fits your product.

* **Custodial wallets** are created and managed through the OMS API (`POST /customers/{id}/wallets`). OMS holds the keys and executes all operations as server-to-server API calls, no user signing required. Each wallet holds a single asset on one chain and has an on-chain address; read its balance with `GET /wallets/{id}/balance`.
* **Non-custodial wallets** put key control in the user's hands and are provided by Polygon's embedded wallet infrastructure. They sit alongside the OMS API and are useful for consumer apps that need user-signed transactions or self-custody. See the [Wallets section](/wallets/custodial-wallets) for the full custody options.

### Customers

Customers represent the end users in your application. Each customer owns one or more wallets, and can hold deposit addresses, virtual accounts, counterparties, and external accounts. Customers are assigned endorsements that determine which features and financial operations they can access. Endorsements include `basic` (standard operations), `cryptoCustody` (crypto custody and advanced features), and `usd` (USD stablecoin operations).

### Assets and Networks

Every side of a transaction has an **asset** (what kind of money) and a **network** (how it moves). The same field structure works for both crypto and fiat. All values are lowercase.

| Asset  | Network    | Meaning                                                   |
| ------ | ---------- | --------------------------------------------------------- |
| `usdc` | `polygon`  | USDC on Polygon Chain                                     |
| `usdc` | `ethereum` | USDC on Ethereum mainnet                                  |
| `usd`  | `ach`      | US dollars via ACH bank transfer                          |
| `usd`  | `wire`     | US dollars via domestic wire transfer                     |
| `usd`  | `card`     | US dollars via debit card                                 |
| `usd`  | `cash`     | US dollars via physical cash at retail locations and ATMs |

For the documented list of supported assets and networks, see the [Currencies and rails](/payments/core-concepts/currencies-and-rails) reference page.

### Fees and Pricing

Economics live in a single top-level `pricing` object on both quotes and transactions. `pricing.source` and `pricing.destination` each report `amountGross` (before fees), `amountNet` (after fees), and a `feesDeducted` object with `total`, `developer`, `oms`, and `gas` components, all denominated in that side's asset. `pricing` also carries the rate pair (`exchangeRate`, `effectiveRate`), the asset `pair`, `fixedAmountSide` (which side the caller specified), `sponsorGas`, and `sponsorGasCost`. The core equation is: `pricing.source.amountNet × pricing.exchangeRate = pricing.destination.amountGross`. Developers who want to cover gas for their users can set `sponsorGas: true` on the quote request, which moves gas costs to `pricing.sponsorGasCost` (an out-of-band developer cost).

### External Accounts

External accounts represent off-platform funding and payout endpoints (bank accounts, debit cards, external wallets) that you reference by ID when creating a quote or transaction. Register them with `POST /external-accounts`: pass an `owner` (`{ "kind": "customer", "customerId": ... }` or `{ "kind": "counterparty", "counterpartyId": ... }`), a `type` (`bankUs`, `bankIban`, `bankCanada`, `card`, or `walletExternal`), and exactly one per-type object matching the type (for example `bankUs: { accountNumber, routingNumber, accountType, bankName }`). Sensitive values are write-only; responses return `accountNumberLast4` or `cardNumberLast4`. List with `GET /external-accounts?customerId=...` (required), update `label`/`metadata` with `PATCH`, and remove with `DELETE`.

External accounts use type-specific ID prefixes: `ext_card_` (debit cards), `ext_bankIban_` (IBAN banks), `ext_bankUs_` (US banks), `ext_bankCa_` (Canadian banks), `ext_wlt_` (external wallets).

### Counterparties

A counterparty is a third party a customer transacts with: an entry in the customer's address book, such as a vendor you pay. Create one with `POST /counterparties` (requires `customerId` and `name`; optional `entityType`, contact fields, and an `address` of `streetAddress`, `city`, `postalCode`, `country`). Counterparties can own external accounts through the `owner` field on registration. Deleting a counterparty that still owns active external accounts returns `409`.

***

## Get started

Get access, exchange your API key for a bearer token, then start calling endpoints.

<Steps>
  <Step title="Get API key access">
    The OMS API is in early access. API keys are granted on request, so request access and our team will reach out with sandbox and production credentials.

    <Card title="Request OMS access" icon="envelope" href="https://info.polygon.technology/get-early-access?utm_source=docs&utm_medium=card&utm_campaign=oms_access">
      Share your use case and we'll get you set up with API access.
    </Card>

    Once approved, open the OMS Dashboard, go to **API Keys**, and generate a key. A key is a name and secret pair; the secret is shown only once, so store it immediately.
  </Step>

  <Step title="Request an auth token">
    You do not send the API key directly on requests. Exchange the key and secret for a short-lived bearer token at `POST /auth/token`, then send that token as `Authorization: Bearer {accessToken}` on every other endpoint.

    <CodeGroup>
      ```bash Sandbox theme={null}
      curl -X POST https://sandbox-api.polygon.technology/v0.10/auth/token \
        -H "Content-Type: application/json" \
        -d '{
          "apiKey": "{api_key}",
          "apiSecret": "{api_secret}"
        }'
      ```

      ```bash Production theme={null}
      curl -X POST https://api.polygon.technology/v0.10/auth/token \
        -H "Content-Type: application/json" \
        -d '{
          "apiKey": "{api_key}",
          "apiSecret": "{api_secret}"
        }'
      ```
    </CodeGroup>

    The response returns an `accessToken` valid for 60 minutes. When a request returns `401`, the token has expired: request a new one and retry. The token endpoint is rate-limited per client IP; if it returns `429`, wait for the interval in the `Retry-After` response header before retrying.
  </Step>
</Steps>

With a token in hand, create your first quote or browse the full endpoint reference:

<CardGroup cols={2}>
  <Card title="Get started guide" icon="rocket" href="/payments/get-started">
    Zero to your first transaction, step by step.
  </Card>

  <Card title="Fiat to crypto" icon="receipt" href="/payments/guides/fiat-to-crypto">
    A full quote-to-transaction walkthrough with payloads and webhook events.
  </Card>
</CardGroup>

For a step-by-step walkthrough from zero to your first transaction, see the [Get started guide](/payments/get-started).
