> For clean Markdown of any page, append .md to the page URL.
> For a complete documentation index, see https://docs.crisscross.money/llms.txt.
> For AI client integration (Claude Code, Cursor, etc.), connect to the MCP server at https://docs.crisscross.money/_mcp/server.

# Verify a bank account

POST https://api.crisscross.money/v1/verification/bank-account
Content-Type: application/json

**Deprecated — use [`POST /accounts/validate`](#operation/validateAccount) with `accountType: bank_account` instead.**

Verifies a bank account before initiating a payout. Use this to confirm the account exists, is open, and optionally that the account holder identity or business registration matches. Reduces failed payouts and helps with reconciliation. Currently supported for South Africa (ZAR); use the same bank codes as for [ZAR payouts](/payout-market-south-africa#bank-codes-south-africa).


Reference: https://docs.crisscross.money/api-reference/payouts/verification/verify-bank-account

## Authentication

- `Authorization` header (bearer token, required) — OAuth 2.0 access token

## Request

### Body (application/json)

This endpoint expects a VerifyBankAccountRequest.

- `accountNumber` (string, required) — Bank account number to verify
- `bankCode` (string, required) — Bank identifier. For ZAR use a supported bank code (e.g. absa, standard_bank, fnb). See South Africa payout guide for the full list.
- `country` (enum, required) — 3-letter ISO country code (e.g., ZAF for South Africa)
  - Allowed values: `ZAF`
- `merchantId` (string, required) — Merchant ID for the verification request
- `accountType` (enum, optional) — Optional account type; verified if provided
  - Allowed values: `current`, `savings`
- `accountHolder` (VerifyBankAccountRequestAccountHolder, optional) — Optional account holder details. When provided, the API verifies that the account matches (e.g. name, identity or business registration).

## Response

### 200

Bank account verification result

- `verified` (boolean, required) — Overall verification result. True if the account is valid and open, and any provided identity/name/registration matched.
- `accountHolderName` (string, optional) — Account holder name as returned by the bank, when available
- `accountExists` (boolean, optional) — Whether the account exists at the bank
- `identityMatch` (boolean, optional) — Whether the provided identity document or business registration matched the account holder (only when accountHolder was provided)
- `accountOpen` (boolean, optional) — Whether the account is still open at the bank
- `accountAcceptsCredits` (boolean, optional) — Whether the account can receive credits (e.g. payouts)

## Errors

### 400 Bad Request Error

Bad Request - Invalid request parameters

- `message` (string, required) — Human-readable description of what went wrong.
- `error` (string, required) — Error category, typically the HTTP reason phrase (e.g. "Bad Request", "Unauthorized", "Conflict", "Unprocessable Entity", "Internal Server Error").
- `statusCode` (integer, required) — HTTP status code, mirroring the response header. The response header is the source of truth.

### 401 Unauthorized Error

Unauthorized - Invalid or missing authentication

- `message` (string, required) — Human-readable description of what went wrong.
- `error` (string, required) — Error category, typically the HTTP reason phrase (e.g. "Bad Request", "Unauthorized", "Conflict", "Unprocessable Entity", "Internal Server Error").
- `statusCode` (integer, required) — HTTP status code, mirroring the response header. The response header is the source of truth.

### 422 Unprocessable Entity Error

Request validation failed

- `message` (string, required) — Human-readable description of what went wrong.
- `error` (string, required) — Error category, typically the HTTP reason phrase (e.g. "Bad Request", "Unauthorized", "Conflict", "Unprocessable Entity", "Internal Server Error").
- `statusCode` (integer, required) — HTTP status code, mirroring the response header. The response header is the source of truth.

### 500 Internal Server Error

Internal server error

- `message` (string, required) — Human-readable description of what went wrong.
- `error` (string, required) — Error category, typically the HTTP reason phrase (e.g. "Bad Request", "Unauthorized", "Conflict", "Unprocessable Entity", "Internal Server Error").
- `statusCode` (integer, required) — HTTP status code, mirroring the response header. The response header is the source of truth.

## Types

### VerifyBankAccountRequestAccountHolder

Optional account holder details. When provided, the API verifies that the account matches (e.g. name, identity or business registration).

- `type` (enum, optional) — Type of account holder
  - Allowed values: `individual`, `business`
- `initials` (string, optional) — Individual's initials (optional; verified if provided for individual)
- `familyName` (string, optional) — Individual's family/last name, or business registered name (optional; verified if provided)
- `identityDocument` (VerifyBankAccountRequestAccountHolderIdentityDocument, optional) — South African ID document (for individual, local). Use either identityDocument or passport.
- `passport` (VerifyBankAccountRequestAccountHolderPassport, optional) — Passport (for individual, foreign). Use either identityDocument or passport.
- `registration` (VerifyBankAccountRequestAccountHolderRegistration, optional) — Business registration (for business account holder).
- `name` (string, optional) — Business name (optional; verified if provided for business)

### VerifyBankAccountRequestAccountHolderIdentityDocument

South African ID document (for individual, local). Use either identityDocument or passport.

- `country` (string, optional) — 2-letter ISO country code (e.g., ZA)
- `number` (string, optional) — ID number (e.g. 13-digit SA ID)

### VerifyBankAccountRequestAccountHolderPassport

Passport (for individual, foreign). Use either identityDocument or passport.

- `country` (string, optional) — 2-letter ISO country code
- `number` (string, optional) — Passport number

### VerifyBankAccountRequestAccountHolderRegistration

Business registration (for business account holder).

- `country` (string, optional) — 2-letter ISO country code
- `number` (string, optional) — Business registration number

## Examples

**Request**

```json
{
  "accountNumber": "4080499390",
  "bankCode": "absa",
  "country": "ZAF",
  "merchantId": "your-merchant-id"
}
```

**Response**

```json
{
  "verified": true,
  "accountHolderName": "J Clegg",
  "accountExists": true,
  "identityMatch": true,
  "accountOpen": true,
  "accountAcceptsCredits": true
}
```

**SDK Code**

```python
import requests

url = "https://api.crisscross.money/v1/verification/bank-account"

payload = {
    "accountNumber": "4080499390",
    "bankCode": "absa",
    "country": "ZAF",
    "merchantId": "your-merchant-id"
}
headers = {
    "Authorization": "Bearer <token>",
    "Content-Type": "application/json"
}

response = requests.post(url, json=payload, headers=headers)

print(response.json())
```

```javascript
const url = 'https://api.crisscross.money/v1/verification/bank-account';
const options = {
  method: 'POST',
  headers: {Authorization: 'Bearer <token>', 'Content-Type': 'application/json'},
  body: '{"accountNumber":"4080499390","bankCode":"absa","country":"ZAF","merchantId":"your-merchant-id"}'
};

try {
  const response = await fetch(url, options);
  const data = await response.json();
  console.log(data);
} catch (error) {
  console.error(error);
}
```

```go
package main

import (
	"fmt"
	"strings"
	"net/http"
	"io"
)

func main() {

	url := "https://api.crisscross.money/v1/verification/bank-account"

	payload := strings.NewReader("{\n  \"accountNumber\": \"4080499390\",\n  \"bankCode\": \"absa\",\n  \"country\": \"ZAF\",\n  \"merchantId\": \"your-merchant-id\"\n}")

	req, _ := http.NewRequest("POST", url, payload)

	req.Header.Add("Authorization", "Bearer <token>")
	req.Header.Add("Content-Type", "application/json")

	res, _ := http.DefaultClient.Do(req)

	defer res.Body.Close()
	body, _ := io.ReadAll(res.Body)

	fmt.Println(res)
	fmt.Println(string(body))

}
```

```ruby
require 'uri'
require 'net/http'

url = URI("https://api.crisscross.money/v1/verification/bank-account")

http = Net::HTTP.new(url.host, url.port)
http.use_ssl = true

request = Net::HTTP::Post.new(url)
request["Authorization"] = 'Bearer <token>'
request["Content-Type"] = 'application/json'
request.body = "{\n  \"accountNumber\": \"4080499390\",\n  \"bankCode\": \"absa\",\n  \"country\": \"ZAF\",\n  \"merchantId\": \"your-merchant-id\"\n}"

response = http.request(request)
puts response.read_body
```

```java
import com.mashape.unirest.http.HttpResponse;
import com.mashape.unirest.http.Unirest;

HttpResponse<String> response = Unirest.post("https://api.crisscross.money/v1/verification/bank-account")
  .header("Authorization", "Bearer <token>")
  .header("Content-Type", "application/json")
  .body("{\n  \"accountNumber\": \"4080499390\",\n  \"bankCode\": \"absa\",\n  \"country\": \"ZAF\",\n  \"merchantId\": \"your-merchant-id\"\n}")
  .asString();
```

```php
<?php
require_once('vendor/autoload.php');

$client = new \GuzzleHttp\Client();

$response = $client->request('POST', 'https://api.crisscross.money/v1/verification/bank-account', [
  'body' => '{
  "accountNumber": "4080499390",
  "bankCode": "absa",
  "country": "ZAF",
  "merchantId": "your-merchant-id"
}',
  'headers' => [
    'Authorization' => 'Bearer <token>',
    'Content-Type' => 'application/json',
  ],
]);

echo $response->getBody();
```

```csharp
using RestSharp;

var client = new RestClient("https://api.crisscross.money/v1/verification/bank-account");
var request = new RestRequest(Method.POST);
request.AddHeader("Authorization", "Bearer <token>");
request.AddHeader("Content-Type", "application/json");
request.AddParameter("application/json", "{\n  \"accountNumber\": \"4080499390\",\n  \"bankCode\": \"absa\",\n  \"country\": \"ZAF\",\n  \"merchantId\": \"your-merchant-id\"\n}", ParameterType.RequestBody);
IRestResponse response = client.Execute(request);
```

```swift
import Foundation

let headers = [
  "Authorization": "Bearer <token>",
  "Content-Type": "application/json"
]
let parameters = [
  "accountNumber": "4080499390",
  "bankCode": "absa",
  "country": "ZAF",
  "merchantId": "your-merchant-id"
] as [String : Any]

let postData = JSONSerialization.data(withJSONObject: parameters, options: [])

let request = NSMutableURLRequest(url: NSURL(string: "https://api.crisscross.money/v1/verification/bank-account")! as URL,
                                        cachePolicy: .useProtocolCachePolicy,
                                    timeoutInterval: 10.0)
request.httpMethod = "POST"
request.allHTTPHeaderFields = headers
request.httpBody = postData as Data

let session = URLSession.shared
let dataTask = session.dataTask(with: request as URLRequest, completionHandler: { (data, response, error) -> Void in
  if (error != nil) {
    print(error as Any)
  } else {
    let httpResponse = response as? HTTPURLResponse
    print(httpResponse)
  }
})

dataTask.resume()
```