POST /external-accounts and reference the returned ext_ ID on quotes.
Supported types
Each external account carries a type-specific ID prefix. The prefix tells you which instrument type to select on the quote side.Referencing an external account in a transaction
Reference the external account ID in the quote’sdestination (or source, for cards). The instrument is typed by type, and the id in details carries the ext_ prefix.
US bank destination
network is one of ach, achSameDay, or wire. The accountHolder field is always customer. IBAN (bankIban) and Canadian (bankCanada) destinations follow the same pattern with their respective ext_ IDs.
External wallet destination
id (a registered external account, ext_wlt_ prefix) or blockchainAddress (a raw on-chain address).
Card source or destination
asset: usd; its network is inferred server-side. Reference it as the source to pull from a card or as the destination to push to one. See Debit cards for both directions.
Registering external accounts
Register external accounts withPOST /external-accounts. The body carries an owner, a type, and exactly one per-type detail object whose key matches type; a mismatch is rejected with 422. Optional label and metadata fields round out the request.
The owner selects who the account belongs to:
{ "kind": "customer", "customerId": "cst_..." }for an account owned by the customer.{ "kind": "counterparty", "counterpartyId": "ctp_..." }for an address-book entry owned by one of the customer’s counterparties.
An external wallet is registered per network family: an EVM address registered once is valid across every EVM chain. See Debit cards for the card-specific details.
Provisioning runs synchronously against the configured payment provider: the account starts
pending and flips to active or failed.
SWIFT-supported destination countries
IBAN registrations are screened at create time against the list of countries OMS can pay out to over SWIFT. If the destination country (derived from the IBAN, an explicitcountryCode, or the BIC when neither is available) is not on that list, the create returns 201 with status: rejected and rejectionReason: countryNotSupported, and no vendor call is made. bankUs, walletExternal, and card registrations are not screened this way.
Duplicate submissions
POST /external-accounts and POST /external-accounts/cards deduplicate by owner. A duplicate registration for the same owner returns 409 externalAccountAlreadyRegistered; a registration of the same instrument under a different owner of the same customer returns a new 201 with a distinct ext_ ID.
For wallet and bank duplicates the 409 body carries a details object that identifies the existing record:
existingAccountStatus is the current status of the surviving record. active and pending both return 409, so a client cannot use retry to force a re-drive of a pending row: recovery from a stuck pending runs asynchronously through the stale-pending sweeper. failed and rejected rows are transparently re-driven under their existing ext_ ID and adopt the resubmitted label and metadata.
Card duplicates return the same 409 externalAccountAlreadyRegistered code with no details object, because the card provider’s dedupe is global and OMS cannot name a specific account.
Both create routes require the Idempotency-Key header and enforce replay: an identical retry with the same key returns the original response.
Response
Sensitive fields are write-only. Full account numbers, IBANs, card numbers, and CVVs are never stored on reads or echoed back; the response carries only their last-four renderings (accountNumberLast4, ibanLast4, cardNumberLast4). Exactly one per-type response object is populated, selected by type. For bankUs, accountType is always present on reads: checking when it was omitted at create time, otherwise the value you supplied.
Owner rendering
External account responses carryownerDisplayName, an opaque, ready-to-render label for the owner (for example, the customer’s or counterparty’s name). Customer-owned accounts additionally carry a customer summary:
customer is populated on GET /external-accounts, GET /external-accounts/{externalAccountId}, and PATCH /external-accounts/{externalAccountId} responses for customer-owned accounts. Counterparty-owned accounts populate ownerDisplayName from the counterparty and omit customer. See Customer summary on a transaction for the shared shape.
Released sender-attribution holds
When you register awalletExternal account whose address matches inbound transfers held for sender attribution, the create response includes resolvedTransactions: the IDs of the transactions the registration submitted for release. Attribution is asynchronous; each transaction moves from awaitingAction.awaitingSenderAttribution to processing once the provider confirms settlement, so an immediate GET on one of the IDs may still show awaitingAction. The field appears only on the POST create response, never on reads.
Managing external accounts
GET /external-accountslists external accounts in the project.customerId,counterpartyId, andstatusare optional filters; omit them all to list every non-deleted account in the project.status=deletedexplicitly lists soft-deleted rows. Paginate withlimit,startingAfter, andendingBefore.GET /external-accounts/{externalAccountId}fetches a single account by ID.PATCH /external-accounts/{externalAccountId}updateslabelandmetadataonly. Financial and identity fields are immutable; to change a destination, register a new account and re-point to it. Any other key in the body is rejected with400.DELETE /external-accounts/{externalAccountId}soft-deletes the account and returns204.
Statuses
Invalid external accounts
When a payout to an active external account is rejected with a confirmed rail-specific return code, OMS transitions the account tostatus: invalid and stamps a closed-enum invalidReason on it. The transition fires an externalAccount.statusChanged webhook, and every active virtual account or deposit address that references the invalidated account flips to inactiveActionRequired so it can no longer route new payouts to a known-bad destination.
The account also carries an
invalidReasonCode field with the provider’s own code for the underlying failure (for example, a NACHA return code such as R15 on a US bank ACH payout). Use invalidReason to decide what to do; use invalidReasonCode when you need the exact provider code for support or reconciliation.
The identifiers on an external account are immutable; there is no update path that can repair an invalid account. Register a new external account with the corrected details and re-point any dependent virtual accounts or deposit addresses to it to recover.
Wire and SWIFT rails do not currently populate invalidReason; a wire payout failure surfaces on the transaction itself, not on the destination external account.
Related
- Debit cards: using a card as a funding source or payout destination
- Bank transfers guide: ACH and wire transfer walkthrough
- Transactions: how to use an external account on a quote