# API reference

> Every Speedmap endpoint with parameters, request and response examples, and a Try it panel for sandbox keys.

Generated from the [OpenAPI description](https://speedmap-delta.vercel.app/openapi.json) (OpenAPI 3.0.3, API version 1.0.0). All requests go to `http://localhost:4000` and authenticate with `Authorization: Bearer sk_test_...` or `sk_live_...`. Bodies are JSON.

Each operation has a **Try it** panel. Paste a **sandbox** key (`sk_test_`) to send real requests from this page and see the live response; sandbox calls use [test data](https://speedmap-delta.vercel.app/docs/sandbox) and are free. Live keys are not accepted here.

## Identity

NIN and phone.

### NIN Verification

`POST /v1/verify/nin`

Verify a National Identity Number against the NIMC record by raw NIN, in-person selfie, or NINAuth share code.

**Parameters**

| Name | In | Type | Required | Description |
| --- | --- | --- | --- | --- |
| `Idempotency-Key` | header | string | No | Retries with the same key and body return the original result and are never billed twice. 8 to 128 characters. |

**Body (method: `nin`)**: Raw NIN lookup. Restricted by NIMC to financial institutions and government agencies.

| Field | Type | Required | Description |
| --- | --- | --- | --- |
| `method` | string | Yes | Always `nin`. |
| `nin` | 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 | 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`. |

**Body (method: `in_person`)**: NIN plus a live selfie. The selfie is the consent. Available to all business types.

| Field | Type | Required | Description |
| --- | --- | --- | --- |
| `method` | string | Yes | Always `in_person`. |
| `nin` | string | Yes | Format: 11 digits. |
| `selfie_image` | string | Yes | Base64-encoded JPEG selfie. At least 100 characters. |
| `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). |
| `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`. |

**Body (method: `share_code`)**: 6-character code the subject generates in the NINAuth app. The code is the consent.

| Field | Type | Required | Description |
| --- | --- | --- | --- |
| `method` | string | Yes | Always `share_code`. |
| `share_code` | string | Yes | Format: 5 or 6 letters or 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). |
| `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`. |

**Example request**

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

**Responses**

- `200` Completed (verified or not_found) (Verification)
- `202` Pending: the provider did not answer in time. Poll GET /v1/verifications/{reference_id}. (Verification)
- `400` Invalid request (Error)
- `401` Missing or invalid API key (Error)
- `402` Wallet balance too low (live) (Error)
- `403` Not entitled, or live access not enabled (Error)
- `409` Idempotency-Key reused with a different body (Error)
- `412` Consent missing, pending or mismatched (Error)
- `422` Provider rejected the request (Error)
- `503` Temporarily unavailable; safe to retry (Error)

**Example response (200)**

```json
{
  "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"
}
```

### Phone Identity Lookup

`POST /v1/verify/phone`

Resolve a mobile number to the name and date of birth on its ICAD/BVN record. For corroboration and fraud signal, not as standalone identity.

**Parameters**

| Name | In | Type | Required | Description |
| --- | --- | --- | --- | --- |
| `Idempotency-Key` | header | string | No | Retries with the same key and body return the original result and are never billed twice. 8 to 128 characters. |

**Body**

| Field | Type | Required | Description |
| --- | --- | --- | --- |
| `phone` | string | Yes | Format: Nigerian mobile number, `0803…` or `+234803…`. |
| `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 | 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`. |

**Example request**

```bash
curl -X POST http://localhost:4000/v1/verify/phone \
  -H "Authorization: Bearer sk_test_..." \
  -H "Idempotency-Key: $(uuidgen)" \
  -H "Content-Type: application/json" \
  -d '{
  "phone": "08031234567",
  "reason": "financialProducts",
  "consent": {
    "obtained": true
  },
  "match": {
    "last_name": "Griffin"
  }
}'
```

**Responses**

- `200` Completed (verified or not_found) (Verification)
- `202` Pending: the provider did not answer in time. Poll GET /v1/verifications/{reference_id}. (Verification)
- `400` Invalid request (Error)
- `401` Missing or invalid API key (Error)
- `402` Wallet balance too low (live) (Error)
- `403` Not entitled, or live access not enabled (Error)
- `409` Idempotency-Key reused with a different body (Error)
- `412` Consent missing, pending or mismatched (Error)
- `422` Provider rejected the request (Error)
- `503` Temporarily unavailable; safe to retry (Error)

**Example response (200)**

```json
{
  "reference_id": "spm_8171f3a87cbe221341f7185f",
  "object": "verification",
  "product": "phone",
  "method": "phone_lookup",
  "status": "verified",
  "match": true,
  "field_matches": {
    "last_name": true
  },
  "field_confidence": {
    "last_name": 100
  },
  "mode": "sandbox",
  "reason": "financialProducts",
  "subject": "*******4567",
  "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": 233,
  "created_at": "2026-10-19T09:41:07.512Z",
  "updated_at": "2026-10-19T09:41:07.874Z"
}
```

