SpareSpare Docs
GuidesAPI Reference

Account Verification (AV)

Confirm a bank account (IBAN) belongs to a given identity, and get a clear match / no-match result.

Integration guide

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:

EndpointUse it whenRequired inputs
ID MatchYou know the identity type and valueiban, idType, idValue
CorporateYou have a company registration number, a UNN, or bothiban, 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:

FieldDescription
data.requestIdUnique identifier for this verification. Keep it for reconciliation and support
data.resultMATCH, NO_MATCH, or ERROR
data.bankThe bank behind the IBAN: englishName, arabicName, bankCode, swiftCode
data.errorDescriptionWhy the check did not match, or why it failed. Empty on a match
data.idTypeThe ID type that was checked
data.idValueThe ID value that was checked
data.beneficiaryNameThe registered account holder, when the bank returns it
data.executionDateWhen the check ran, ISO 8601 date-time
errorSet when the request itself fails: INVALID_DATA, ACCOUNT_VERIFICATION_FAILED, SUBSCRIPTION_EXPIRED, or SERVER_ERROR
errorDescriptionPer-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.Result

Corporate

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.Result

Handle 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 read data.errorDescription for the reason.
  • ERROR: the check could not be completed. Read data.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)

IdentityIDIBAN
National ID1083886659SA02 3082 5947 4580 2005 8295
National ID1106033093SA43 7875 7427 5654 2671 7936
National ID1098765432SA41 6500 0000 202H 7183 0112
Iqama ID2526303150SA02 0548 2877 1759 4474 3614
Iqama ID2087654321SA82 6500 0000 303I 8294 1223

Match (corporate)

IdentityIDIBAN
CRN7007599983SA02 5579 4102 7134 1512 1120
CRN7027586689SA30 8991 6678 6803 6895 3453
CRN1010123456SA76 6500 0000 404J 9305 2334
UNN7001393799SA02 1021 7220 3012 9444 5123
UNN7010087117SA34 6048 3234 0864 9618 8141
UNN7001234567SA73 6500 0000 505K 0416 3445

No-match (retail)

IdentityIDIBAN
National ID1009590330SA31 8332 8443 8118 6826 6901
National ID2467146109SA31 8332 8443 8118 6826 6901
Iqama ID2210802912SA83 7810 7145 6769 6592 3629
Iqama ID2306183463SA58 9002 9478 9806 9743 8649

No-match (corporate)

IdentityIDIBAN
CRN7001711555SA76 9144 6091 1395 3372 1812
CRN7006040238SA04 8308 0462 9107 2422 1083
UNN7033051019SA27 8919 0306 0282 2557 3839
UNN1010428195SA52 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:

  1. The IBAN and ID combination is invalid.
  2. The ID entered does not match Account Name.
  3. The type of account provided is not supported.
  4. The IBAN provided does not match the details on record.
  5. There is an issue with the Creditor Account Number.
  6. The bank account associated with this IBAN has been closed.
  7. This bank account associated with this IBAN is blocked.
  8. This bank account associated with this IBAN is sequestration.
  9. This bank account associated with this IBAN is in liquidation.
  10. This bank account owner associated with this IBAN is deceased.
  11. There is an issue with the IBAN provided.
  12. 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.

Open in Postman

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. Use data.result to determine the outcome, and read data.errorDescription for 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.

On this page