Fund via Plaid-Connected Bank Account
How do I fund a fiat account from a Plaid-connected bank account?
This guide walks you through funding a customer's fiat account from their Plaid-connected
external bank account.
Prerequisites
Before you begin, ensure you have:
- a customer already created and verified;
- a
fiataccount created for the customer; and, - a Plaid-connected external bank account linked to the customer. See
Add Customer External Bank Accounts.
Overview
The Plaid funding process involves three main steps:
- Create a
fundingquote - Execute a
transferwith the customer'sexternal_bank_account_guid - Monitor the transfer until it reaches the
completedstate
Step 1: Create a funding quote
funding quoteCreate a quote via POST /api/quotes:
{
"product_type": "funding",
"customer_guid": "customer_guid",
"asset": "USD",
"side": "deposit",
"deliver_amount": 10000
}
Base unitsThe
deliver_amountis in the base units of the currency. In the example above, 10,000 =
10,000 cents = $100.00 USD. CallGET /api/assetsto get the number of base decimals for a
currency.
deliver_amountvsreceive_amount
deliver_amountfixes what you send; fees come out of what the recipient gets.
receive_amountfixes what the recipient gets; you pay fees on top. See
Create and execute a trade
for the canonical explanation.
Set asset to USD, side to deposit, and deliver_amount to the desired amount. Once you
have your quote guid, proceed to the transfers API.
Step 2: Execute the transfer
transferYou need the customer's external_bank_account_guid for this step. Retrieve it by calling
GET /api/external_bank_accounts with the customer's guid.
Create a transfer via POST /api/transfers with transfer_type set to funding, the quote
guid, and the external_bank_account_guid:
{
"quote_guid": "quote_guid",
"transfer_type": "funding",
"external_bank_account_guid": "external_bank_account_guid",
"payment_rail": "ach",
"source_participants": [
{
"type": "customer",
"guid": "customer_guid",
"amount": 10000
}
],
"destination_participants": [
{
"type": "customer",
"guid": "customer_guid",
"amount": 10000
}
]
}
Multiple fiat accountsThis example omits
fiat_account_guidbecause it assumes onefiataccount per asset. If the
customer or bank is configured with multiplefiataccounts for the asset, you must include
fiat_account_guid. See
Specify a fiat account.
Sandbox limitExternal Bank Accounts with
account_kind=plaidare limited to $100.00 per funding transfer in sandbox.
Transfers exceeding $100.00 will fail. You can perform an unlimited number of transfers up to
$100.00.
Sandbox testingThe
expected_statefield is only available in sandbox to force specific transfer outcomes. Add
"expected_state": "pending"to the transfer request body to test state handling.
Payment rails
The Cybrid Platform defaults all USD transfers to same-day ACH ("payment_rail": "ach") and all CAD
transfers to EFT ("payment_rail": "eft").
Accounts can be funded using other payment rails — see
Fund via Deposit Bank Account.
Step 3: Monitor the transfer
Transfers are created asynchronously. Monitor the transfer until it reaches the completed state.
Settlement times depend on the payment_rail selected. For example, an ACH transfer requested at
4pm on a business day may not complete until 4pm the next business day. See
Fiat Transfer Settlement
for details.
Monitor transfer state by querying GET /api/transfers/{transfer_guid}, or register for
webhooks to receive automatic notifications on state
changes.
After creation, the transfer state is pending and progresses to completed when the fiat
settles.
Cancel a transfer
Transfers can only be cancelled between posting and bank execution — a small time window. No
API endpoint is available for partner-initiated cancellation.
Contact Cybrid support to request a transfer cancellation.
Handle Plaid errors
Creating a funding transfer using an External Bank Account of "account_kind": "plaid" or
"account_kind": "plaid_processor_token" can produce several errors.
The Cybrid Platform informs the caller of errors or required actions when using Plaid-connected
bank accounts.
Depending on the error received from Plaid, the associated External Bank Account for a transfer can be:
- Put into a
refresh_requiredstate. This occurs when the customer must re-authenticate to their bank
account via Plaid. When this does occur the partner must put their customer through a flow where Plaid
Link is initialized inupdatemode. See the guide
Refresh External Bank Accounts. The
associated External Bank Account cannot be used until the account is re-authorized. - Put into a
deletingand eventuallydeletedstate. This occurs when Plaid has lost access to the
customer's account completely or the customer has not granted sufficient permissions to Plaid to
access their account. When this does occur the partner must instruct the customer to connect a different
bank account via Plaid or to grant the required permissions to Plaid while interacting with Plaid Link.
Failure codes
When a funding transfer fails, the transfer state is failed with a failure_code.
Platform errors
| Failure code | Description | Action |
|---|---|---|
cancelled | The transfer was manually cancelled. | -- |
compliance_rejection | The transfer was rejected for compliance reasons. | -- |
internal_error | An internal error occurred. | Retry the transfer. |
limit_exceeded | The customer exceeded their activity limits. | -- |
non_sufficient_funds | The customer does not have enough funds. | -- |
party_name_invalid | The external bank account has an invalid party name. | -- |
payment_rail_invalid | The payment rail is not supported by the external bank account. | -- |
refresh_required | The external bank account must be reconnected via Plaid. | -- |
reversed | The transfer was reversed (returned). | -- |
return_risk_exceeded | The risk of return is too high based on factors such as Plaid Signal, account age, and account history. Risk screening flagged the transfer as likely to be returned by the originating bank (for example, due to insufficient funds, an unauthorized return, or account-related issues). | Try a lower amount, a different day, or a different bank account. For new or limited-history accounts, starting with smaller transactions may help. This failure cannot be overridden. |
Plaid errors
| Failure code | Description | Action |
|---|---|---|
plaid_access_not_granted | Permissions not granted. Account set to deleted. | Re-add via Plaid. Docs |
plaid_institution_not_responding | Balance could not be retrieved. | Retry. Docs |
plaid_internal_server_error | Balance could not be retrieved. | Retry. Docs |
plaid_item_not_found | Plaid lost access to the account. Account set to deleted. | Re-add via Plaid. Docs |
plaid_item_not_supported | Account not supported. Account set to deleted. | Connect a different account. Docs |
plaid_multiple_accounts | Multiple accounts selected in Plaid Link. Account set to deleted. | Select only one account. |
plaid_no_accounts | No compatible accounts found. Account set to deleted. | Docs |
plaid_none_balances_error | Error retrieving balances from Plaid. | Retry. |
plaid_unknown_error | Balance could not be retrieved. | Retry. Docs |
Troubleshooting non_sufficient_funds errors
non_sufficient_funds errorsIf a transfer fails with a non_sufficient_funds error but the customer confirms there are sufficient funds in their
account, the cached Plaid balance may be stale. Force a live balance refresh to confirm whether the cached value was
out of date — see Check an external bank account balance.
Compare the refreshed balance to the transfer amount. If the issue persists after refreshing, review your API request
and response logs for the transfer attempt, including any error codes or messages. If the problem continues, contact
support with detailed logs and request IDs for further investigation.
Check an external bank account balance
Retrieve a Plaid-connected external bank account's balance by calling
GET /api/external_bank_accounts/{external_bank_account_guid} with include_balances=true.
Cached vs live balances
By default, include_balances=true returns the cached balance, which may be stale. Add
force_balance_refresh=true to bypass the cache and pull the live balance directly from the customer's financial
institution through Plaid at request time:
GET /api/external_bank_accounts/{external_bank_account_guid}?include_balances=true&force_balance_refresh=true
The response includes a balance_updated_at timestamp indicating how fresh the returned balance is. The balances
object contains available (funds usable now) and current (including pending transactions), both in the base units
of the account's currency (for example, cents for USD).
When to force a refresh
Use force_balance_refresh=true only when you need the most up-to-date balance — for example, when
investigating a non_sufficient_funds failure that the customer disputes, to confirm whether the
cached balance was stale.
For routine or repeated balance displays, rely on the cached balance (include_balances=true without
force_balance_refresh).
Use forced refreshes sparinglyDo not poll this endpoint with
force_balance_refresh=true. Each forced refresh makes a live call to the financial
institution, which is slower and more costly than reading the cached balance.
Performance and limits
Because a forced refresh waits on a live Plaid call to the financial institution, it is noticeably slower than a
standard read and is subject to Plaid's rate limits. Set a generous client-side timeout and avoid blocking a
user-facing flow synchronously on the response. Setting include_pii=true adds similar latency, because it triggers an
additional third-party lookup.
Transient 429 Too Many Requests (rate limiting) or 5xx errors can occur under high load. For
429, honor a Retry-After header when present and cap retries. Use exponential backoff with jitter
for retries.
Balance refresh errors
A forced refresh can return a 422 response when the live balance cannot be retrieved:
| Response | Meaning | Action |
|---|---|---|
422 Financial institution did not respond (request_timeout) | The institution did not respond in time. | Usually transient. Retry later. |
422 Bank account refresh required | The connection has expired. The account moves to refresh_required and cannot be used until re-authenticated. | Put the customer through Plaid Link in update mode. See Refresh External Bank Accounts. |
422 Bank account can no longer be used and is being deleted. It must be re-added | The account is no longer valid and is being deleted. | Re-add the account via Plaid. |
Withdraw funds
To withdraw funds from a customer's fiat account to their external bank account,
create a quote with side set to withdrawal:
{
"product_type": "funding",
"customer_guid": "customer_guid",
"asset": "USD",
"side": "withdrawal",
"deliver_amount": 10000
}Then create a funding transfer via POST /api/transfers:
{
"quote_guid": "quote_guid",
"transfer_type": "funding",
"external_bank_account_guid": "external_bank_account_guid",
"payment_rail": "wire",
"beneficiary_memo": "Invoice #12345 payment",
"source_participants": [
{
"type": "customer",
"guid": "customer_guid",
"amount": 10000
}
],
"destination_participants": [
{
"type": "customer",
"guid": "customer_guid",
"amount": 10000
}
]
}The external_bank_account_guid can reference any type of external bank account — Plaid-connected or raw routing.
Beneficiary memo
The optional beneficiary_memo parameter sets the memo that appears on bank statements
and wire confirmations for the beneficiary.
Constraints:
- Maximum 128 characters
- Cannot be an empty string — use
nullor omit the field to skip - Only applies to withdrawal-direction funding transfers (not deposits, and not
other transfer types such as book, crypto, or lightning)