## Financial

BVN and bank accounts.

### BVN Verification

`POST /v1/verify/bvn`

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

**Parameters**

| Name | In | Type | Required | Description |
| --- | --- | --- | --- | --- |
| `Idempotency-Key` | header | string | No | Retries with the same key and body return the original result and are never billed twice. 8 to 128 characters. |

**Body (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 (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`. |

**Example request**

```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"
  }
}'
```

**Responses**

- `200` Completed (verified or not_found) (Verification)
- `202` Pending: the provider did not answer in time. Poll GET /v1/verifications/{reference_id}. (Verification)
- `400` Invalid request (Error)
- `401` Missing or invalid API key (Error)
- `402` Wallet balance too low (live) (Error)
- `403` Not entitled, or live access not enabled (Error)
- `409` Idempotency-Key reused with a different body (Error)
- `412` Consent missing, pending or mismatched (Error)
- `422` Provider rejected the request (Error)
- `503` Temporarily unavailable; safe to retry (Error)

**Example response (200)**

```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"
}
```

### Bank Account Verification

`POST /v1/verify/account`

Resolve and verify the holder name on a Nigerian bank account via NIBSS ICAD Name Enquiry. Batch-capable.

**Parameters**

| Name | In | Type | Required | Description |
| --- | --- | --- | --- | --- |
| `Idempotency-Key` | header | string | No | Retries with the same key and body return the original result and are never billed twice. 8 to 128 characters. |

**Body**

| Field | Type | Required | Description |
| --- | --- | --- | --- |
| `account_number` | string | Yes | Format: 10 digits. |
| `bank_code` | string | Yes | Format: 3 to 6 digits. |
| `match` | object | No |  |
| `match.name` | string | Yes | Expected account holder name; compared token by token, order-insensitive. At least 2 characters. |

**Example request**

```bash
curl -X POST http://localhost:4000/v1/verify/account \
  -H "Authorization: Bearer sk_test_..." \
  -H "Idempotency-Key: $(uuidgen)" \
  -H "Content-Type: application/json" \
  -d '{
  "account_number": "0123456789",
  "bank_code": "058",
  "match": {
    "name": "Clarke Griffin"
  }
}'
```

**Responses**

- `200` Completed (verified or not_found) (Verification)
- `202` Pending: the provider did not answer in time. Poll GET /v1/verifications/{reference_id}. (Verification)
- `400` Invalid request (Error)
- `401` Missing or invalid API key (Error)
- `402` Wallet balance too low (live) (Error)
- `403` Not entitled, or live access not enabled (Error)
- `409` Idempotency-Key reused with a different body (Error)
- `412` Consent missing, pending or mismatched (Error)
- `422` Provider rejected the request (Error)
- `503` Temporarily unavailable; safe to retry (Error)

**Example response (200)**

```json
{
  "reference_id": "spm_8171f3a87cbe221341f7185f",
  "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"
}
```

## Biometric

Face liveness and match.

### Face Liveness & Match

`POST /v1/verify/face`

Passive liveness detection and face comparison against the BVN photo in one call, via NIBSS N-Face.

**Parameters**

| Name | In | Type | Required | Description |
| --- | --- | --- | --- | --- |
| `Idempotency-Key` | header | string | No | Retries with the same key and body return the original result and are never billed twice. 8 to 128 characters. |

**Body**

| Field | Type | Required | Description |
| --- | --- | --- | --- |
| `image` | string | Yes | Selfie as base64 JPEG/PNG, or an https URL when image_type is url. At least 20 characters. |
| `image_type` | string | No | One of: `base64`, `url`. Defaults to `base64`. |
| `bvn` | string | No | Compare the selfie against the photo on this BVN. Omit for liveness only. Format: 11 digits. |
| `liveness_threshold` | number | No | Between 0 and 1. Defaults to `0.6`. |
| `face_match_threshold` | number | No | Between 0 and 1. Defaults to `0.9`. |
| `order` | string | No | Liveness first, comparison first, or both in parallel. One of: `pl_fc_serial`, `fc_pl_serial`, `fc_pl_parallel`. Defaults to `pl_fc_serial`. |
| `os_type` | string | No | One of: `ios`, `android`, `desktop`, `unknown`. Defaults to `unknown`. |
| `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). |

**Example request**

```bash
curl -X POST http://localhost:4000/v1/verify/face \
  -H "Authorization: Bearer sk_test_..." \
  -H "Idempotency-Key: $(uuidgen)" \
  -H "Content-Type: application/json" \
  -d '{
  "image": "<base64 selfie>",
  "bvn": "22222222221",
  "reason": "financialProducts"
}'
```

**Responses**

