Create an External Account
Create an External Account (saved payment destination). Provisions synchronously against the configured payment provider; the account starts pending and flips to active or failed. Exactly one per-type detail object must match type. The owner discriminator determines whether this account belongs to a customer directly or to one of the customer’s counterparties.
Uniqueness (walletExternal). A wallet is registered at most once per owner: the key is (owner, normalized blockchain address, network family). Consequences:
- Re-registering an address the same owner already holds
active,pendingorinvalidreturns409 externalAccountAlreadyRegistered, withdetails.existingAccountIdnaming that account anddetails.existingAccountStatustelling you what to do next: -"pending"means a registration is still in flight and this is TRANSIENT — the account resolves toactiveorfailedon its own (typically within ~30 minutes; see the stale-pending sweep), after which re-registering afailedaccount re-drives it. Retrying the create immediately will not help; pollGETonexistingAccountId, or simply wait and re-submit later. -"active"(or"invalid") is TERMINAL: use the account named byexistingAccountIdinstead of retrying the create. - Two different owners under the same customer (the customer itself and one of its counterparties, or two counterparties) may each register the same address — each gets its own account,201. - The same address under two different customers is likewise allowed. - An account infailedorrejectedstate does not block re-registration: submitting it again re-drives provisioning on that same account (sameid) and adopts the newly submittedlabelandmetadata, returning201. Deleted accounts free their key entirely.
Send an Idempotency-Key and retry with the SAME key after a network failure: the original response is replayed, so a 409 always means a genuinely different request collided with an existing account.
Authorizations
Token from POST /auth/token
Headers
Required on POST and PUT requests. Use a unique value per logical mutation attempt, for example a UUID.
Body
Create an External Account. Exactly one per-type detail object must be supplied, matching type; the service validates the correct subset and rejects mismatches with 422.
Who owns this account - { kind: customer, customerId } or { kind: counterparty, counterpartyId }. Cards must be customer-owned.
- Customer-owned
- Counterparty-owned
The instrument type. Exactly one matching per-type detail object must be supplied.
bankUs, bankIban, bankCanada, card, walletExternal Optional display label.
Free-form key-value pairs stored on the resource and echoed back on reads.
Required when type = bankUs.
Required when type = bankIban.
Required when type = bankCanada.
Required when type = walletExternal.
Response
The request has succeeded and a new resource has been created as a result.
A saved payment destination registered for a customer or one of their counterparties. Exactly one of the per-type response detail objects is populated, selected by type. Write-only secrets (full account number, full IBAN) are never present on reads - only their last-4 renderings.
External Account ID (ext_ prefix).
^[a-z]+_([0-9a-hjkmnp-tv-z]{26}|[0-9a-f]{8}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{12})$Resource type discriminator. Always "externalAccount".
externalAccount Who owns this account: the customer or one of their counterparties.
- Customer-owned
- Counterparty-owned
Server-rendered display name of the owning Customer — lets a list row render "who owns this account" without a second request. Additive next to the owner reference. Present only when owner.kind is customer; absent for counterparty-owned accounts.
Resolved id + display name of the owning customer. Present only when owner.kind = customer; absent for counterparty-owned accounts.
The instrument type; determines which detail object is populated.
bankUs, bankIban, bankCanada, card, walletExternal High-level grouping: fiatAccount for bank or card accounts, crypto for wallets. The same value as the instrument category when this account is referenced in a transaction.
fiatAccount, crypto Current lifecycle status. A transition to invalid always fires the externalAccount.statusChanged webhook.
active, pending, rejected, invalid, deleted, failed Set when status = failed.
ereborRejected, cardProviderRejected, providerAccountMissing, cardLimitReached, cardInUse, provisioningTimeout, systemError Structured provider rejection detail. Set when status = failed and the failure was a provider terminal rejection; absent for provisioning timeouts and internal failures.
Why registration was rejected. Present when status is rejected; the key is absent otherwise.
countryProhibited, cardVerificationDeclined, bankVerificationDeclined, countryNotSupported Why the account became unusable after activation, derived from a returned payout. Present when status is invalid; the key is absent otherwise.
accountClosed, accountFrozenByBank, routingOrAccountNumberInvalid, accountHolderDeceased, accountDoesNotSupportTransfers, payeeNameMismatch, walletUnreachableOnNetwork, billingAddressMismatch, cardExpired, cardClosedOrLostStolen, pushToCardUnsupported, cardDeclinedByIssuer The provider's own code for the failure behind invalidReason, verbatim — for US bank accounts the NACHA return code from the failed payout, e.g. R15. Absent when the provider gave no code, or when the account was invalidated by something other than a returned payment.
Read invalidReason to decide what to do; read this when you need the precise cause for support or reconciliation. invalidReason is a deliberately small set, so several codes map to one member — treat this field as an open vocabulary and do not switch on it. A return code that maps to no invalidReason leaves the account active and sets neither field; the returned payout still fails the transaction, but the unmapped code is not surfaced on the External Account.
Optional display label.
Free-form key-value pairs supplied at creation or update.
Populated when type = bankUs.
Populated when type = bankIban.
Populated when type = bankCanada.
Populated when type = walletExternal.
Populated when type = card.
Transaction ids that this registration submitted for sender-attribution release. Returned ONLY on the POST create response, and only when registering this walletExternal matched held inbounds. Attribution is async: each entry is submitted to the provider from awaitingAction.awaitingSenderAttribution and moves to processing.fundsPulled once settlement confirms — so an immediate GET of an id may still show awaitingAction. Omitted on GETs (the create path is the only writer).
Public TypeID, e.g. txn_01h455vb4pex5vsknk084sn02q; legacy UUID suffixes are accepted until non-v7 rows are retired.
^[a-z]+_([0-9a-hjkmnp-tv-z]{26}|[0-9a-f]{8}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{12})$When the external account was registered.
When the external account was last updated.