# Verifications

> Every check creates a verification object with a permanent reference_id. Retrieve it, list it, and resolve pending results.

Each call to a product endpoint creates a **verification**. Speedmap writes it to the database *before* it calls the provider, so even a timed-out check has a `reference_id` you can use to recover the result.

## The verification object

| Field | Type | Required | Description |
| --- | --- | --- | --- |
| `reference_id` | string | No | Permanent identifier, `spm_` followed by 24 hex characters. |
| `object` | string | No | Always `verification`. |
| `product` | string | No | Product id: `nin`, `bvn`, `face`, `phone`, `business`, `account`, `signals`, `passport`, `drivers_license` or `voters_card`. |
| `method` | string | No | Method that ran, e.g. `nin_raw`, `nin_inperson`, `nin_sharecode`, `bvn_boolean`, `bvn_core`, `cac_rc`, `account_name_enquiry`. |
| `status` | string | No | `pending`, `verified`, `not_found` or `failed`. A failed single request returns an error response instead; `failed` appears only in lists and bulk results. |
| `match` | boolean \| null | No | `true` when the record exists and every field you supplied matches. `null` when nothing was compared (e.g. fraud signals) or the check is pending. |
| `field_matches` | object \| null | No | One boolean per field in your `match` object, e.g. `{ "last_name": true }`. |
| `field_confidence` | object \| null | No | How close each supplied value is to the record, 0 to 100, e.g. `{ "first_name": 92 }`. Names allow nicknames, spellings and order (Seun ~ Oluwaseun). `null` for BVN `boolean`, where NIBSS only answers yes or no. |
| `mode` | string | No | `sandbox` or `live`, from the key that made the request. |
| `reason` | string \| null | No | The [purpose code](https://speedmap-delta.vercel.app/docs/compliance#purpose-codes) you declared. |
| `subject` | string \| null | No | Masked identifier, e.g. `*******2109`. Never the full value. |
| `summary` | object \| null | No | PII-free result for the product: masked names, scores, flags. Documented on each product page. |
| `consent` | object | No | `mechanism`, `consent_id` and `timestamp`: how consent was satisfied. |
| `source` | string | No | `api`, `dashboard`, `mcp` or `bulk`. |
| `error` | object \| null | No | `code` and `message` when `status` is `failed`. |
| `billing` | object | No | `amount_kobo` and `currency` (`NGN`). Non-zero only for completed live checks. |
| `latency_ms` | integer \| null | No | Provider round trip in milliseconds. |
| `created_at` | string | No | ISO 8601 timestamp. Also the [pagination](https://speedmap-delta.vercel.app/docs/pagination) cursor. |
| `updated_at` | string | No | ISO 8601 timestamp of the last change. |
| `provider` | string | No | Full tier, REST only. Upstream that answered. |
| `provider_reference` | string \| null | No | Full tier, REST only. Upstream reference for disputes. |
| `data` | object | No | Full tier, REST only. The normalized record. See [Response shaping](https://speedmap-delta.vercel.app/docs/response-shaping). |

## Retrieve a verification

Fetch any earlier check by its `reference_id`. The response has the same shape and tier as the original call.

`GET /v1/verifications/{reference_id}`

_cURL_

```bash
curl "http://localhost:4000/v1/verifications/spm_8171f3a87cbe221341f7185f" \
  -H "Authorization: Bearer sk_test_..."
```

_Node.js_

```javascript
const res = await fetch("http://localhost:4000/v1/verifications/spm_8171f3a87cbe221341f7185f", {
  method: "GET",
  headers: {
    Authorization: `Bearer ${process.env.SPEEDMAP_API_KEY}`,
  },
});

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

_Python_

```python
import os, requests

res = requests.get(
    "http://localhost:4000/v1/verifications/spm_8171f3a87cbe221341f7185f",
    headers={
        "Authorization": f"Bearer {os.environ['SPEEDMAP_API_KEY']}",
    },
    timeout=30,
)
res.raise_for_status()
print(res.json())
```

An unknown reference, or one that belongs to the other mode, returns `404 not_found`.

## Pending verifications

Government registries sometimes answer slowly. When the provider does not answer within the request window, Speedmap returns **`202 Accepted`** with `status: "pending"` instead of failing:

**202 Accepted**

```json
{
  "reference_id": "spm_8171f3a87cbe221341f7185f",
  "object": "verification",
  "product": "nin",
  "method": "nin_raw",
  "status": "pending",
  "match": null,
  "field_matches": null,
  "field_confidence": {
    "last_name": 100
  },
  "mode": "sandbox",
  "reason": "financialProducts",
  "subject": "*******9999",
  "summary": null,
  "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": 10012,
  "created_at": "2026-10-19T09:41:07.512Z",
  "updated_at": "2026-10-19T09:41:07.874Z"
}
```

You are not charged for a pending check until it completes. To resolve it, either:

1. **Wait for the webhook.** `verification.completed` fires when the result arrives. See [Webhooks](https://speedmap-delta.vercel.app/docs/webhooks).
2. **Requery.** Call `GET /v1/verifications/{reference_id}`. For products whose provider supports requery (NIN today), a pending check is re-queried with the provider on read, and the response is `200` once it resolves or `202` while it is still pending.

Poll with back-off (for example after 2, 5, 15 and 30 seconds) rather than in a tight loop. In the sandbox, any NIN of all nines (`99999999999`) returns `202` and resolves on the first requery.

## List verifications

Returns the assertion view of your verifications, newest first, for the mode of your key. Lists never contain `data`, whatever your tier.

`GET /v1/verifications`

**Query parameters**

| Field | Type | Required | Description |
| --- | --- | --- | --- |
| `limit` | integer | No | Between 1 and 100. Defaults to `20`. |
| `product` | string | No | Only this product, e.g. `nin`. |
| `status` | string | No | Only this status: `pending`, `verified`, `not_found` or `failed`. |
| `before` | string | No | Cursor: the `created_at` of the last item you received. See [Pagination](https://speedmap-delta.vercel.app/docs/pagination). |

_cURL_

```bash
curl "http://localhost:4000/v1/verifications?limit=20&product=nin" \
  -H "Authorization: Bearer sk_test_..."
```

_Node.js_

```javascript
const res = await fetch("http://localhost:4000/v1/verifications?limit=20&product=nin", {
  method: "GET",
  headers: {
    Authorization: `Bearer ${process.env.SPEEDMAP_API_KEY}`,
  },
});

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

_Python_

```python
import os, requests

res = requests.get(
    "http://localhost:4000/v1/verifications",
    headers={
        "Authorization": f"Bearer {os.environ['SPEEDMAP_API_KEY']}",
    },
    params={
        "limit": "20",
        "product": "nin"
    },
    timeout=30,
)
res.raise_for_status()
print(res.json())
```

**200 OK**

```json
{
  "object": "list",
  "data": [
    {
      "reference_id": "spm_8171f3a87cbe221341f7185f",
      "object": "verification",
      "product": "nin",
      "method": "nin_raw",
      "status": "verified",
      "match": true,
      "field_matches": {
        "last_name": true
      },
      "field_confidence": {
        "last_name": 100
      },
      "mode": "sandbox",
      "reason": "financialProducts",
      "subject": "*******2109",
      "summary": {
        "first_name": "A*****",
        "last_name": "O*****"
      },
      "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": 412,
      "created_at": "2026-10-19T09:41:07.512Z",
      "updated_at": "2026-10-19T09:41:07.874Z"
    },
    {
      "reference_id": "spm_5b0e2c9d1f7a3e6b8c4d2a10",
      "object": "verification",
      "product": "account",
      "method": "account_name_enquiry",
      "status": "verified",
      "match": true,
      "field_matches": null,
      "field_confidence": null,
      "mode": "sandbox",
      "reason": null,
      "subject": "******6789",
      "summary": {
        "account_name_masked": "A***** C***** O*****",
        "bank_name": "Guaranty Trust Bank",
        "name_match": true
      },
      "consent": {
        "mechanism": "not_required",
        "consent_id": null,
        "timestamp": "2026-10-19T09:41:07.512Z"
      },
      "source": "api",
      "error": null,
      "billing": {
        "amount_kobo": 0,
        "currency": "NGN"
      },
      "latency_ms": 190,
      "created_at": "2026-10-19T09:41:07.512Z",
      "updated_at": "2026-10-19T09:41:07.874Z"
    }
  ],
  "has_more": true
}
```

## Matching

When you send a `match` object, each field is compared with the record after trimming and case-folding (`date_of_birth` must match exactly, as `YYYY-MM-DD`). Each field gets a yes/no in `field_matches` and, when Speedmap holds the record, a 0 to 100 score in `field_confidence`, so a misspelt or shortened name (Clark for Clarke, Seun for Oluwaseun) shows up as a near miss rather than a flat no:

**Partial match**

```json
{
  "status": "verified",
  "match": false,
  "field_matches": {
    "first_name": false,
    "last_name": true,
    "date_of_birth": true
  },
  "field_confidence": {
    "first_name": 94,
    "last_name": 100,
    "date_of_birth": 100
  },
  "summary": {
    "first_name": "A*****",
    "last_name": "O*****"
  }
}
```

`status: "verified"` with `match: false` means the identity exists but does not belong to the details you hold. Treat it as a failed check, not as a not-found.

---

Source: https://speedmap-delta.vercel.app/docs/verifications
