Account Verification (AV)
Confirm a bank account (IBAN) belongs to a given identity, and get a clear match / no-match result.
Goal: Verify that a bank account (IBAN) matches a supplied identity document, and get a clear match / no-match result.
Estimated time: 15 minutes
Prerequisites
- Sandbox credentials (App ID, API key) and an access token
- An IBAN plus an identity value to check against
When to use
- KYC enhancement during customer onboarding
- Verifying a payout account belongs to the expected person or business
- Reducing misdirected-payment and fraud risk before you transfer funds
What it confirms
Account Verification (AV) checks whether a KSA IBAN belongs to a person or business. Choose the endpoint based on the information you have:
| Endpoint | Use it when | Required inputs |
|---|---|---|
| ID Match | You know the identity type and value | iban, idType, idValue |
| Corporate | You have a company registration number, a UNN, or both | iban, plus at least one of companyRegistrationNumber or unn |
Both endpoints return the outcome (MATCH, NO_MATCH, or ERROR), the bank behind the IBAN, and the registered beneficiary name when the bank returns one.
For ID Match, idType must be NATIONAL_ID, IQAMA, COMPANY_REGISTRATION_NUMBER, or UNN.
Response
Every response uses the standard response structure. On success, the verification result comes in the data property of the response:
| Field | Description |
|---|---|
data.requestId | Unique identifier for this verification. Keep it for reconciliation and support |
data.result | MATCH, NO_MATCH, or ERROR |
data.bank | The bank behind the IBAN: englishName, arabicName, bankCode, swiftCode |
data.errorDescription | Why the check did not match, or why it failed. Empty on a match |
data.idType | The ID type that was checked |
data.idValue | The ID value that was checked |
data.beneficiaryName | The registered account holder, when the bank returns it |
data.executionDate | When the check ran, ISO 8601 date-time |
error | Set when the request itself fails: INVALID_DATA, ACCOUNT_VERIFICATION_FAILED, SUBSCRIPTION_EXPIRED, or SERVER_ERROR |
errorDescription | Per-field validation messages on a 400 |
A successful response returns HTTP 200. To determine whether the result is MATCH, NO_MATCH, or ERROR, use data.result rather than the HTTP status code. See the Account Verification API reference for the full response structure.
{
"data": {
"requestId": "9f1c1f34-6c6e-4a2f-9c1a-4f8e2b7d0a51",
"result": "MATCH",
"bank": {
"englishName": "ARAB NATIONAL BANK",
"arabicName": "Ψ§ΩΨ¨ΩΩ Ψ§ΩΨΉΨ±Ψ¨Ω Ψ§ΩΩΨ·ΩΩ",
"bankCode": "30",
"swiftCode": "ARNBSARI"
},
"idType": "NATIONAL_ID",
"idValue": "1083886659",
"beneficiaryName": "HAITHAM AL AYED",
"executionDate": "2026-01-15T09:24:11Z"
}
}Flow
Integration summary
Authenticate
Generate an API key in the dashboard under Settings β API Keys, label it, set its permissions, and store it somewhere safe. The key is shown once.
Then exchange your credentials for a Bearer access token, and refresh it before it expires. See Quick Start Setup.
Choose the endpoint
Use ID Match when you know the identity type and value. Use Corporate when you have a company registration number, a UNN, or both.
ID Match
POST /api/v2.0/av/AccountVerification/IdMatch
Send the IBAN and identity with your Bearer token:
curl -X POST https://sandbox.sparefinancial.sa/api/v2.0/av/AccountVerification/IdMatch \
-H "Authorization: Bearer YOUR_ACCESS_TOKEN" \
-H "Content-Type: application/json" \
-d '{
"iban": "SA0230825947458020058295",
"idType": "NATIONAL_ID",
"idValue": "1083886659"
}'const res = await fetch(
"https://sandbox.sparefinancial.sa/api/v2.0/av/AccountVerification/IdMatch",
{
method: "POST",
headers: {
Authorization: `Bearer ${accessToken}`,
"Content-Type": "application/json",
},
body: JSON.stringify({
iban: "SA0230825947458020058295",
idType: "NATIONAL_ID",
idValue: "1083886659",
}),
},
);
const { data } = await res.json();
// data.result is "MATCH", "NO_MATCH", or "ERROR"import requests
res = requests.post(
"https://sandbox.sparefinancial.sa/api/v2.0/av/AccountVerification/IdMatch",
headers={
"Authorization": f"Bearer {access_token}",
"Content-Type": "application/json",
},
json={
"iban": "SA0230825947458020058295",
"idType": "NATIONAL_ID",
"idValue": "1083886659",
},
)
data = res.json()["data"]
result = data["result"]import java.net.URI;
import java.net.http.HttpClient;
import java.net.http.HttpRequest;
import java.net.http.HttpResponse;
HttpClient client = HttpClient.newHttpClient();
String body = """
{
"iban": "SA0230825947458020058295",
"idType": "NATIONAL_ID",
"idValue": "1083886659"
}
""";
HttpRequest request = HttpRequest.newBuilder()
.uri(URI.create("https://sandbox.sparefinancial.sa/api/v2.0/av/AccountVerification/IdMatch"))
.header("Authorization", "Bearer " + accessToken)
.header("Content-Type", "application/json")
.POST(HttpRequest.BodyPublishers.ofString(body))
.build();
HttpResponse<String> response = client.send(request, HttpResponse.BodyHandlers.ofString());
JsonNode data = new ObjectMapper().readTree(response.body()).get("data");
String result = data.get("result").asText();using System;
using System.Net.Http;
using System.Text;
using System.Text.Json;
using System.Threading.Tasks;
using var client = new HttpClient();
var request = new HttpRequestMessage(
HttpMethod.Post,
"https://sandbox.sparefinancial.sa/api/v2.0/av/AccountVerification/IdMatch");
request.Headers.Add("Authorization", $"Bearer {accessToken}");
request.Content = new StringContent(
"{ \"iban\": \"SA0230825947458020058295\", \"idType\": \"NATIONAL_ID\", \"idValue\": \"1083886659\" }",
Encoding.UTF8,
"application/json");
var response = await client.SendAsync(request);
var body = await response.Content.ReadAsStringAsync();
using var doc = JsonDocument.Parse(body);
var data = doc.RootElement.GetProperty("data");
var result = data.GetProperty("result").GetString();package main
import (
"bytes"
"encoding/json"
"net/http"
)
payload := []byte(`{
"iban": "SA0230825947458020058295",
"idType": "NATIONAL_ID",
"idValue": "1083886659"
}`)
req, err := http.NewRequest(
http.MethodPost,
"https://sandbox.sparefinancial.sa/api/v2.0/av/AccountVerification/IdMatch",
bytes.NewReader(payload),
)
if err != nil {
panic(err)
}
req.Header.Set("Authorization", "Bearer "+accessToken)
req.Header.Set("Content-Type", "application/json")
res, err := http.DefaultClient.Do(req)
if err != nil {
panic(err)
}
defer res.Body.Close()
var body struct {
Data struct {
RequestID string `json:"requestId"`
Result string `json:"result"`
ErrorDescription string `json:"errorDescription"`
} `json:"data"`
}
json.NewDecoder(res.Body).Decode(&body)
result := body.Data.ResultCorporate
POST /api/v2.0/av/AccountVerification/Corporate
Send the IBAN and at least one of companyRegistrationNumber or unn. This example uses a company registration number:
curl -X POST https://sandbox.sparefinancial.sa/api/v2.0/av/AccountVerification/Corporate \
-H "Authorization: Bearer YOUR_ACCESS_TOKEN" \
-H "Content-Type: application/json" \
-d '{
"iban": "SA0255794102713415121120",
"companyRegistrationNumber": "7007599983"
}'const res = await fetch(
"https://sandbox.sparefinancial.sa/api/v2.0/av/AccountVerification/Corporate",
{
method: "POST",
headers: {
Authorization: `Bearer ${accessToken}`,
"Content-Type": "application/json",
},
body: JSON.stringify({
iban: "SA0255794102713415121120",
companyRegistrationNumber: "7007599983",
}),
},
);
const { data } = await res.json();
// Use data.result to determine the outcome.import requests
res = requests.post(
"https://sandbox.sparefinancial.sa/api/v2.0/av/AccountVerification/Corporate",
headers={
"Authorization": f"Bearer {access_token}",
"Content-Type": "application/json",
},
json={
"iban": "SA0255794102713415121120",
"companyRegistrationNumber": "7007599983",
},
)
data = res.json()["data"]
result = data["result"]import java.net.URI;
import java.net.http.HttpClient;
import java.net.http.HttpRequest;
import java.net.http.HttpResponse;
HttpClient client = HttpClient.newHttpClient();
String body = """
{
"iban": "SA0255794102713415121120",
"companyRegistrationNumber": "7007599983"
}
""";
HttpRequest request = HttpRequest.newBuilder()
.uri(URI.create("https://sandbox.sparefinancial.sa/api/v2.0/av/AccountVerification/Corporate"))
.header("Authorization", "Bearer " + accessToken)
.header("Content-Type", "application/json")
.POST(HttpRequest.BodyPublishers.ofString(body))
.build();
HttpResponse<String> response = client.send(request, HttpResponse.BodyHandlers.ofString());
JsonNode data = new ObjectMapper().readTree(response.body()).get("data");
String result = data.get("result").asText();using System;
using System.Net.Http;
using System.Text;
using System.Text.Json;
using System.Threading.Tasks;
using var client = new HttpClient();
var request = new HttpRequestMessage(
HttpMethod.Post,
"https://sandbox.sparefinancial.sa/api/v2.0/av/AccountVerification/Corporate");
request.Headers.Add("Authorization", $"Bearer {accessToken}");
request.Content = new StringContent(
"{ \"iban\": \"SA0255794102713415121120\", \"companyRegistrationNumber\": \"7007599983\" }",
Encoding.UTF8,
"application/json");
var response = await client.SendAsync(request);
var body = await response.Content.ReadAsStringAsync();
using var doc = JsonDocument.Parse(body);
var data = doc.RootElement.GetProperty("data");
var result = data.GetProperty("result").GetString();package main
import (
"bytes"
"encoding/json"
"net/http"
)
payload := []byte(`{
"iban": "SA0255794102713415121120",
"companyRegistrationNumber": "7007599983"
}`)
req, err := http.NewRequest(
http.MethodPost,
"https://sandbox.sparefinancial.sa/api/v2.0/av/AccountVerification/Corporate",
bytes.NewReader(payload),
)
if err != nil {
panic(err)
}
req.Header.Set("Authorization", "Bearer "+accessToken)
req.Header.Set("Content-Type", "application/json")
res, err := http.DefaultClient.Do(req)
if err != nil {
panic(err)
}
defer res.Body.Close()
var body struct {
Data struct {
RequestID string `json:"requestId"`
Result string `json:"result"`
ErrorDescription string `json:"errorDescription"`
} `json:"data"`
}
json.NewDecoder(res.Body).Decode(&body)
result := body.Data.ResultHandle the result
Use data.result to determine the outcome:
MATCH: the IBAN belongs to the holder of the ID you sent. Proceed.NO_MATCH: they do not belong together. Hold the payout and readdata.errorDescriptionfor the reason.ERROR: the check could not be completed. Readdata.errorDescription, then retry or fall back to manual review.
Store data.requestId on your side. It identifies the check in reconciliation and support requests. Handle request-level failures (400 invalid input, 401 authentication failure, 403 forbidden, 429 too many requests) separately from a successful response with data.result set to NO_MATCH.
Sandbox testing
The sandbox provides match and no-match combinations for retail and corporate accounts. For a no-match result, pair any valid ID with a listed no-match IBAN.
Match (retail)
| Identity | ID | IBAN |
|---|---|---|
| National ID | 1083886659 | SA02 3082 5947 4580 2005 8295 |
| National ID | 1106033093 | SA43 7875 7427 5654 2671 7936 |
| National ID | 1098765432 | SA41 6500 0000 202H 7183 0112 |
| Iqama ID | 2526303150 | SA02 0548 2877 1759 4474 3614 |
| Iqama ID | 2087654321 | SA82 6500 0000 303I 8294 1223 |
Match (corporate)
| Identity | ID | IBAN |
|---|---|---|
| CRN | 7007599983 | SA02 5579 4102 7134 1512 1120 |
| CRN | 7027586689 | SA30 8991 6678 6803 6895 3453 |
| CRN | 1010123456 | SA76 6500 0000 404J 9305 2334 |
| UNN | 7001393799 | SA02 1021 7220 3012 9444 5123 |
| UNN | 7010087117 | SA34 6048 3234 0864 9618 8141 |
| UNN | 7001234567 | SA73 6500 0000 505K 0416 3445 |
No-match (retail)
| Identity | ID | IBAN |
|---|---|---|
| National ID | 1009590330 | SA31 8332 8443 8118 6826 6901 |
| National ID | 2467146109 | SA31 8332 8443 8118 6826 6901 |
| Iqama ID | 2210802912 | SA83 7810 7145 6769 6592 3629 |
| Iqama ID | 2306183463 | SA58 9002 9478 9806 9743 8649 |
No-match (corporate)
| Identity | ID | IBAN |
|---|---|---|
| CRN | 7001711555 | SA76 9144 6091 1395 3372 1812 |
| CRN | 7006040238 | SA04 8308 0462 9107 2422 1083 |
| UNN | 7033051019 | SA27 8919 0306 0282 2557 3839 |
| UNN | 1010428195 | SA52 4004 5420 7029 2488 4337 |
Match and No-Match Results
The combinations in the table above return MATCH. A NO_MATCH means the IBAN and the ID you sent do not belong together, and data.errorDescription carries the reason. The full set of reasons:
- The IBAN and ID combination is invalid.
- The ID entered does not match Account Name.
- The type of account provided is not supported.
- The IBAN provided does not match the details on record.
- There is an issue with the Creditor Account Number.
- The bank account associated with this IBAN has been closed.
- This bank account associated with this IBAN is blocked.
- This bank account associated with this IBAN is sequestration.
- This bank account associated with this IBAN is in liquidation.
- This bank account owner associated with this IBAN is deceased.
- There is an issue with the IBAN provided.
- We don't support some of the details provided with the IBAN or ID.
Error Results
An ERROR result means the check could not be completed: the bank is not supported, or something failed on the bank's side or ours. data.errorDescription carries one of the following descriptions.
Retry limit
The last description above is a rate limit. An IBAN can be retried at most 10 times, after which it is blocked for 12 hours. Cache the result you already have instead of re-checking the same IBAN.
Run the KSA API in Postman
Import the latest KSA collection and call Account Verification without writing code.
Key takeaways
- Use ID Match for a person or a known identity type. Use Corporate when you have a company registration number, a UNN, or both.
- A successful response returns HTTP
200. Usedata.resultto determine the outcome, and readdata.errorDescriptionfor the reason behind a no-match or an error. - Use it before a payout or during onboarding to catch misdirected payments and impersonation.
- For beneficiary name lookup or a combined retail/corporate check, use Beneficiary Verification or AV Pro.