Skip to main content
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 POST and PATCH also requires an Idempotency-Key header. See Get started for the full flow.
This guide shows you how to pay someone who is not your customer: a vendor, a payee, or any other third party. In OMS, that recipient is a counterparty, an entry in the customer’s address book that can own registered external accounts. Beneficiaries never need an OMS account of their own. The flow has four steps: create the counterparty, register their bank account, create a quote, and execute the transaction.

Prerequisites

  1. A customer with a cst_ ID and the usd endorsement: the payout is funded from this customer’s wallet.
  2. A funded OMS wallet with a wlt_ ID holding enough USDC to cover the payout.
  3. The recipient’s details: their legal name, and their bank account information.

Step 1: Create the counterparty

POST /counterparties requires only the owning customerId and the recipient’s name. The identity fields are optional but improve compliance screening: entityType (individual or business), email, phone, dateOfBirth, taxId, nationality, and an address.

Request

POST /counterparties
Authorization: Bearer {accessToken}
Idempotency-Key: ctp-acme-001
Content-Type: application/json
{
  "customerId": "cst_01H9Xa...",
  "name": "Acme Supplies LLC",
  "entityType": "business",
  "email": "billing@acme.example",
  "taxId": "98-7654321",
  "address": {
    "streetAddress": "500 Market St",
    "city": "San Francisco",
    "countryArea": "CA",
    "postalCode": "94105",
    "country": "US"
  }
}
The address shape is streetAddress, city, postalCode, and country (required when an address is supplied), plus an optional countryArea for the state or region.

Response, 201 Created

{
  "id": "ctp_01H9Xc...",
  "object": "counterparty",
  "customerId": "cst_01H9Xa...",
  "name": "Acme Supplies LLC",
  "entityType": "business",
  "email": "billing@acme.example",
  "taxId": "98-7654321",
  "address": {
    "streetAddress": "500 Market St",
    "city": "San Francisco",
    "countryArea": "CA",
    "postalCode": "94105",
    "country": "US"
  },
  "status": "active",
  "rejectionReason": null,
  "createdAt": "2026-07-07T10:00:00Z",
  "updatedAt": "2026-07-07T10:00:00Z"
}
Store the ctp_ ID. A counterparty that fails compliance screening comes back rejected with a rejectionReason; only active counterparties can own external accounts.

Step 2: Register the counterparty’s bank account

Register the recipient’s bank with POST /external-accounts, setting the owner to the counterparty. The per-type detail object must match the type: bankUs for US accounts, bankIban for international IBAN accounts, or bankCanada for Canadian accounts.

Request

POST /external-accounts
Authorization: Bearer {accessToken}
Idempotency-Key: ext-acme-bank-001
Content-Type: application/json
{
  "owner": { "kind": "counterparty", "counterpartyId": "ctp_01H9Xc..." },
  "type": "bankUs",
  "bankUs": {
    "accountNumber": "987654321012",
    "routingNumber": "021000021",
    "accountType": "checking",
    "bankName": "Chase"
  },
  "label": "Acme payouts"
}

Response, 201 Created

{
  "id": "ext_bankUs_01H9Xm...",
  "object": "externalAccount",
  "owner": { "kind": "counterparty", "counterpartyId": "ctp_01H9Xc..." },
  "type": "bankUs",
  "category": "fiatAccount",
  "status": "pending",
  "bankUs": {
    "accountNumberLast4": "1012",
    "routingNumber": "021000021",
    "accountType": "checking",
    "bankName": "Chase"
  },
  "label": "Acme payouts",
  "createdAt": "2026-07-07T10:01:00Z",
  "updatedAt": "2026-07-07T10:01:00Z"
}
The full account number is write-only; reads return accountNumberLast4. The account starts pending and flips to active (usable on quotes) or failed. Wait for active before quoting against it.

Step 3: Create the quote

The quote is identical to any other bank payout: the source is the customer’s OMS wallet, and the destination is the counterparty’s registered bank account referenced by its ext_bankUs_ ID.
POST /quotes
Authorization: Bearer {accessToken}
Idempotency-Key: qt-acme-payout-001
Content-Type: application/json
{
  "customerId": "cst_01H9Xa...",
  "source": {
    "type": "walletOms",
    "details": { "id": "wlt_01H9Xb...", "asset": "usdc", "network": "polygon" },
    "amount": "2500.00"
  },
  "destination": {
    "type": "bankUs",
    "details": {
      "id": "ext_bankUs_01H9Xm...",
      "asset": "usd",
      "network": "ach",
      "accountHolder": "customer"
    }
  }
}
On the response, OMS resolves the recipient’s identity onto the destination’s party block, so you can render who is being paid without extra reads:
"destination": {
  "party": {
    "relationship": "externalRegistered",
    "counterpartyId": "ctp_01H9Xc...",
    "entityType": "business",
    "name": "Acme Supplies LLC"
  },
  "type": "bankUs",
  "category": "fiatAccount",
  "details": { "id": "ext_bankUs_01H9Xm...", "asset": "usd", "network": "ach" }
}
All amounts, fees, and rates live in the top-level pricing object. The direction is inferred as cryptoToFiatAccount.

Step 4: Execute and track

Accept the quote with POST /transactions and the quoteId:
{ "quoteId": "qt_01H9Xq..." }
The transaction starts at processing and moves to completed when the bank payout settles, or failed with an error object. The payout leg surfaces granular progress in subStatus: processing.awaitingFiatOut while the payout is queued, then processing.fiatOut once it is in flight. Poll GET /transactions/{transactionId} or subscribe to transaction.cryptoToFiat.completed (and transaction.cryptoToFiat.failed); see the webhook events catalog for the full list. To reconcile a payout run, filter GET /transactions?customerId=...: each transaction’s destination party carries the counterpartyId it paid. Payouts are sent from named accounts: the recipient’s bank statement shows the paying customer’s own legal-entity name as the sender, not Polygon and not your platform.

Managing counterparties

  • GET /counterparties?customerId=... lists a customer’s counterparties (customerId is required). Paginate with limit, startingAfter, and endingBefore.
  • GET /counterparties/{counterpartyId} fetches one by ID.
  • PATCH /counterparties/{counterpartyId} partially updates identity fields; omitted fields are unchanged.
  • DELETE /counterparties/{counterpartyId} soft-deletes. It returns 409 while the counterparty still owns active external accounts: delete those accounts first, then delete the counterparty.