- `200` Completed (verified or not_found) (Verification)
- `202` Pending: the provider did not answer in time. Poll GET /v1/verifications/{reference_id}. (Verification)
- `400` Invalid request (Error)
- `401` Missing or invalid API key (Error)
- `402` Wallet balance too low (live) (Error)
- `403` Not entitled, or live access not enabled (Error)
- `409` Idempotency-Key reused with a different body (Error)
- `412` Consent missing, pending or mismatched (Error)
- `422` Provider rejected the request (Error)
- `503` Temporarily unavailable; safe to retry (Error)

**Example response (200)**

```json
{
  "reference_id": "spm_8171f3a87cbe221341f7185f",
  "object": "verification",
  "product": "face",
  "method": "face_liveness_match",
  "status": "verified",
  "match": true,
  "field_matches": null,
  "field_confidence": null,
  "mode": "sandbox",
  "reason": "financialProducts",
  "subject": "*******2221",
  "summary": {
    "liveness_passed": true,
    "liveness_confidence": 0.885,
    "liveness_quality": 0.885,
    "face_match_passed": true,
    "face_match_confidence": 0.988,
    "face_match_quality": 0.902,
    "session_code": "11b004ce-9f2f-43df-858e-79eacf50af07",
    "thresholds": {
      "liveness": 0.6,
      "face_match": 0.9
    }
  },
  "consent": {
    "mechanism": "in_person_selfie",
    "consent_id": null,
    "timestamp": "2026-10-19T09:41:07.512Z"
  },
  "source": "api",
  "error": null,
  "billing": {
    "amount_kobo": 0,
    "currency": "NGN"
  },
  "latency_ms": 1180,
  "created_at": "2026-10-19T09:41:07.512Z",
  "updated_at": "2026-10-19T09:41:07.874Z"
}
```

## Business

CAC company verification (KYB).

### Business Verification (KYB)

`POST /v1/verify/business`

CAC company verification through NIBSS CIV: status, registration, directors, shareholders, share capital and winding-up.

**Parameters**

| Name | In | Type | Required | Description |
| --- | --- | --- | --- | --- |
| `Idempotency-Key` | header | string | No | Retries with the same key and body return the original result and are never billed twice. 8 to 128 characters. |

**Body**

| Field | Type | Required | Description |
| --- | --- | --- | --- |
| `rc_number` | string | No | CAC RC or BN number, e.g. RC1234567. At least 3 characters. |
| `tin` | string | No | At least 5 characters. |
| `company_name` | string | No | At least 2 characters. |
| `include` | array of strings | No | Each one of: `directors`, `shareholders`, `share_capital`, `line_of_business`, `affiliates`, `status_report`, `winding_up`, `certificate`. Defaults to `["directors","shareholders","share_capital","status_report"]`. |
| `reason` | string | No | 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). |
| `match` | object | No |  |
| `match.company_name` | string | No | At least 2 characters. |

**Example request**

```bash
curl -X POST http://localhost:4000/v1/verify/business \
  -H "Authorization: Bearer sk_test_..." \
  -H "Idempotency-Key: $(uuidgen)" \
  -H "Content-Type: application/json" \
  -d '{
  "rc_number": "RC1234567",
  "include": [
    "directors",
    "shareholders",
    "share_capital",
    "status_report"
  ]
}'
```

**Responses**

- `200` Completed (verified or not_found) (Verification)
- `202` Pending: the provider did not answer in time. Poll GET /v1/verifications/{reference_id}. (Verification)
- `400` Invalid request (Error)
- `401` Missing or invalid API key (Error)
- `402` Wallet balance too low (live) (Error)
- `403` Not entitled, or live access not enabled (Error)
- `409` Idempotency-Key reused with a different body (Error)
- `412` Consent missing, pending or mismatched (Error)
- `422` Provider rejected the request (Error)
- `503` Temporarily unavailable; safe to retry (Error)

**Example response (200)**

```json
{
  "reference_id": "spm_8171f3a87cbe221341f7185f",
  "object": "verification",
  "product": "business",
  "method": "cac_rc",
  "status": "verified",
  "match": true,
  "field_matches": null,
  "field_confidence": null,
  "mode": "sandbox",
  "reason": null,
  "subject": null,
  "summary": {
    "company_name": "SPEEDILY SERVICES LIMITED",
    "rc_number": "RC1234567",
    "status": "ACTIVE",
    "active": true,
    "type": "Private Company Limited by Shares",
    "registered_at": "2016-05-12",
    "state": "FCT",
    "name_match": null
  },
  "consent": {
    "mechanism": "public_registry",
    "consent_id": null,
    "timestamp": "2026-10-19T09:41:07.512Z"
  },
  "source": "api",
  "error": null,
  "billing": {
    "amount_kobo": 0,
    "currency": "NGN"
  },
  "latency_ms": 640,
  "created_at": "2026-10-19T09:41:07.512Z",
  "updated_at": "2026-10-19T09:41:07.874Z"
}
```

