External Bank Accounts
Create bank-level external bank accounts using raw routing details on the Cybrid platform.
Bank-level external bank accounts
Partners can connect external bank accounts by providing raw routing details.
- The external bank account must be owned by KYC'd/KYB'd accounts on the Cybrid platform.
- Supported for bank-level external bank accounts only (not customer-level).
- Both sandbox and production environments require manual configuration.
Manual setup requiredContact partner support to complete the setup.
Account creation
You can create accounts for a Bank or a Customer.
To create accounts for your Bank, omit the customer_guid parameter in the request body. To create accounts for your Customers, include the customer_guid parameter in the request body.
ACH pull transfersOnly bank accounts connected via Plaid are supported for ACH pull transfers. Manual entry of bank account details is not supported for ACH pulls; Plaid connection is required.
Ownership verification from2026-09-01From API version
2026-09-01, the Platform verifies ownership of a customer-owned account during account creation. Ownership of a counterparty account is implied to be the counterparty's, and the bank attests a bank-level account. Send theAccept-Version: 2026-09-01header (or a later version) to onboard an account this way. On that version,account_kind: plaid_processor_tokenis not supported. For a customer-owned or bank-ownedaccount_kind: raw_routing_detailsrequest,counterparty_nameandcounterparty_addressare required as holder information. For a counterparty-owned account, you do not send them; see below.For an account owned by a customer or a counterparty, the owner must already be verified: counterparty verification for a counterparty, KYC or KYB for a customer. At banks that verify individuals non-documentarily, an individual customer whose attested personal details have passed may create the account before verification completes, because the account is what authenticates them.
Counterparty-owned accounts
A request that includes counterparty_guid, for an individual or a business counterparty, creates the account with the counterparty's own verified details from when the counterparty was created. Do not send counterparty_name, counterparty_address or counterparty_email_address: sending any of them returns 400 with invalid_parameter.
Ownership of a counterparty account is implied to be the counterparty's, so there is no ownership check: no holder-name match, no bank_account identity verification and no Persona inquiry. The account usually moves storing → pending → completed. If the counterparty matches an existing identity that needs a compliance review, it moves to reviewing and ends completed, failed with compliance_rejection, or expired. The counterparty must already be verified, or the request returns 400 with unverified_counterparty. In sandbox, expected_behaviours is accepted and ignored for counterparty accounts.
Customer-owned and bank-owned raw routing accounts
For a customer-owned account (customer_guid without counterparty_guid) or a bank-owned account_kind: raw_routing_details account, counterparty_name and counterparty_address are the holder information, and both are required; for a customer-owned account, they are the input to the ownership check. The address must meet your bank's counterparty address requirements for its country; country_code is always required. Optional fields are validated when supplied. Omitting either field returns 400 with invalid_parameter; sending either as an empty object returns 400 with missing_holder_info.
If the customer's holder-name match is inconclusive, the account stays pending while the customer completes a Persona inquiry, where they can upload a bank statement. You do not create an identity verification for this: the Platform creates a type: bank_account, method: account_ownership one. Find it with GET /api/identity_verifications?external_bank_account_guid=<guid>, and once it is waiting with a persona_inquiry_id, open the inquiry for the customer with POST /api/persona_sessions. When the customer completes the inquiry, the account moves to reviewing, then ends completed, failed with ownership_mismatch, or expired if the verification lapses.
Bank-level accounts
Bank-level accounts, created without a customer_guid or a counterparty_guid, have no owner to verify. The precondition does not apply to them and no automated ownership check runs, because the bank attests the account itself. The account moves storing → pending → completed; if the provider rejects the bank's legal-entity details, it waits in reviewing while Cybrid repairs them. Accounts created on earlier versions keep their original behavior. See API Versioning.
State
| State | Description |
|---|---|
storing | The Platform is storing the external bank account details in our private store |
pending | The Platform is verifying that the account owner owns the external bank account |
reviewing | The ownership check was inconclusive, or the account is held for compliance review or repair, before it completes, fails, or expires |
expired | The ownership verification or compliance review lapsed before it was completed, so the account was never verified |
completed | The Platform has created the external bank account |
unverified | The external bank account is created, but it has not yet been verified |
failed | The Platform was not able to successfully create the external bank account |
refresh_required | The Platform has created the external bank account, but needs to be refreshed |
deleting | The Platform is deleting the external bank account |
deleted | The Platform has deleted the external bank account |
API version requiredThe
pending,reviewing, andexpiredstates are returned from API version2026-09-01. Send theAccept-Version: 2026-09-01header (or a later version) to receive them. Earlier versions reportpendingandreviewingasstoring, because an account in either state is still in flight, and reportexpiredasfailed, because it is terminal. See API Versioning.
Read account details
These notes apply to any external bank account you read with its bank account details, whichever owner it belongs to — bank, customer or counterparty. Note two things about the routing entries returned.
routing_number and routing_number_type are nullable. They are null for an identifier-only account, such as a counterparty account addressed by an Interac e-Transfer email or phone number, where there is no routing number to return.
An account can return more than one routing entry. A Plaid-linked account may return separate ACH and wire entries that share an account number but carry different routing numbers. Use payment_rail on each entry to tell them apart rather than assuming a single entry.
Failure codes
| Code | Description |
|---|---|
invalid_routing_number | The provided routing number is invalid |
invalid_account_number | The account number is invalid |
invalid_account_type | The account type is invalid |
duplicate | An account with the same details already exists. This typically means the bank account has already been added previously, and attempting to add it again will result in a failed state with the duplicate failure code. |
plaid_processor_token | An account could not be created due to an invalid Plaid processor token or an error with Plaid. Applies to accounts onboarded before API version 2026-09-01; from 2026-09-01, a request with account_kind: plaid_processor_token is rejected outright with 422 unsupported_value and no account is created. |
plaid_multiple_accounts | The supplied Plaid token is associated with multiple accounts. Must only be a single account. |
create_failed | The bank account and associated holder could not be created correctly. On 2026-09-01 and later, also returned when a permanent provider or validation error ends account creation |
unverified_counterparty | The counterparty account is unverified |
ownership_mismatch | The account holder could not be shown to own the external bank account, either because the holder name did not match or because a reviewer denied the verification |
compliance_rejection | Compliance reviewed the account holder and declined the external bank account; contact Cybrid Support |
missing_holder_info | The account holder's details could not be determined. On a plaid account the linked financial institution did not return an account holder name; re-link the account or create it with account_kind: raw_routing_details. Returned only for customer-owned or bank-owned accounts. |
legal_entity_incomplete_pii | The financial institution could not create the account because the information held for the account holder is insufficient. For a counterparty-owned account, re-create the counterparty with the required information; for a customer-owned or bank-owned account, contact Cybrid Support — those records cannot be corrected through the API. |
API version requiredThe
ownership_mismatch,compliance_rejection, andmissing_holder_infocodes apply only to accounts created on API version2026-09-01or later. Send theAccept-Version: 2026-09-01header (or a later version) onPOST /api/external_bank_accountsto onboard an account this way. Ownership verification reportsownership_mismatch, a declined compliance review reportscompliance_rejection, and the account creation that follows reportsmissing_holder_info. A verification or compliance review that lapses before it completes leaves the account in theexpiredstate rather than reporting a failure code. A customer-owned or bank-ownedraw_routing_detailsrequest that suppliescounterparty_nameorcounterparty_addressas an empty object is instead rejected synchronously with400andmissing_holder_info, and no account is created; leaving either field out entirely returns400withinvalid_parameterfrom parameter validation. From2026-09-01, a permanent provider or validation error during account creation ends the account infailedwithcreate_failed, or withlegal_entity_incomplete_piiwhere the owner's legal-entity details are incomplete. A bank-owned account whose legal-entity details the provider rejects is the exception: it waits inreviewingwhile Cybrid repairs them. See API Versioning.
Updated 6 days ago

