# BVN Verification

> Validate a Bank Verification Number through NIBSS: boolean match without PII, or the full record with subject consent.

Checks a Bank Verification Number through NIBSS. Use `boolean` for the common bank flow: you collect the customer's details, send them with the BVN, and get back which ones are correct, without receiving any personal data. Use `core` only when you need the full BVN record; it requires the customer's consent through the NIBSS Consent Hub.

`POST /v1/verify/bvn`

- **Availability:** Sandbox and live
- **Indicative price:** ₦100.00 per completed check
- **Consent:** `boolean`: your attestation. `core`: a consent granted by the data subject ([Consent](https://speedmap-delta.vercel.app/docs/consent)).
- **MCP tool:** `verify_bvn` (boolean method only)

## Methods

Choose a method with the `method` field. Each one has its own required fields and consent mechanism.

| method | Description | Required fields |
| --- | --- | --- |
| `boolean` | Match / no-match against the BVN record. Returns no PII. The cheapest BVN check. | `bvn`, `reason`, `consent`, `match` |
| `core` | Full BVN record. Requires a consent granted by the subject through POST /v1/consents. | `bvn`, `reason`, `consent_id` |

## Request

**Body parameters (method: `boolean`)**

Match / no-match against the BVN record. Returns no PII. The cheapest BVN check.

| Field | Type | Required | Description |
| --- | --- | --- | --- |
| `method` | string | Yes | Always `boolean`. |
| `bvn` | string | Yes | Format: 11 digits. |
| `reason` | string | Yes | Purpose of the check. One of the 38 NIMC purpose codes. See the [purpose code list](https://speedmap-delta.vercel.app/docs/compliance#purpose-codes). |
| `consent` | object | Yes | Your attestation that you obtained the data subject's consent for this purpose. |
| `consent.obtained` | boolean | Yes | Always `true`. |
| `match` | object | Yes | Optional values to compare against the record. Each comparison is returned in field_matches; match is true only if all of them agree. |
| `match.first_name` | string | No | Must not be empty. |
| `match.middle_name` | string | No | Must not be empty. |
| `match.last_name` | string | No | Must not be empty. |
| `match.date_of_birth` | string | No | Format: date, `YYYY-MM-DD`. |

**Body parameters (method: `core`)**

Full BVN record. Requires a consent granted by the subject through POST /v1/consents.

| Field | Type | Required | Description |
| --- | --- | --- | --- |
| `method` | string | Yes | Always `core`. |
| `bvn` | string | Yes | Format: 11 digits. |
| `reason` | string | Yes | Purpose of the check. One of the 38 NIMC purpose codes. See the [purpose code list](https://speedmap-delta.vercel.app/docs/compliance#purpose-codes). |
| `consent_id` | string | Yes | Format: starts_with. |
| `match` | object | No | Optional values to compare against the record. Each comparison is returned in field_matches; match is true only if all of them agree. |
| `match.first_name` | string | No | Must not be empty. |
| `match.middle_name` | string | No | Must not be empty. |
| `match.last_name` | string | No | Must not be empty. |
| `match.date_of_birth` | string | No | Format: date, `YYYY-MM-DD`. |

- `boolean` needs at least one field in `match`. You can send up to six: `first_name`, `middle_name`, `last_name`, `date_of_birth`, `phone` and `gender`. Sending all six gives the strongest check.
- Each field you send comes back in `field_matches` as `true` or `false`, so you can see exactly what was wrong (often a misspelt middle name). `match` is `true` only if all of them agree.
- `core` needs a `consent_id` from `POST /v1/consents` whose status is `granted`, for the same BVN, within 24 hours of the grant.

Send an `Idempotency-Key` header so a retried request is never billed twice. See [Idempotency](https://speedmap-delta.vercel.app/docs/idempotency).

_cURL_

```bash
curl -X POST http://localhost:4000/v1/verify/bvn \
  -H "Authorization: Bearer sk_test_..." \
  -H "Idempotency-Key: $(uuidgen)" \
  -H "Content-Type: application/json" \
  -d '{
  "method": "boolean",
  "bvn": "22222222221",
  "reason": "financialProducts",
  "consent": {
    "obtained": true
  },
  "match": {
    "first_name": "Clarke",
    "last_name": "Griffin",
    "date_of_birth": "1992-04-18"
  }
}'
```

_Node.js_

```javascript
const res = await fetch("http://localhost:4000/v1/verify/bvn", {
  method: "POST",
  headers: {
    Authorization: `Bearer ${process.env.SPEEDMAP_API_KEY}`,
    "Content-Type": "application/json",
    "Idempotency-Key": crypto.randomUUID(),
  },
  body: JSON.stringify({
    "method": "boolean",
    "bvn": "22222222221",
    "reason": "financialProducts",
    "consent": {
      "obtained": true
    },
    "match": {
      "first_name": "Clarke",
      "last_name": "Griffin",
      "date_of_birth": "1992-04-18"
    }
  }),
});

const verification = await res.json();
if (!res.ok) throw new Error(verification.error.message);
console.log(verification.status, verification.match);
```

_Python_

```python
import os, uuid, requests

res = requests.post(
    "http://localhost:4000/v1/verify/bvn",
    headers={
        "Authorization": f"Bearer {os.environ['SPEEDMAP_API_KEY']}",
        "Idempotency-Key": str(uuid.uuid4()),
    },
    json={
        "method": "boolean",
        "bvn": "22222222221",
        "reason": "financialProducts",
        "consent": {
            "obtained": True
        },
        "match": {
            "first_name": "Clarke",
            "last_name": "Griffin",
            "date_of_birth": "1992-04-18"
        }
    },
    timeout=30,
)
res.raise_for_status()
verification = res.json()
print(verification["status"], verification["match"])
```

## Response

A completed check returns `200` with a [verification object](https://speedmap-delta.vercel.app/docs/verifications). `status` is `verified` or `not_found`. If the provider has not answered in time you get `202` with `status: "pending"`; [retrieve it later](https://speedmap-delta.vercel.app/docs/verifications#pending-verifications) or wait for the `verification.completed` [webhook](https://speedmap-delta.vercel.app/docs/webhooks).

**200 OK · assertion**

```json
{
  "reference_id": "spm_8171f3a87cbe221341f7185f",
  "object": "verification",
  "product": "bvn",
  "method": "bvn_boolean",
  "status": "verified",
  "match": true,
  "field_matches": {
    "first_name": true,
    "last_name": true,
    "date_of_birth": true,
    "phone": true
  },
  "field_confidence": null,
  "mode": "sandbox",
  "reason": "financialProducts",
  "subject": "*******2221",
  "summary": {
    "match": true
  },
  "consent": {
    "mechanism": "client_attested",
    "consent_id": null,
    "timestamp": "2026-10-19T09:41:07.512Z"
  },
  "source": "api",
  "error": null,
  "billing": {
    "amount_kobo": 0,
    "currency": "NGN"
  },
  "latency_ms": 268,
  "created_at": "2026-10-19T09:41:07.512Z",
  "updated_at": "2026-10-19T09:41:07.874Z"
}
```

**summary**

| Field | Type | Required | Description |
| --- | --- | --- | --- |
| `match` | boolean | No | `boolean` method: whether every supplied field matched the BVN record. The per-field answers are in `field_matches` on the verification. |
| `first_name` | string | No | `core` method: masked first name. |
| `last_name` | string | No | `core` method: masked last name. |
| `watchlisted` | boolean | No | `core` method: whether the BVN is on the industry watchlist. |

Full-tier accounts calling the REST API also receive `provider`, `provider_reference` and `data`, the normalized record. MCP, webhooks, lists and bulk results never include it. See [Response shaping](https://speedmap-delta.vercel.app/docs/response-shaping).

**Full tier only**

```json
{
  "provider": "sandbox",
  "provider_reference": "SBX6CB753DB2C",
  "data": {
    "match": true,
    "field_matches": {
      "first_name": true,
      "last_name": true,
      "date_of_birth": true,
      "phone": true
    }
  }
}
```

## Test values

Send these bodies with a sandbox key. More outcome triggers are on the [Sandbox](https://speedmap-delta.vercel.app/docs/sandbox) page.

**Boolean match**

```json
{
  "method": "boolean",
  "bvn": "22222222221",
  "reason": "financialProducts",
  "consent": {
    "obtained": true
  },
  "match": {
    "first_name": "Clarke",
    "last_name": "Griffin"
  }
}
```

**Boolean no-match**

```json
{
  "method": "boolean",
  "bvn": "22222222222",
  "reason": "financialProducts",
  "consent": {
    "obtained": true
  },
  "match": {
    "last_name": "Smith"
  }
}
```

**Watchlisted BVN (boolean)**

```json
{
  "method": "boolean",
  "bvn": "22222222225",
  "reason": "creditBackgroundCheck",
  "consent": {
    "obtained": true
  },
  "match": {
    "last_name": "Murphy"
  }
}
```

## Errors

| Status | Code | Meaning |
| --- | --- | --- |
| 400 | `invalid_request` | The body failed validation. `details` lists each field. |
| 401 | `unauthorized` | Missing or invalid API key. |
| 402 | `insufficient_balance` | Live only: wallet balance is below the check price. |
| 403 | `not_entitled` | Your workspace is not enabled for this product or method. |
| 409 | `idempotency_key_reused` | The `Idempotency-Key` was used with a different body. |
| 404 | `consent_not_found` | No consent with that `consent_id` in this mode. |
| 412 | `consent_not_granted` | The consent is pending, denied, expired or revoked. |
| 412 | `consent_expired` | The grant is older than 24 hours. Request a new consent. |
| 412 | `consent_subject_mismatch` | The consent was granted for a different BVN. |
| 422 | `provider_rejected_request` | The upstream registry rejected the request. |
| 503 | `verification_unavailable` | Temporary failure. Retry, with the same or a new `Idempotency-Key`. |

All errors share one shape. See [Errors](https://speedmap-delta.vercel.app/docs/errors) for the full list.

---

Source: https://speedmap-delta.vercel.app/docs/products/bvn
