---
updatedAt: 2026-08-16T03:03:46.000Z
agentTools:
  projectIndex: https://docs.wirexapp.com/llms.txt
---

# Bank Transfers

Estimate and execute a SEPA, ACH or Faster Payments transfer from a corporate bank account.

## Before You Start

Read the following guides before proceeding:

| Guide                                                               | Why                                        |
| ------------------------------------------------------------------- | ------------------------------------------ |
| [Getting Started](https://docs.wirexapp.com/docs/corporate-getting-started)                    | Platform overview and setup                |
| [Api Basics](https://docs.wirexapp.com/docs/corporate-api-basics)                              | Required headers and request configuration |
| [Authentication](https://docs.wirexapp.com/docs/corporate-authentication)                      | How to obtain the corporation token        |
| [Activating Bank Account Details](https://docs.wirexapp.com/docs/corporate-account-activation) | Where `account_id` comes from              |
| [Capabilities](https://docs.wirexapp.com/docs/corporate-capabilities)                          | Each rail is gated by its own capability   |

***

## Overview

A bank transfer sends fiat from a corporate bank account to a beneficiary, debiting a token balance from
the corporation wallet to fund it. The rail is not chosen by a field — it is implied by the
`account_id`, whose details type determines both the rail and the beneficiary fields required.

Two flows exist for the same outcome.

|                 | v1 / v2 bank transfer        | v3 corridor transfer                                                                                    |
| --------------- | ---------------------------- | ------------------------------------------------------------------------------------------------------- |
| **Rails**       | SEPA, ACH, Faster Payments   | Those three plus PIX, SPEI, FEDWIRE, SWIFT, CIPS, CHATS, FPS-HK, IMPS, InstaPay, BI-FAST, NIP, IPP, PSE |
| **Beneficiary** | Inline in the request body   | A stored recipient, by `recipient_id`                                                                   |
| **Steps**       | Estimate, execute            | Estimate, initiate, sometimes confirm                                                                   |
| **Endpoint**    | `POST /api/v1/bank/transfer` | `POST /api/v3/bank/initiate/{corridor}`                                                                 |
| **Guide**       | This page                    | [Corridor Transfers](https://docs.wirexapp.com/docs/corporate-corridor-transfers)                                                  |

Use v3 for anything beyond the three original rails, and whenever the beneficiary is already a stored
recipient. This page documents the v1 and v2 flow.

***

## Step 1: Estimate the Transfer

Two estimate endpoints exist. They differ in one respect:

|                                | `POST /api/v1/bank/transfer/estimate` | `POST /api/v2/bank/transfer/estimate`                                  |
| ------------------------------ | ------------------------------------- | ---------------------------------------------------------------------- |
| **Beneficiary in the request** | Not accepted                          | `recipient` and `recipient_account` accepted, and **required for ACH** |
| **Use when**                   | SEPA and Faster Payments              | ACH, or when you want the quote computed against the real beneficiary  |

Both require `TransactionSu` or `TransactionCreate`.

```
POST /api/v2/bank/transfer/estimate
```

**Request body:**

```json
{
  "account_id": "3fa85f64-5717-4562-b3fc-2c963f66afa6:64120850-73a1-4df5-a074-d463258c9deb",
  "amount": 1000.00,
  "reference": "Invoice 2024-0042",
  "tokens": [
    "0x0774164DC20524Bb239b39D1DC42573C3E4C6976"
  ],
  "recipient": {
    "first_name": "Alex",
    "last_name": "Grey"
  },
  "recipient_account": {
    "account_number": "12345678",
    "routing_number": "123456789",
    "bank_name": "The Bank of New York",
    "legal_address": {
      "line1": "10 Downing Street",
      "city": "London",
      "zip_code": "SW1A 2AA",
      "country": "GB"
    }
  }
}
```

| Field               | Type            | Required    | Description                                                                             |
| ------------------- | --------------- | ----------- | --------------------------------------------------------------------------------------- |
| `account_id`        | string          | Yes         | The composite `id` from `GET /api/v1/bank/accounts` — `<accountId>:<detailsId>`         |
| `amount`            | number          | Yes         | Amount in the account's currency                                                        |
| `tokens`            | array of string | No          | Token addresses to quote against. Every token available to the corporation when omitted |
| `reference`         | string          | No          | Comment attached to the transfer                                                        |
| `recipient`         | object          | Conditional | Beneficiary identity. Required for ACH                                                  |
| `recipient_account` | object          | Conditional | Beneficiary bank details. Required for ACH                                              |

**Response:**

```json
{
  "id": "3fa85f64-5717-4562-b3fc-2c963f66afa6",
  "amount": 1000.00,
  "currency": "EUR",
  "expires_at": 1704107700,
  "estimated_amounts": [
    {
      "amount": 1094.11,
      "precise_amount": "1094110000000000000000",
      "token_address": "0x0774164DC20524Bb239b39D1DC42573C3E4C6976",
      "token_symbol": "WUSD",
      "rate": 1.09411,
      "fee_amount": 0.5,
      "fee_precise_amount": "500000000000000000"
    }
  ]
}
```

| Field                            | Description                                                         |
| -------------------------------- | ------------------------------------------------------------------- |
| `id`                             | The estimation id. Pass it to the execute call as `estimation_id`   |
| `amount`                         | Amount that will be sent, in `currency`                             |
| `currency`                       | Currency of the bank account                                        |
| `expires_at`                     | Unix timestamp after which the estimate is no longer valid          |
| `estimated_amounts[]`            | One entry per token — what would be debited if that token is chosen |
| `estimated_amounts[].fee_amount` | Per-token fee. Empty when the estimator reports no per-token fee    |

> The estimate's field is `id`; the execute call's field is `estimation_id`. They are the same value
> under two names.

***

## Step 2: Execute the Transfer

```
POST /api/v1/bank/transfer
```

Requires `TransactionSu` or `TransactionCreate`.

**Request body:**

```json
{
  "account_id": "3fa85f64-5717-4562-b3fc-2c963f66afa6:64120850-73a1-4df5-a074-d463258c9deb",
  "token_address": "0x0774164DC20524Bb239b39D1DC42573C3E4C6976",
  "estimation_id": "3fa85f64-5717-4562-b3fc-2c963f66afa6",
  "reference": "Invoice 2024-0042",
  "recipient": {
    "first_name": "Alex",
    "last_name": "Grey"
  },
  "recipient_account": {
    "iban": "DE89370400440532013000",
    "bic": "COBADEFF"
  }
}
```

| Field               | Type   | Required    | Description                                                                |
| ------------------- | ------ | ----------- | -------------------------------------------------------------------------- |
| `account_id`        | string | Yes         | The composite account identifier. Determines the rail                      |
| `token_address`     | string | Yes         | Token to debit from the corporation wallet                                 |
| `recipient`         | object | Yes         | Beneficiary identity                                                       |
| `recipient_account` | object | Yes         | Beneficiary bank details. Fields required depend on the rail               |
| `estimation_id`     | string | Conditional | Required when `amount` is omitted. Uses the quoted rate                    |
| `amount`            | number | Conditional | Required when `estimation_id` is omitted. Executes at current market rates |
| `reference`         | string | No          | Comment attached to the transfer                                           |

**Response:**

```json
{
  "id": "64120850-73a1-4df5-a074-d463258c9deb"
}
```

| Field | Description                                                                 |
| ----- | --------------------------------------------------------------------------- |
| `id`  | Identifier of the executed transaction. It is the activity feed item's `id` |

> **Send `estimation_id`, not `amount`, unless you accept the market rate.** With `amount` the transfer
> executes at whatever the rate is at execution time, and the figure quoted to the corporation is not
> what settles. Sending neither is rejected with `400 ErrorMissingField`.

### Beneficiary Identity

| Field                                         | Required when                 |
| --------------------------------------------- | ----------------------------- |
| `recipient.first_name`, `recipient.last_name` | The beneficiary is a person   |
| `recipient.company_name`                      | The beneficiary is a business |

### Beneficiary Bank Details by Rail

The rail comes from the details type behind `account_id`, and it decides which fields are mandatory:

| Rail            | Required in `recipient_account`                     |
| --------------- | --------------------------------------------------- |
| SEPA            | `iban`, `bic`                                       |
| ACH             | `account_number`, `routing_number`, `legal_address` |
| Faster Payments | `account_number`, `sort_code`                       |

`legal_address` requires `line1`, `city`, `zip_code` and `country`; `line2` and `state` are optional.

> **`legal_address` is triggered by the ACH field pair, not by the rail.** Any request carrying both
> `account_number` and `routing_number` must also carry `legal_address`, or it is rejected with
> `recipient account.legal address is required for ACH`.

> An `account_id` whose details type is neither SEPA, ACH nor Faster Payments — a SPEI or SWIFT
> account — is rejected with `400 ErrorNotSupported` and `Invalid account id`. Use
> [Corridor Transfers](https://docs.wirexapp.com/docs/corporate-corridor-transfers) for those rails.

### Capability per Rail

The capability checked is the outbound one for the account's details type:

| Rail            | Capability checked                                                |
| --------------- | ----------------------------------------------------------------- |
| SEPA            | `SepaOut1stParty`                                                 |
| ACH             | `AchOut1stParty`                                                  |
| Faster Payments | `FasterPaymentsOut3rdParty`                                       |
| SPEI            | `SpeiOut1stParty` — reachable only through the corridor endpoints |

> The `1stParty` capabilities cover transfers to accounts the corporation owns. Faster Payments is
> checked against `FasterPaymentsOut3rdParty` instead. A corporation holding only
> `SepaOut3rdParty` cannot send a SEPA transfer through this endpoint — it needs `SepaOut1stParty`.

### Code

```javascript
const estimateResponse = await fetch(`${baseUrl}/api/v2/bank/transfer/estimate`, {
  method: 'POST',
  headers: {
    'Authorization': `Bearer ${corporationToken}`,
    'Content-Type': 'application/json'
  },
  body: JSON.stringify({ account_id: accountId, amount: 1000.00, tokens: [tokenAddress] })
});
const estimate = await estimateResponse.json();

const response = await fetch(`${baseUrl}/api/v1/bank/transfer`, {
  method: 'POST',
  headers: {
    'Authorization': `Bearer ${corporationToken}`,
    'Content-Type': 'application/json'
  },
  body: JSON.stringify({
    account_id: accountId,
    token_address: tokenAddress,
    estimation_id: estimate.id,
    reference: 'Invoice 2024-0042',
    recipient: { first_name: 'Alex', last_name: 'Grey' },
    recipient_account: { iban: 'DE89370400440532013000', bic: 'COBADEFF' }
  })
});
const { id: transactionId } = await response.json();
```
```python
estimate = requests.post(
    f"{base_url}/api/v2/bank/transfer/estimate",
    headers={
        "Authorization": f"Bearer {corporation_token}",
        "Content-Type": "application/json",
    },
    json={"account_id": account_id, "amount": 1000.00, "tokens": [token_address]},
).json()

response = requests.post(
    f"{base_url}/api/v1/bank/transfer",
    headers={
        "Authorization": f"Bearer {corporation_token}",
        "Content-Type": "application/json",
    },
    json={
        "account_id": account_id,
        "token_address": token_address,
        "estimation_id": estimate["id"],
        "reference": "Invoice 2024-0042",
        "recipient": {"first_name": "Alex", "last_name": "Grey"},
        "recipient_account": {"iban": "DE89370400440532013000", "bic": "COBADEFF"},
    },
)
transaction_id = response.json()["id"]
```
```go
estimateBody, _ := json.Marshal(map[string]interface{}{
    "account_id": accountID,
    "amount":     1000.00,
    "tokens":     []string{tokenAddress},
})

estimateReq, _ := http.NewRequest("POST", baseURL+"/api/v2/bank/transfer/estimate", bytes.NewBuffer(estimateBody))
estimateReq.Header.Set("Authorization", "Bearer "+corporationToken)
estimateReq.Header.Set("Content-Type", "application/json")

estimateResp, _ := http.DefaultClient.Do(estimateReq)
defer estimateResp.Body.Close()

var estimate BankTransferEstimateResponse
json.NewDecoder(estimateResp.Body).Decode(&estimate)

body, _ := json.Marshal(map[string]interface{}{
    "account_id":    accountID,
    "token_address": tokenAddress,
    "estimation_id": estimate.Id,
    "reference":     "Invoice 2024-0042",
    "recipient":     map[string]string{"first_name": "Alex", "last_name": "Grey"},
    "recipient_account": map[string]string{
        "iban": "DE89370400440532013000",
        "bic":  "COBADEFF",
    },
})

req, _ := http.NewRequest("POST", baseURL+"/api/v1/bank/transfer", bytes.NewBuffer(body))
req.Header.Set("Authorization", "Bearer "+corporationToken)
req.Header.Set("Content-Type", "application/json")

resp, _ := http.DefaultClient.Do(req)
defer resp.Body.Close()
```

***

## What Happens Next

1. The chosen token is debited from the corporation wallet.
2. The fiat transfer is submitted on the rail.
3. The movement appears in `GET /api/v1/activity/feed` under the returned `id`.
4. Progress is delivered to `POST {your_webhook_base_url}/v2/webhooks/activities`, and the debit to
   `/v2/webhooks/balances`.

Settlement time is the rail's, not the API's: Faster Payments is near-instant, SEPA is same or next
business day, ACH takes one to three business days.

***

## Error Handling

```json
{
  "error_reason": "ErrorInvalidField",
  "error_description": "Invalid format for account id",
  "error_category": {
    "category": "CategoryValidationFailure",
    "http_status_code": 400
  },
  "error_details": [
    { "key": "field", "details": "account_id" },
    { "key": "issue", "details": "invalid_format" }
  ]
}
```

### Validation Errors (400)

| Error Reason        | Error Details                             | Description                                           | Resolution                                                            |
| ------------------- | ----------------------------------------- | ----------------------------------------------------- | --------------------------------------------------------------------- |
| `ErrorMissingField` | `field: account_id`                       | `account id is required`                              | Send the composite id                                                 |
| `ErrorInvalidField` | `issue: invalid_format`                   | `Invalid format for account id`                       | The value must be two UUIDs joined by `:`                             |
| `ErrorNotFound`     | `issue: not_found_or_not_owned`           | `Invalid account id`                                  | The account does not belong to this corporation                       |
| `ErrorNotSupported` | `issue: unsupported_bank_account_type`    | `Invalid account id`                                  | The rail is not SEPA, ACH or Faster Payments. Use a corridor transfer |
| `ErrorInvalidField` | `field: token_address`                    | `token address must be a valid address`               | Send a token address from the estimate                                |
| `ErrorMissingField` | `field: amount`, `field: estimation_id`   | `amount is required when estimation id missing`       | Send one of them                                                      |
| `ErrorInvalidField` | `field: estimation_id`                    | `estimation id must be a valid UUID`                  | Send the estimate's `id`                                              |
| `ErrorMissingField` | `field: recipient_account.iban`           | SEPA requires an IBAN                                 | Send `iban` and `bic`                                                 |
| `ErrorMissingField` | `field: recipient_account.bic`            | SEPA requires a BIC                                   | As above                                                              |
| `ErrorMissingField` | `field: recipient_account.account_number` | ACH or Faster Payments requires an account number     | Send the rail's required pair                                         |
| `ErrorMissingField` | `field: recipient_account.sort_code`      | Faster Payments requires a sort code                  | Send `sort_code`                                                      |
| `ErrorMissingField` | `issue: required_for_ach`                 | `recipient account.legal address is required for ACH` | Send `legal_address` with `line1`, `city`, `zip_code`, `country`      |
| `ErrorGeneral`      | —                                         | `Capability is not active`                            | The outbound capability for this rail is not `Active`                 |

### Permission Errors (403)

| Error Reason            | Description                               | Resolution                                            |
| ----------------------- | ----------------------------------------- | ----------------------------------------------------- |
| `ErrorPermissionDenied` | `User does not have required permissions` | Transfers need `TransactionCreate` or `TransactionSu` |

### Server Errors (500)

| Error Reason   | Description       | Resolution                                                                                           |
| -------------- | ----------------- | ---------------------------------------------------------------------------------------------------- |
| `ErrorGeneral` | Estimate failure  | The rate service or bank service was unavailable. Retry                                              |
| `ErrorGeneral` | Execution failure | The transfer was not submitted. Re-estimate before retrying — the previous estimate may have expired |