> ## 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.

# External accounts

> Off-platform bank accounts, external wallets, and debit cards, referenced by ID on a quote.

External accounts are off-platform funding and payout endpoints: bank accounts, external wallets, and debit cards. Each is referenced by ID on a quote side. OMS never holds funds in an external account; it is only a source or destination reference on a transaction.

A quote's source is always an OMS wallet or a card, so banks are always destinations. Cards can be either a source (pull-from-card) or a destination (push-to-card). External wallets are crypto destinations.

An external account belongs either to a customer directly or to one of the customer's counterparties (a saved third-party payee). You register accounts with `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.

| ID prefix       | Type                                      | Role on a quote       |
| --------------- | ----------------------------------------- | --------------------- |
| `ext_bankUs_`   | US bank account (ACH, same-day ACH, wire) | Destination           |
| `ext_bankIban_` | IBAN bank account (international, SWIFT)  | Destination           |
| `ext_bankCa_`   | Canadian bank account                     | Destination           |
| `ext_wlt_`      | External wallet (held outside OMS)        | Destination           |
| `ext_card_`     | Debit card                                | Source or destination |

## Referencing an external account in a transaction

Reference the external account ID in the quote's `destination` (or `source`, for cards). The instrument is typed by `type`, and the `id` in `details` carries the `ext_` prefix.

### US bank destination

```json theme={null}
{
  "customerId": "cst_...",
  "source": {
    "type": "walletCrypto",
    "details": { "id": "wlt_...", "asset": "usdc", "network": "polygon" },
    "amount": "500.00"
  },
  "destination": {
    "type": "bankUs",
    "details": {
      "id": "ext_bankUs_...",
      "asset": "usd",
      "network": "ach",
      "accountHolder": "customer"
    }
  }
}
```

The US bank `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

```json theme={null}
{
  "destination": {
    "type": "walletExternal",
    "details": { "id": "ext_wlt_...", "asset": "usdc", "network": "polygon" }
  }
}
```

For an external wallet on a quote you supply exactly one of `id` (a registered external account, `ext_wlt_` prefix) or `blockchainAddress` (a raw on-chain address).

### Card source or destination

```json theme={null}
{
  "source": {
    "type": "card",
    "details": { "id": "ext_card_...", "asset": "usd" },
    "amount": "100.00"
  }
}
```

A card requires `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](/payments/debit-cards) for both directions.

## Registering external accounts

Register external accounts with `POST /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.

<CodeGroup>
  ```json US bank theme={null}
  {
    "owner": { "kind": "customer", "customerId": "cst_..." },
    "type": "bankUs",
    "bankUs": {
      "accountNumber": "123456789012",
      "routingNumber": "021000021",
      "accountType": "checking",
      "bankName": "Chase"
    },
    "label": "Primary checking"
  }
  ```

  ```json IBAN bank theme={null}
  {
    "owner": { "kind": "counterparty", "counterpartyId": "ctp_..." },
    "type": "bankIban",
    "bankIban": {
      "iban": "DE89370400440532013000",
      "BIC": "COBADEFFXXX"
    }
  }
  ```

  ```json Canadian bank theme={null}
  {
    "owner": { "kind": "customer", "customerId": "cst_..." },
    "type": "bankCanada",
    "bankCanada": {
      "institutionNumber": "003",
      "transitNumber": "12345",
      "accountNumber": "1234567"
    }
  }
  ```

  ```json Debit card theme={null}
  {
    "owner": { "kind": "customer", "customerId": "cst_..." },
    "type": "card",
    "card": {
      "cardNumber": "4111111111111111",
      "cvv": "123",
      "expiryMonth": 4,
      "expiryYear": 2028,
      "cardProvider": "visa",
      "billingAddress": {
        "addressLine1": "123 Main St",
        "city": "Austin",
        "state": "TX",
        "zipCode": "78701",
        "country": "US"
      }
    }
  }
  ```

  ```json External wallet theme={null}
  {
    "owner": { "kind": "customer", "customerId": "cst_..." },
    "type": "walletExternal",
    "walletExternal": {
      "blockchainAddress": "0xabc123...",
      "networkFamily": "evm",
      "custodian": "COINBASE_US"
    }
  }
  ```
</CodeGroup>

Per-type required and optional fields:

| Type             | Required                                                                               | Optional                                                                                                  |
| ---------------- | -------------------------------------------------------------------------------------- | --------------------------------------------------------------------------------------------------------- |
| `bankUs`         | `accountNumber`, `routingNumber` (nine-digit ABA)                                      | `accountType` (`checking` or `savings`, defaults to `checking` when omitted), `bankName`                  |
| `bankIban`       | `iban`, `BIC` (8 or 11 characters)                                                     | `countryCode` (derived from the IBAN's first two letters when omitted), `bankAddress`                     |
| `bankCanada`     | `institutionNumber` (three digits), `transitNumber` (five digits), `accountNumber`     | `bankName`                                                                                                |
| `card`           | `cardNumber`, `cvv`, `expiryMonth` (integer), `expiryYear` (integer), `billingAddress` | `cardProvider` (`visa`, `mastercard`, `amex`, or `discover`), `webSessionId` (fraud-screening session ID) |
| `walletExternal` | `blockchainAddress`, `networkFamily` (`evm` or `solana`), `custodian`                  | `otherCustodian` (required when `custodian` is `OTHER`)                                                   |

An external wallet is registered per network family: an EVM address registered once is valid across every EVM chain. See [Debit cards](/payments/debit-cards#registering-a-card) 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 explicit `countryCode`, 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:

```json theme={null}
{
  "error": "externalAccountAlreadyRegistered",
  "code": "externalAccountAlreadyRegistered",
  "details": {
    "existingAccountId": "ext_wlt_...",
    "existingAccountStatus": "active",
    "matchedOn": ["blockchainAddress", "networkFamily"]
  }
}
```

`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.