## Fraud

Risk signals.

### Fraud Signals

`POST /v1/signals`

One risk score from SIM-swap recency, BVN demographic changes and watchlist status. Built for account-takeover and onboarding fraud.

**Parameters**

| Name | In | Type | Required | Description |
| --- | --- | --- | --- | --- |
| `Idempotency-Key` | header | string | No | Retries with the same key and body return the original result and are never billed twice. 8 to 128 characters. |

**Body**

| Field | Type | Required | Description |
| --- | --- | --- | --- |
| `phone` | string | No | Format: Nigerian mobile number, `0803…` or `+234803…`. |
| `bvn` | string | No | 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`. |

**Example request**

```bash
curl -X POST http://localhost:4000/v1/signals \
  -H "Authorization: Bearer sk_test_..." \
  -H "Idempotency-Key: $(uuidgen)" \
  -H "Content-Type: application/json" \
  -d '{
  "phone": "08099990000",
  "bvn": "22222222225",
  "reason": "financialProducts",
  "consent": {
    "obtained": true
  }
}'
```

**Responses**

- `200` Completed (verified or not_found) (Verification)
- `202` Pending: the provider did not answer in time. Poll GET /v1/verifications/{reference_id}. (Verification)
- `400` Invalid request (Error)
- `401` Missing or invalid API key (Error)
- `402` Wallet balance too low (live) (Error)
- `403` Not entitled, or live access not enabled (Error)
- `409` Idempotency-Key reused with a different body (Error)
- `412` Consent missing, pending or mismatched (Error)
- `422` Provider rejected the request (Error)
- `503` Temporarily unavailable; safe to retry (Error)

**Example response (200)**

```json
{
  "reference_id": "spm_8171f3a87cbe221341f7185f",
  "object": "verification",
  "product": "signals",
  "method": "risk_signals",
  "status": "verified",
  "match": null,
  "field_matches": null,
  "field_confidence": null,
  "mode": "sandbox",
  "reason": "financialProducts",
  "subject": "*******2225",
  "summary": {
    "risk_score": 100,
    "risk_level": "high",
    "factors": [
      {
        "code": "watchlisted",
        "weight": 60,
        "detail": "BVN is on the industry watchlist"
      },
      {
        "code": "sim_swap_7d",
        "weight": 35,
        "detail": "SIM swapped 2 day(s) ago"
      },
      {
        "code": "bvn_name_change_30d",
        "weight": 20,
        "detail": "Name changed on the BVN in the last 30 days"
      },
      {
        "code": "bvn_phone_change_30d",
        "weight": 10,
        "detail": "Phone number changed on the BVN in the last 30 days"
      }
    ],
    "sim_swap_recent": true,
    "days_since_sim_swap": 2,
    "bvn_demographic_changes": 2,
    "bvn_demographic_changes_30d": 2,
    "watchlisted": 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": 520,
  "created_at": "2026-10-19T09:41:07.512Z",
  "updated_at": "2026-10-19T09:41:07.874Z"
}
```

## Documents

Passport, driver's licence, voter's card.

### International Passport

`POST /v1/verify/passport`

Verify a international passport against the Nigeria Immigration Service record. Sandbox only until the Nigeria Immigration Service data agreement is live.

**Preview:** available with sandbox keys; live access follows the provider agreement.

**Parameters**

| Name | In | Type | Required | Description |
| --- | --- | --- | --- | --- |
| `Idempotency-Key` | header | string | No | Retries with the same key and body return the original result and are never billed twice. 8 to 128 characters. |

**Body**

| Field | Type | Required | Description |
| --- | --- | --- | --- |
| `passport_number` | string | Yes | Format: a letter followed by 8 digits, e.g. `A12345678`. |
| `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 | 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`. |

**Example request**

```bash
curl -X POST http://localhost:4000/v1/verify/passport \
  -H "Authorization: Bearer sk_test_..." \
  -H "Idempotency-Key: $(uuidgen)" \
  -H "Content-Type: application/json" \
  -d '{
  "passport_number": "A12345678",
  "reason": "financialProducts",
  "consent": {
    "obtained": true
  }
}'
```

**Responses**

- `200` Completed (verified or not_found) (Verification)
- `202` Pending: the provider did not answer in time. Poll GET /v1/verifications/{reference_id}. (Verification)
- `400` Invalid request (Error)
- `401` Missing or invalid API key (Error)
- `402` Wallet balance too low (live) (Error)
- `403` Not entitled, or live access not enabled (Error)
- `409` Idempotency-Key reused with a different body (Error)
- `412` Consent missing, pending or mismatched (Error)
- `422` Provider rejected the request (Error)
- `503` Temporarily unavailable; safe to retry (Error)

**Example response (200)**

```json
{
  "reference_id": "spm_8171f3a87cbe221341f7185f",
  "object": "verification",
  "product": "passport",
  "method": "passport",
  "status": "verified",
  "match": true,
  "field_matches": null,
  "field_confidence": null,
  "mode": "sandbox",
  "reason": "financialProducts",
  "subject": "*****5678",
  "summary": {
    "first_name": "A*****",
    "last_name": "O*****",
    "document_type": "passport",
    "expired": false,
    "expires_at": "2031-06-09"
  },
  "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": 350,
  "created_at": "2026-10-19T09:41:07.512Z",
  "updated_at": "2026-10-19T09:41:07.874Z"
}
```

### Driver's Licence

`POST /v1/verify/drivers-license`

Verify a driver's licence against the FRSC record. Sandbox only until the FRSC data agreement is live.

**Preview:** available with sandbox keys; live access follows the provider agreement.

**Parameters**

| Name | In | Type | Required | Description |
| --- | --- | --- | --- | --- |
| `Idempotency-Key` | header | string | No | Retries with the same key and body return the original result and are never billed twice. 8 to 128 characters. |

**Body**

| Field | Type | Required | Description |
| --- | --- | --- | --- |
| `license_number` | string | Yes | Format: FRSC format, e.g. `LAG12345AA01`. |
| `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 | 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`. |

**Example request**

```bash
curl -X POST http://localhost:4000/v1/verify/drivers-license \
  -H "Authorization: Bearer sk_test_..." \
  -H "Idempotency-Key: $(uuidgen)" \
  -H "Content-Type: application/json" \
  -d '{
  "license_number": "LAG12345AA01",
  "reason": "financialProducts",
  "consent": {
    "obtained": true
  }
}'
```

**Responses**

- `200` Completed (verified or not_found) (Verification)
- `202` Pending: the provider did not answer in time. Poll GET /v1/verifications/{reference_id}. (Verification)
- `400` Invalid request (Error)
- `401` Missing or invalid API key (Error)
- `402` Wallet balance too low (live) (Error)
- `403` Not entitled, or live access not enabled (Error)
- `409` Idempotency-Key reused with a different body (Error)
- `412` Consent missing, pending or mismatched (Error)
- `422` Provider rejected the request (Error)
- `503` Temporarily unavailable; safe to retry (Error)

**Example response (200)**

```json
{
  "reference_id": "spm_8171f3a87cbe221341f7185f",
  "object": "verification",
  "product": "drivers_license",
  "method": "drivers_license",
  "status": "verified",
  "match": true,
  "field_matches": null,
  "field_confidence": null,
  "mode": "sandbox",
  "reason": "financialProducts",
  "subject": "********AA01",
  "summary": {
    "first_name": "A*****",
    "last_name": "O*****",
    "document_type": "drivers_license",
    "expired": false,
    "expires_at": "2027-01-31"
  },
  "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": 340,
  "created_at": "2026-10-19T09:41:07.512Z",
  "updated_at": "2026-10-19T09:41:07.874Z"
}
```

### Voter's Card

`POST /v1/verify/voters-card`

Verify a voter's card against the INEC record. Sandbox only until the INEC data agreement is live.

**Preview:** available with sandbox keys; live access follows the provider agreement.

**Parameters**

| Name | In | Type | Required | Description |
| --- | --- | --- | --- | --- |
| `Idempotency-Key` | header | string | No | Retries with the same key and body return the original result and are never billed twice. 8 to 128 characters. |

**Body**

| Field | Type | Required | Description |
| --- | --- | --- | --- |
| `vin` | string | Yes | Format: 19 letters or 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 | 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`. |

