> For the complete documentation index, see [llms.txt](https://developer.fraudcheck.co.za/llms.txt). Markdown versions of documentation pages are available by appending `.md` to page URLs; this page is available as [Markdown](https://developer.fraudcheck.co.za/credit-and-financial-risk/bank-account-verification.md).

# Bank Account Verification

The Bank Account Verification checks a South African bank account against live banking records. It confirms that the account exists, is open, accepts debits and credits, and that the account holder's ID and surname match the bank's records. Use it to validate beneficiary details before making a payment, debit order, or onboarding a new customer.

Three endpoints are available - pick the one that matches the holder's identification document:

| Holder identifies with | Endpoint                                        |
| ---------------------- | ----------------------------------------------- |
| South African ID       | `/account-verification/verify/sa-id`            |
| South African passport | `/account-verification/verify/sa-passport`      |
| Foreign passport       | `/account-verification/verify/foreign-passport` |

All three accept the same request shape and return the same response.

## When to use this

Use this service to:

* Verify a beneficiary account before making a payment or EFT
* Confirm a debit order will succeed before submitting it
* Validate customer banking details during onboarding
* Detect fraud where account details don't match the named account holder

## How it works

Submit the account holder's identity, name, and bank details in a single POST request. The service queries the bank in real time and returns a synchronous response containing six match flags (account found, ID match, surname match, account open, accepts debits, accepts credits) plus an overall pass/fail outcome. Results can be retrieved later by transaction ID.

| Property              | Value                                                                                      |
| --------------------- | ------------------------------------------------------------------------------------------ |
| Response type         | `Synchronous`                                                                              |
| Typical response time | 2–5s                                                                                       |
| Result retrieval      | Returned inline; also retrievable via `GET /account-verification/results/{transaction_id}` |
| Consent required      | `Yes`                                                                                      |
| Region                | South Africa                                                                               |

## Authentication

```http
Authorization: Bearer {your_access_token}
```

## Endpoints

```http
POST /account-verification/verify/sa-id
POST /account-verification/verify/sa-passport
POST /account-verification/verify/foreign-passport

GET  /account-verification/results/{transaction_id}
GET  /account-verification/status/{transaction_id}
GET  /account-verification/banks
POST /account-verification/validate-bank?bank_name={name}
```

## Request

### Headers

| Header          | Required | Value              |
| --------------- | -------- | ------------------ |
| `Authorization` | Yes      | `Bearer {token}`   |
| `Content-Type`  | Yes      | `application/json` |

### Body parameters

| Field                          | Type    | Required | Description                                                                      |
| ------------------------------ | ------- | -------- | -------------------------------------------------------------------------------- |
| `IdentityNo`                   | string  | Yes      | 13-digit SA ID number, or passport number                                        |
| `FirstName`                    | string  | Yes      | Account holder's first name                                                      |
| `LastName`                     | string  | Yes      | Account holder's surname                                                         |
| `BankName`                     | string  | Yes      | A supported bank code or explicit alias; see [Supported banks](#supported-banks) |
| `BankAccountNumber`            | string  | Yes      | Bank account number                                                              |
| `BankBranchCode`               | string  | No       | 6-digit branch code. If omitted or invalid, the configured bank code is used.    |
| `BankAccountType`              | string  | Yes      | One of `SAVINGS`, `CURRENT`, `TRANSMISSION`, `BOND`, `SUBSCRIPTION`              |
| `ConsentObtainedByDataSubject` | boolean | Yes      | Must be `true`. Confirms POPIA consent.                                          |
| `SecondName`                   | string  | No       | Middle name                                                                      |

### Example request

```json
{
  "IdentityNo": "8601015800086",
  "FirstName": "John",
  "SecondName": "David",
  "LastName": "Smith",
  "BankName": "FNB",
  "BankAccountNumber": "1234567890",
  "BankBranchCode": "250155",
  "BankAccountType": "SAVINGS",
  "ConsentObtainedByDataSubject": true
}
```

## Response

### Top-level fields

| Field                 | Type    | Description                                                               |
| --------------------- | ------- | ------------------------------------------------------------------------- |
| `transaction_id`      | string  | Unique identifier for this verification                                   |
| `success`             | boolean | Whether the request was processed                                         |
| `verification_passed` | boolean | `true` if the account was found and the ID matches (basic checks)         |
| `all_checks_passed`   | boolean | `true` only if **all six** checks pass - use this for payment decisioning |
| `screening_result`    | string  | `ACCEPT` (all checks pass) or `REJECT` (one or more failed)               |
| `account_found`       | boolean | Whether the account exists at the specified bank                          |
| `data`                | object  | Detailed match flags (see below)                                          |

### `data` fields - the six checks

| Field                       | `Yes` means                                            |
| --------------------------- | ------------------------------------------------------ |
| `account_found`             | The account exists at the specified bank and branch    |
| `id_number_match`           | Submitted ID number matches the account holder on file |
| `surname_match`             | Submitted surname matches the account holder on file   |
| `initials_match`            | Submitted initials match the account holder on file    |
| `account_open`              | The account is currently open                          |
| `account_dormant`           | **Inverse signal** - `No` is the desired value         |
| `account_open_three_months` | The account has been open for at least three months    |
| `account_accepts_debits`    | The account can receive debit orders                   |
| `account_accepts_credits`   | The account can receive credits / deposits             |

Other useful fields in `data`: `account_issuer` (the bank name returned), `account_type_return` (returned account type code), `branch_number`, and `account_number`.

### Example response - all checks passed

```json
{
  "success": true,
  "transaction_id": "d41d8cd98f00b204e9800998ecf8427e",
  "verification_passed": true,
  "all_checks_passed": true,
  "screening_result": "ACCEPT",
  "data": {
    "branch_number": "250155",
    "account_number": "1234567890",
    "account_type": "SAVINGS",
    "account_issuer": "FNB",
    "account_found": "Yes",
    "id_number_match": "Yes",
    "initials_match": "Yes",
    "surname_match": "Yes",
    "account_open": "Yes",
    "account_dormant": "No",
    "account_open_three_months": "Yes",
    "account_accepts_debits": "Yes",
    "account_accepts_credits": "Yes"
  }
}
```

### Example response - one check failed

```json
{
  "success": true,
  "transaction_id": "d41d8cd98f00b204e9800998ecf8427e",
  "verification_passed": true,
  "all_checks_passed": false,
  "screening_result": "REJECT",
  "data": {
    "account_found": "Yes",
    "id_number_match": "Yes",
    "surname_match": "No",
    "account_open": "Yes",
    "account_accepts_debits": "Yes",
    "account_accepts_credits": "Yes"
  }
}
```

## Success criteria

For payment operations, use the `all_checks_passed` flag. It is `true` only when **all** of the following are met:

| Check                   | Required value |
| ----------------------- | -------------- |
| Account found           | `Yes`          |
| ID number matches       | `Yes`          |
| Surname matches         | `Yes`          |
| Account open            | `Yes`          |
| Account accepts debits  | `Yes`          |
| Account accepts credits | `Yes`          |

If any single check fails, `all_checks_passed` is `false` and `screening_result` is `REJECT` - inspect the `data` object to see which check failed.

## Retrieving results later

Re-fetch a previously completed verification by transaction ID:

```http
GET /account-verification/results/{transaction_id}
GET /account-verification/status/{transaction_id}
```

The `results` endpoint returns the full response. The `status` endpoint returns a lightweight status check.

## Errors

| HTTP | Code            | Meaning                                                                    | Action                                     |
| ---- | --------------- | -------------------------------------------------------------------------- | ------------------------------------------ |
| 400  | `bad_request`   | Invalid `BankName`, `BankAccountType`, or missing `FirstName`/`SecondName` | Check supported values and required fields |
| 401  | `unauthorized`  | Token missing or expired                                                   | Refresh your access token                  |
| 403  | `forbidden`     | Account lacks access to this service                                       | Contact your account manager               |
| 422  | `invalid_input` | Required field missing or consent not `true`                               | See `details.field`                        |
| 500  | `server_error`  | Unexpected error                                                           | Contact support                            |

> A failed verification (e.g. ID mismatch) returns HTTP `200` with `screening_result: "REJECT"` - it is a successful API call with a negative outcome, not an error.

## Code examples

{% tabs %}
{% tab title="cURL" %}

```bash
BASE_URL="https://consumer-service-api.fraudcheckonline.co.za/consumer-service"

curl -X POST "$BASE_URL/account-verification/verify/sa-id" \
  -H "Authorization: Bearer $ACCESS_TOKEN" \
  -H "Content-Type: application/json" \
  -d '{
    "IdentityNo": "8601015800086",
    "FirstName": "John",
    "LastName": "Smith",
    "BankName": "FNB",
    "BankAccountNumber": "1234567890",
    "BankBranchCode": "250155",
    "BankAccountType": "SAVINGS",
    "ConsentObtainedByDataSubject": true
  }'
```

{% endtab %}

{% tab title="Python" %}

```python
import requests

BASE_URL = "https://consumer-service-api.fraudcheckonline.co.za/consumer-service"

response = requests.post(
    f"{BASE_URL}/account-verification/verify/sa-id",
    headers={
        "Authorization": f"Bearer {access_token}",
        "Content-Type": "application/json",
    },
    json={
        "IdentityNo": "8601015800086",
        "FirstName": "John",
        "LastName": "Smith",
        "BankName": "FNB",
        "BankAccountNumber": "1234567890",
        "BankBranchCode": "250155",
        "BankAccountType": "SAVINGS",
        "ConsentObtainedByDataSubject": True,
    },
)
result = response.json()

if result["all_checks_passed"]:
    print(f"✓ Account verified - safe to pay")
else:
    print(f"✗ Verification failed:")
    checks = result["data"]
    for field in ("account_found", "id_number_match", "surname_match",
                  "account_open", "account_accepts_debits", "account_accepts_credits"):
        if checks.get(field) != "Yes":
            print(f"  - {field}: {checks.get(field)}")
```

{% endtab %}

{% tab title="JavaScript (Node.js)" %}

```javascript
const BASE_URL = "https://consumer-service-api.fraudcheckonline.co.za/consumer-service";

const response = await fetch(`${BASE_URL}/account-verification/verify/sa-id`, {
  method: "POST",
  headers: {
    Authorization: `Bearer ${accessToken}`,
    "Content-Type": "application/json",
  },
  body: JSON.stringify({
    IdentityNo: "8601015800086",
    FirstName: "John",
    LastName: "Smith",
    BankName: "FNB",
    BankAccountNumber: "1234567890",
    BankBranchCode: "250155",
    BankAccountType: "SAVINGS",
    ConsentObtainedByDataSubject: true,
  }),
});
const result = await response.json();

if (result.all_checks_passed) {
  console.log("✓ Account verified - safe to pay");
} else {
  console.warn("✗ Verification failed", result.data);
}
```

{% endtab %}
{% endtabs %}

## Reference values

### Supported banks

* First National Bank
* Standard Bank
* ABSA Bank
* Nedbank
* African Bank
* Capitec Bank
* Investec Bank
* Bidvest Bank
* SASFIN Bank
* Discovery Bank
* Grindrod Bank
* GRO Bank
* Bank Zero
* Old Mutual Bank
* Standard Chartered
* Tyme Bank

### Supported account types

`SAVINGS`, `CURRENT`, `TRANSMISSION`, `BOND`, `SUBSCRIPTION`

## Compliance & consent

* Obtain explicit consent from the data subject before submitting a request.
* Set `ConsentObtainedByDataSubject` to `true` to confirm consent has been captured.
* Retain proof of consent and the `transaction_id` for each verification in line with POPIA requirements.

## FAQ

<details>

<summary>Which flag should I use to decide whether to pay an account?</summary>

Use `all_checks_passed`. It is `true` only when all six checks pass and is the safe signal for payment operations. `verification_passed` only confirms the account exists and the ID matches - it does not confirm the account can accept funds.

</details>

<details>

<summary>Why do I see HTTP 200 with <code>screening_result: "REJECT"</code>?</summary>

The API call succeeded - the bank was queried and returned a result - but one or more checks failed. Inspect the `data` object to see which.

</details>

<details>

<summary>Which endpoint should I use for foreign nationals?</summary>

Use `/account-verification/verify/foreign-passport` and submit the passport number in `IdentityNo`.

</details>

<details>

<summary>Are surname matches case-sensitive?</summary>

No, but spelling and accents must match the bank's records. Use the exact surname as it appears on the holder's ID document.

</details>

## Changelog

| Date       | Version | Change                            |
| ---------- | ------- | --------------------------------- |
| 2026-07-15 | v1.1    | Expanded the supported-bank list. |
| 2026-04-11 | v1.0    | Initial release.                  |