```json theme={null}
{
  "id": "ext_bankUs_...",
  "object": "externalAccount",
  "owner": { "kind": "customer", "customerId": "cst_..." },
  "type": "bankUs",
  "category": "fiatAccount",
  "status": "pending",
  "bankUs": {
    "accountNumberLast4": "9012",
    "routingNumber": "021000021",
    "accountType": "checking",
    "bankName": "Chase"
  },
  "label": "Primary checking",
  "createdAt": "2026-07-07T10:30:00Z",
  "updatedAt": "2026-07-07T10:30:00Z"
}
```

### Owner rendering

External account responses carry `ownerDisplayName`, 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:

```json theme={null}
"ownerDisplayName": "Jane Doe",
"customer": { "id": "cst_...", "name": "Jane Doe" }
```

`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](/payments/transactions#customer-summary-on-a-transaction) for the shared shape.

### Released sender-attribution holds

When you register a `walletExternal` 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-accounts` lists external accounts in the project. `customerId`, `counterpartyId`, and `status` are optional filters; omit them all to list every non-deleted account in the project. `status=deleted` explicitly lists soft-deleted rows. Paginate with `limit`, `startingAfter`, and `endingBefore`.
* `GET /external-accounts/{externalAccountId}` fetches a single account by ID.
* `PATCH /external-accounts/{externalAccountId}` updates `label` and `metadata` only. 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 with `400`.
* `DELETE /external-accounts/{externalAccountId}` soft-deletes the account and returns `204`.

## Statuses

| Status     | Meaning                                                                                                                                                                                                                           |
| ---------- | --------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- |
| `pending`  | Submitted to the provider; provisioning in progress.                                                                                                                                                                              |
| `active`   | Usable on quotes.                                                                                                                                                                                                                 |
| `rejected` | Refused at create time; `rejectionReason` identifies the cause (for example, a country prohibited by compliance screening, or an international destination whose country is outside OMS's SWIFT coverage, `countryNotSupported`). |
| `invalid`  | Became unusable after activation (for example, following an ACH payout return). `invalidReason` identifies the cause; see [Invalid external accounts](#invalid-external-accounts).                                                |
| `failed`   | Provisioning failed or the provider rejected the account; `failureReason` identifies the cause.                                                                                                                                   |
| `deleted`  | Soft-deleted.                                                                                                                                                                                                                     |

## Invalid external accounts

When a payout to an active external account is rejected with a confirmed rail-specific return code, OMS transitions the account to `status: invalid` and stamps a closed-enum `invalidReason` on it. The transition fires an `externalAccount.statusChanged` webhook, and every active [virtual account](/payments/virtual-accounts) or [deposit address](/payments/deposit-addresses) that references the invalidated account flips to `inactiveActionRequired` so it can no longer route new payouts to a known-bad destination.

| `invalidReason`                  | Meaning                                                                   |
| -------------------------------- | ------------------------------------------------------------------------- |
| `accountClosed`                  | The receiving bank reported the account closed.                           |
| `accountFrozenByBank`            | The receiving bank reported the account frozen.                           |
| `routingOrAccountNumberInvalid`  | The routing or account number was rejected as invalid.                    |
| `accountHolderDeceased`          | The receiving bank reported the account holder deceased.                  |
| `accountDoesNotSupportTransfers` | The receiving account cannot accept the requested transfer type.          |
| `payeeNameMismatch`              | The payee name on the payout did not match the account holder on file.    |
| `walletUnreachableOnNetwork`     | An on-chain destination was reported unreachable on the target network.   |
| `billingAddressMismatch`         | The card issuer rejected the transaction on billing-address verification. |
| `cardExpired`                    | The card is past its expiration date.                                     |
| `cardClosedOrLostStolen`         | The card issuer reported the card closed, lost, or stolen.                |
| `pushToCardUnsupported`          | The card does not support push-to-card payouts.                           |
| `cardDeclinedByIssuer`           | The card issuer declined the payout.                                      |

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](/payments/debit-cards): using a card as a funding source or payout destination
* [Bank transfers guide](/api-reference/guide-bank-transfers): ACH and wire transfer walkthrough
* [Transactions](/payments/transactions): how to use an external account on a quote