**Example request**

```bash
curl -X POST http://localhost:4000/v1/verify/voters-card \
  -H "Authorization: Bearer sk_test_..." \
  -H "Idempotency-Key: $(uuidgen)" \
  -H "Content-Type: application/json" \
  -d '{
  "vin": "90F5B1A2C3D4E5F6071",
  "reason": "financialProducts",
  "consent": {
    "obtained": true
  }
}'
```

**Responses**

- `200` Completed (verified or not_found) (Verification)
- `202` Pending: the provider did not answer in time. Poll GET /v1/verifications/{reference_id}. (Verification)
- `400` Invalid request (Error)
- `401` Missing or invalid API key (Error)
- `402` Wallet balance too low (live) (Error)
- `403` Not entitled, or live access not enabled (Error)
- `409` Idempotency-Key reused with a different body (Error)
- `412` Consent missing, pending or mismatched (Error)
- `422` Provider rejected the request (Error)
- `503` Temporarily unavailable; safe to retry (Error)

**Example response (200)**

```json
{
  "reference_id": "spm_8171f3a87cbe221341f7185f",
  "object": "verification",
  "product": "voters_card",
  "method": "voters_card",
  "status": "verified",
  "match": true,
  "field_matches": null,
  "field_confidence": null,
  "mode": "sandbox",
  "reason": "financialProducts",
  "subject": "***************6071",
  "summary": {
    "first_name": "A*****",
    "last_name": "O*****",
    "document_type": "voters_card",
    "expired": false,
    "expires_at": 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": 330,
  "created_at": "2026-10-19T09:41:07.512Z",
  "updated_at": "2026-10-19T09:41:07.874Z"
}
```

## Verifications

Retrieve and list the results of earlier checks. See [Verifications](https://speedmap-delta.vercel.app/docs/verifications).

### List verifications

`GET /v1/verifications`

**Parameters**

| Name | In | Type | Required | Description |
| --- | --- | --- | --- | --- |
| `limit` | query | integer | No | Maximum 100. Defaults to `20`. |
| `product` | query | string | No |  |
| `status` | query | string | No |  |
| `before` | query | string | No | Cursor: created_at of the last item ISO 8601 timestamp. |

**Example request**

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

**Responses**

- `200` Assertion-only list

**Example response (200)**

```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
}
```

### Retrieve a verification

`GET /v1/verifications/{reference_id}`

Pending verifications are re-queried with the provider on read.

**Parameters**

| Name | In | Type | Required | Description |
| --- | --- | --- | --- | --- |
| `reference_id` | path | string | Yes |  |

**Example request**

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

**Responses**

- `200` Result (Verification)
- `202` Still pending
- `401` Missing or invalid API key (Error)
- `404` Not found (Error)

**Example response (200)**

```json
{
  "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"
}
```

## Consent

Consent requests the data subject approves by link, email or USSD. See [Consent](https://speedmap-delta.vercel.app/docs/consent).

### Request consent

`POST /v1/consents`

Creates a consent request the data subject approves by link, email or USSD. Required before BVN core lookups.

**Body**

| Field | Type | Required | Description |
| --- | --- | --- | --- |
| `identifier_type` | string | Yes | One of: `bvn`, `nin`. |
| `identifier` | string | Yes | Format: 11 digits. |
| `categories` | string | No | Y/N mask over personal identification, contact, demographic, geographic data. Format: 4-character Y/N mask, e.g. `YYNN`. Defaults to `YYNN`. |
| `channel` | string | No | One of: `redirect`, `email`, `ussd`. Defaults to `redirect`. |
| `reason` | string | No | Defaults to `financialProducts`. |
| `email` | string | No | Format: email. |
| `redirect_url` | string | No | Format: uri. |

**Example request**

```bash
curl -X POST "http://localhost:4000/v1/consents" \
  -H "Authorization: Bearer sk_test_..." \
  -H "Content-Type: application/json" \
  -d '{
  "identifier_type": "bvn",
  "identifier": "22222222221",
  "categories": "YYNN",
  "channel": "redirect",
  "reason": "financialProducts"
}'
```

**Responses**

- `201` Consent request created
- `400` Invalid request (Error)
- `401` Missing or invalid API key (Error)

**Example response (201)**

```json
{
  "id": "cns_423a8f143e7f2f8ffe7dda3344d36710",
  "object": "consent",
  "identifier_type": "bvn",
  "subject": "*******2221",
  "categories": "YYNN",
  "category_labels": [
    "Personal identification",
    "Contact information"
  ],
  "channel": "ussd",
  "status": "pending",
  "reason": "financialProducts",
  "authorization_url": "https://speedmap-delta.vercel.app/consent/cns_423a8f143e7f2f8ffe7dda3344d36710",
  "ussd_code": "*565*1*7*70205378#",
  "redirect_url": null,
  "granted_at": null,
  "expires_at": "2026-10-20T09:41:07.512Z",
  "created_at": "2026-10-19T09:41:07.512Z",
  "mode": "sandbox"
}
```

### List consents

`GET /v1/consents`

**Example request**

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

**Responses**

- `200` Consents

**Example response (200)**

```json
{
  "object": "list",
  "data": [
    {
      "id": "cns_423a8f143e7f2f8ffe7dda3344d36710",
      "object": "consent",
      "identifier_type": "bvn",
      "subject": "*******2221",
      "categories": "YYNN",
      "category_labels": [
        "Personal identification",
        "Contact information"
      ],
      "channel": "ussd",
      "status": "granted",
      "reason": "financialProducts",
      "authorization_url": "https://speedmap-delta.vercel.app/consent/cns_423a8f143e7f2f8ffe7dda3344d36710",
      "ussd_code": "*565*1*7*70205378#",
      "redirect_url": null,
      "granted_at": "2026-10-19T09:43:52.004Z",
      "expires_at": "2026-10-20T09:43:52.004Z",
      "created_at": "2026-10-19T09:41:07.512Z",
      "mode": "sandbox"
    }
  ],
  "has_more": false
}
```

### Retrieve a consent

`GET /v1/consents/{id}`

**Parameters**

| Name | In | Type | Required | Description |
| --- | --- | --- | --- | --- |
| `id` | path | string | Yes |  |

**Example request**

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

**Responses**

- `200` Consent
- `404` Not found (Error)

**Example response (200)**

```json
{
  "id": "cns_423a8f143e7f2f8ffe7dda3344d36710",
  "object": "consent",
  "identifier_type": "bvn",
  "subject": "*******2221",
  "categories": "YYNN",
  "category_labels": [
    "Personal identification",
    "Contact information"
  ],
  "channel": "ussd",
  "status": "granted",
  "reason": "financialProducts",
  "authorization_url": "https://speedmap-delta.vercel.app/consent/cns_423a8f143e7f2f8ffe7dda3344d36710",
  "ussd_code": "*565*1*7*70205378#",
  "redirect_url": null,
  "granted_at": "2026-10-19T09:43:52.004Z",
  "expires_at": "2026-10-20T09:43:52.004Z",
  "created_at": "2026-10-19T09:41:07.512Z",
  "mode": "sandbox"
}
```

### Revoke a consent

`POST /v1/consents/{id}/revoke`

**Parameters**

| Name | In | Type | Required | Description |
| --- | --- | --- | --- | --- |
| `id` | path | string | Yes |  |

**Example request**

```bash
curl -X POST "http://localhost:4000/v1/consents/cns_423a8f143e7f2f8ffe7dda3344d36710/revoke" \
  -H "Authorization: Bearer sk_test_..."
```

**Responses**

- `200` Revoked

**Example response (200)**

```json
{
  "id": "cns_423a8f143e7f2f8ffe7dda3344d36710",
  "object": "consent",
  "identifier_type": "bvn",
  "subject": "*******2221",
  "categories": "YYNN",
  "category_labels": [
    "Personal identification",
    "Contact information"
  ],
  "channel": "ussd",
  "status": "revoked",
  "reason": "financialProducts",
  "authorization_url": "https://speedmap-delta.vercel.app/consent/cns_423a8f143e7f2f8ffe7dda3344d36710",
  "ussd_code": "*565*1*7*70205378#",
  "redirect_url": null,
  "granted_at": "2026-10-19T09:43:52.004Z",
  "expires_at": "2026-10-20T09:43:52.004Z",
  "created_at": "2026-10-19T09:41:07.512Z",
  "mode": "sandbox"
}
```

### Sandbox: approve or deny a consent

`POST /v1/sandbox/consents/{id}/{decision}`

_Sandbox keys only._

Acts as the data subject. Sandbox keys only.

**Parameters**

| Name | In | Type | Required | Description |
| --- | --- | --- | --- | --- |
| `id` | path | string | Yes |  |
| `decision` | path | string | Yes | One of: `approve`, `deny`. |

**Example request**

```bash
curl -X POST "http://localhost:4000/v1/sandbox/consents/cns_423a8f143e7f2f8ffe7dda3344d36710/approve" \
  -H "Authorization: Bearer sk_test_..."
```

**Responses**

- `200` Updated consent

**Example response (200)**

```json
{
  "id": "cns_423a8f143e7f2f8ffe7dda3344d36710",
  "object": "consent",
  "identifier_type": "bvn",
  "subject": "*******2221",
  "categories": "YYNN",
  "category_labels": [
    "Personal identification",
    "Contact information"
  ],
  "channel": "ussd",
  "status": "granted",
  "reason": "financialProducts",
  "authorization_url": "https://speedmap-delta.vercel.app/consent/cns_423a8f143e7f2f8ffe7dda3344d36710",
  "ussd_code": "*565*1*7*70205378#",
  "redirect_url": null,
  "granted_at": "2026-10-19T09:43:52.004Z",
  "expires_at": "2026-10-20T09:43:52.004Z",
  "created_at": "2026-10-19T09:41:07.512Z",
  "mode": "sandbox"
}
```

## Bulk

Up to 10,000 checks per job; results are read from the job, never returned inline. See [Bulk verification](https://speedmap-delta.vercel.app/docs/bulk).

### Create a bulk job

`POST /v1/bulk`

Up to 10,000 items of one product. Returns a job handle; results are read from the job, never inline.

**Body**

| Field | Type | Required | Description |
| --- | --- | --- | --- |
| `product` | string | Yes | One of: `nin`, `bvn`, `phone`, `business`, `account`, `signals`, `passport`, `drivers_license`, `voters_card`. |
| `items` | array of objects | Yes | 1 to 10,000 items. |
| `name` | string | No | At most 120 characters. |

**Example request**

```bash
curl -X POST "http://localhost:4000/v1/bulk" \
  -H "Authorization: Bearer sk_test_..." \
  -H "Content-Type: application/json" \
  -d '{
  "product": "account",
  "name": "Payout batch 42",
  "items": [
    {
      "account_number": "0123456789",
      "bank_code": "058",
      "match": {
        "name": "Clarke Griffin"
      }
    }
  ]
}'
```

**Responses**

- `202` Job queued
- `400` Invalid request (Error)
- `401` Missing or invalid API key (Error)
- `403` Not entitled, or live access not enabled (Error)

**Example response (202)**

```json
{
  "id": "job_1a2b3c4d5e6f7a8b",
  "object": "bulk_job",
  "product": "account",
  "name": "Payout batch 42",
  "status": "queued",
  "total": 2500,
  "processed": 0,
  "counts": {
    "verified": 0,
    "not_found": 0,
    "failed": 0
  },
  "progress": 0,
  "mode": "sandbox",
  "created_at": "2026-10-19T09:41:07.512Z",
  "completed_at": null
}
```

### List bulk jobs

`GET /v1/bulk`

**Example request**

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

**Responses**

- `200` Jobs

**Example response (200)**

```json
{
  "object": "list",
  "data": [
    {
      "id": "job_1a2b3c4d5e6f7a8b",
      "object": "bulk_job",
      "product": "account",
      "name": "Payout batch 42",
      "status": "running",
      "total": 2500,
      "processed": 1180,
      "counts": {
        "verified": 1102,
        "not_found": 61,
        "failed": 17
      },
      "progress": 47,
      "mode": "sandbox",
      "created_at": "2026-10-19T09:41:07.512Z",
      "completed_at": null
    }
  ],
  "has_more": false
}
```

### Bulk job progress

`GET /v1/bulk/{id}`

**Parameters**

| Name | In | Type | Required | Description |
| --- | --- | --- | --- | --- |
| `id` | path | string | Yes |  |

**Example request**

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

**Responses**

- `200` Job

**Example response (200)**

```json
{
  "id": "job_1a2b3c4d5e6f7a8b",
  "object": "bulk_job",
  "product": "account",
  "name": "Payout batch 42",
  "status": "running",
  "total": 2500,
  "processed": 1180,
  "counts": {
    "verified": 1102,
    "not_found": 61,
    "failed": 17
  },
  "progress": 47,
  "mode": "sandbox",
  "created_at": "2026-10-19T09:41:07.512Z",
  "completed_at": null
}
```

### Bulk job results

`GET /v1/bulk/{id}/results`

Per-row status and match. PII-free; fetch individual records by reference_id.

**Parameters**

| Name | In | Type | Required | Description |
| --- | --- | --- | --- | --- |
| `id` | path | string | Yes |  |

**Example request**

```bash
curl "http://localhost:4000/v1/bulk/job_1a2b3c4d5e6f7a8b/results" \
  -H "Authorization: Bearer sk_test_..."
```

**Responses**

- `200` Rows

**Example response (200)**

```json
{
  "object": "list",
  "data": [
    {
      "row": 1,
      "reference_id": "spm_0c1d2e3f4a5b6c7d8e9f0a1b",
      "status": "verified",
      "match": true,
      "subject": "******6789"
    },
    {
      "row": 2,
      "reference_id": "spm_1d2e3f4a5b6c7d8e9f0a1b2c",
      "status": "verified",
      "match": false,
      "subject": "******7890"
    },
    {
      "row": 3,
      "reference_id": "spm_2e3f4a5b6c7d8e9f0a1b2c3d",
      "status": "not_found",
      "match": false,
      "subject": "******0000"
    }
  ]
}
```

## Reference

Lookup lists: products and prices, purpose codes and bank codes.

### List products, prices and entitlements

`GET /v1/products`

**Example request**

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

**Responses**

- `200` Products

**Example response (200)**

```json
{
  "object": "list",
  "data": [
    {
      "id": "nin",
      "name": "NIN Verification",
      "category": "identity",
      "summary": "Verify a National Identity Number against the NIMC record by raw NIN, in-person selfie, or NINAuth share code.",
      "endpoint": "POST /v1/verify/nin",
      "availability": "available",
      "enabled": true,
      "price": {
        "amount_kobo": 10000,
        "currency": "NGN"
      }
    },
    "…"
  ]
}
```

### List the 38 NIMC purpose codes

`GET /v1/reasons`

**Example request**

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

**Responses**

- `200` Reasons

**Example response (200)**

```json
{
  "object": "list",
  "data": [
    {
      "key": "taxationEnrollment",
      "label": "Taxation Enrollment"
    },
    {
      "key": "taxationAssessment",
      "label": "Taxation Assessment"
    },
    {
      "key": "taxationEnforcement",
      "label": "Taxation Enforcement"
    },
    "…"
  ]
}
```

### List bank codes

`GET /v1/banks`

**Example request**

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

**Responses**

- `200` Banks

**Example response (200)**

```json
{
  "object": "list",
  "data": [
    {
      "code": "044",
      "name": "Access Bank"
    },
    {
      "code": "058",
      "name": "Guaranty Trust Bank"
    },
    "…"
  ]
}
```

---

Source: https://speedmap-delta.vercel.app/docs/api-reference
