# Speedmap: identity verification API and MCP server for Nigeria > Speedmap is the identity verification platform of Speedily Services Limited, a licensed NIMC Front-End Processor. One REST API, one normalized response and one MCP server (Speedmap Connect) for NIN, BVN, face liveness, CAC business verification (KYB), bank account name enquiry (NUBAN), phone lookup and fraud signals. Speedmap Connect is the first identity MCP server designed so a NIN never enters an AI context window. This file is the complete, self-contained guide for AI assistants and developers. Base URLs: API `https://api.speedmap.ng`, MCP `https://api.speedmap.ng/mcp`, web and docs `https://speedmap.ng`. OpenAPI: `https://api.speedmap.ng/openapi.json` (also served at `https://speedmap.ng/openapi.json`). Shorter index: `https://speedmap.ng/llms.txt`. ## What Speedmap is - **Operator:** Speedily Services Limited, Nigeria. Licensed NIMC Front-End Processor (FEP), NDPA-certified, with a NIBSS agreement. Speedily runs enrollment agents in every state of Nigeria, about 150 licensed devices, and has enrolled more than 50,000 people into the National Identity Database. - **How verification is sourced:** NIN checks run through Speedily’s licensed NIMC Front-End Processor access and through NIBSS FAS. BVN, N-Face, CAC (CIV), account name enquiry (ICAD), phone lookup, SIM-swap, BVN change history and watchlist checks are NIBSS products. Speedmap does not claim direct database access to NIMC. - **Products:** NIN Verification, BVN Verification, Face Liveness & Match, Phone Identity Lookup, Business Verification (KYB), Bank Account Verification, Fraud Signals, plus preview document checks (International Passport, Driver's Licence, Voter's Card) that are sandbox-only until the respective data agreements are live. - **Channels:** REST API, SDKs (`@speedmap/node` on npm, `speedmap` on PyPI), Speedmap Connect (MCP, remote Streamable HTTP or the `@speedmap/mcp` stdio bridge), dashboard and bulk jobs. - **Design rules that hold on every channel:** 1. Tools and the assertion tier return assertions (verified / not_found, match booleans, masked names, scores, flags), never raw identity records. 2. Every identity check carries a `reason` from the fixed list of 38 NIMC purpose codes. An integration cannot invent one. 3. Checks that touch personal data require consent before execution: either the client's attestation (`consent.obtained: true`), a subject-approved consent (`consent_id`), a live selfie, or a NINAuth share code. 4. Bulk never returns rows inline. You get a job handle; results live in the dashboard, the results endpoint and webhooks. 5. Sandbox (`sk_test_`) and live (`sk_live_`) are separate. Sandbox returns deterministic test data and is free; live queries NIMC/NIBSS channels and is billed per completed check. - **What Speedmap cannot do:** there is no reverse lookup. Nothing accepts a name, phone or date of birth and returns a NIN or BVN. "Forgot my NIN" is out of scope by design. Docs: `https://speedmap.ng/docs`. Dashboard: `https://speedmap.ng/dashboard`. Keys: `https://speedmap.ng/dashboard/keys`. Support: `developers@speedmap.ng`. Security: `security@speedmap.ng`. ## Authentication Send your secret key as a bearer token on every request: ``` Authorization: Bearer sk_test_... # sandbox: free, deterministic test data Authorization: Bearer sk_live_... # live: queries providers, billed from your wallet ``` - The key prefix selects the mode. There is one base URL for both. - Keys are created in the dashboard (`https://speedmap.ng/dashboard/keys`). Keep them server-side; never ship a key in a browser or a mobile app. - Optional `Idempotency-Key` header (8 to 128 characters) on every verification `POST`. A retry with the same key and the same body returns the original result and is never billed twice. Reusing a key with a different body returns `409 idempotency_key_reused`. - All responses are JSON. All timestamps are ISO 8601 UTC. ## The Verification object Every product returns the same object. The PII-free view below is what the assertion tier, MCP tools, webhooks and list endpoints return. Accounts on the full response tier using the REST API additionally receive `provider`, `provider_reference` and `data` (the full normalized record). | Field | Meaning | |---|---| | `reference_id` | `spm_...`. Your handle for retrieval, requery, webhooks and disputes. | | `object` | Always `verification`. | | `product` | `nin`, `bvn`, `face`, `phone`, `business`, `account`, `signals`, `passport`, `drivers_license`, `voters_card`. | | `method` | The provider method used, e.g. `nin_raw`, `bvn_boolean`, `cac_rc`, `account_name_enquiry`. | | `status` | `verified`, `not_found`, or `pending` (HTTP 202; poll `GET /v1/verifications/{reference_id}`). | | `match` | `true` when the record exists and every supplied `match` field agrees; `false` on any mismatch; `null` when nothing was compared. | | `field_matches` | Per-field booleans for the values you supplied in `match`. | | `subject` | Masked identifier, e.g. `*******2109`. | | `summary` | PII-free assertion payload: masked names, scores, flags, company status. | | `mode` | `sandbox` or `live`. | | `reason` | The purpose code you declared. | | `consent` | `{ mechanism, consent_id, timestamp }`. Mechanisms include `client_attested`, `consent_hub`, `in_person_selfie`, `share_code`, `public_registry`, `not_required`. | | `billing` | `{ amount_kobo, currency }`. Zero in sandbox and for pending or failed checks. | | `source` | `api`, `mcp`, `bulk` or `dashboard`. | | `error` | `null`, or `{ code, message }` when status is `failed`. | | `latency_ms`, `created_at`, `updated_at` | Timing. | | `data` | Full tier only: the normalized record (names, date of birth, address, photo, directors, and so on). Never returned over MCP, in webhooks or in bulk results. | ## Products Every product is `POST` with a JSON body and returns the Verification object. Prices are the live list prices from the public catalog at generation time; see `GET /v1/products` for your entitlements. ### NIN Verification (`nin`) Verify a National Identity Number against the NIMC record by raw NIN, in-person selfie, or NINAuth share code. - **Endpoint:** `POST https://api.speedmap.ng/v1/verify/nin` - **Category:** identity - **Price (live):** NGN 100.00 per completed check (sandbox is free) - **Availability:** available (sandbox and live) **Request fields** (choose one variant by `method`) Variant `method = "nin"`: | Field | Type | Required | Notes | |---|---|---|---| | `method` | "nin" | yes | Raw NIN lookup. Restricted by NIMC to financial institutions and government agencies. | | `nin` | string `^\d{11}$` | yes | | | `reason` | reason enum (38 values) | yes | Purpose of the check. One of the 38 NIMC purpose codes (GET /v1/reasons). | | `consent` | object | yes | Your attestation that you obtained the data subject's consent for this purpose. Shape: `{"obtained": 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. Fields: first_name, middle_name, last_name, date_of_birth | Variant `method = "in_person"`: | Field | Type | Required | Notes | |---|---|---|---| | `method` | "in_person" | yes | NIN plus a live selfie. The selfie is the consent. Available to all business types. | | `nin` | string `^\d{11}$` | yes | | | `selfie_image` | string | yes | Base64-encoded JPEG selfie | | `reason` | reason enum (38 values) | yes | Purpose of the check. One of the 38 NIMC purpose codes (GET /v1/reasons). | | `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. Fields: first_name, middle_name, last_name, date_of_birth | Variant `method = "share_code"`: | Field | Type | Required | Notes | |---|---|---|---| | `method` | "share_code" | yes | 6-character code the subject generates in the NINAuth app. The code is the consent. | | `share_code` | string `^[A-Za-z0-9]{5,6}$` | yes | | | `reason` | reason enum (38 values) | yes | Purpose of the check. One of the 38 NIMC purpose codes (GET /v1/reasons). | | `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. Fields: first_name, middle_name, last_name, date_of_birth | **Example request** ```json { "method": "nin", "nin": "98765432109", "reason": "financialProducts", "consent": { "obtained": true }, "match": { "last_name": "Griffin" } } ``` **Example response (assertion view; `data` is appended for full-tier REST accounts), HTTP 200** ```json { "reference_id": "spm_b45cd062ceafecf91a01e64d", "object": "verification", "product": "nin", "method": "nin_raw", "status": "verified", "match": true, "field_matches": { "last_name": true }, "mode": "sandbox", "reason": "financialProducts", "subject": "*******2109", "summary": { "first_name": "A*****", "last_name": "O*****" }, "consent": { "mechanism": "client_attested", "consent_id": null, "timestamp": "2026-10-20T03:15:25.181Z" }, "source": "api", "error": null, "billing": { "amount_kobo": 0, "currency": "NGN" }, "latency_ms": 0, "created_at": "2026-10-20T03:15:25.181Z", "updated_at": "2026-10-20T03:15:25.183Z" } ``` **Sandbox test values** | Scenario | Request | |---|---| | Verified (Clarke Griffin) | `{"method": "nin", "nin": "98765432109", "reason": "financialProducts", "consent": {"obtained": true}}` | | Verified (Bellamy Blake) | `{"method": "nin", "nin": "09876543210", "reason": "employmentRecruitment", "consent": {"obtained": true}}` | | Share code | `{"method": "share_code", "share_code": "SPM001", "reason": "financialProducts"}` | | Not found | `{"method": "nin", "nin": "00000000000", "reason": "financialProducts", "consent": {"obtained": true}}` | | Provider timeout (resolves on requery) | `{"method": "nin", "nin": "99999999999", "reason": "financialProducts", "consent": {"obtained": true}}` | ### BVN Verification (`bvn`) Validate a Bank Verification Number through NIBSS: boolean match without PII, or the full record with subject consent. - **Endpoint:** `POST https://api.speedmap.ng/v1/verify/bvn` - **Category:** financial - **Price (live):** NGN 100.00 per completed check (sandbox is free) - **Availability:** available (sandbox and live) **Request fields** (choose one variant by `method`) Variant `method = "boolean"`: | Field | Type | Required | Notes | |---|---|---|---| | `method` | "boolean" | yes | Match / no-match against the BVN record. Returns no PII. The cheapest BVN check. | | `bvn` | string `^\d{11}$` | yes | | | `reason` | reason enum (38 values) | yes | Purpose of the check. One of the 38 NIMC purpose codes (GET /v1/reasons). | | `consent` | object | yes | Your attestation that you obtained the data subject's consent for this purpose. Shape: `{"obtained": 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. Fields: first_name, middle_name, last_name, date_of_birth | Variant `method = "core"`: | Field | Type | Required | Notes | |---|---|---|---| | `method` | "core" | yes | Full BVN record. Requires a consent granted by the subject through POST /v1/consents. | | `bvn` | string `^\d{11}$` | yes | | | `reason` | reason enum (38 values) | yes | Purpose of the check. One of the 38 NIMC purpose codes (GET /v1/reasons). | | `consent_id` | string `^cns_.*` | yes | | | `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. Fields: first_name, middle_name, last_name, date_of_birth | **Example request** ```json { "method": "boolean", "bvn": "22222222221", "reason": "financialProducts", "consent": { "obtained": true }, "match": { "first_name": "Clarke", "last_name": "Griffin", "date_of_birth": "1992-04-18" } } ``` **Example response (assertion view; `data` is appended for full-tier REST accounts), HTTP 200** ```json { "reference_id": "spm_971a28c37e17cc30e9811b78", "object": "verification", "product": "bvn", "method": "bvn_boolean", "status": "verified", "match": true, "field_matches": null, "mode": "sandbox", "reason": "financialProducts", "subject": "*******2221", "summary": { "match": true }, "consent": { "mechanism": "client_attested", "consent_id": null, "timestamp": "2026-10-20T03:15:25.185Z" }, "source": "api", "error": null, "billing": { "amount_kobo": 0, "currency": "NGN" }, "latency_ms": 0, "created_at": "2026-10-20T03:15:25.185Z", "updated_at": "2026-10-20T03:15:25.186Z" } ``` **Sandbox test values** | Scenario | Request | |---|---| | Boolean match | `{"method": "boolean", "bvn": "22222222221", "reason": "financialProducts", "consent": {"obtained": true}, "match": {"first_name": "Clarke", "last_name": "Griffin"}}` | | Boolean no-match | `{"method": "boolean", "bvn": "22222222222", "reason": "financialProducts", "consent": {"obtained": true}, "match": {"last_name": "Smith"}}` | | Watchlisted BVN (boolean) | `{"method": "boolean", "bvn": "22222222225", "reason": "creditBackgroundCheck", "consent": {"obtained": true}, "match": {"last_name": "Murphy"}}` | ### Face Liveness & Match (`face`) Passive liveness detection and face comparison against the BVN photo in one call, via NIBSS N-Face. - **Endpoint:** `POST https://api.speedmap.ng/v1/verify/face` - **Category:** biometric - **Price (live):** NGN 150.00 per completed check (sandbox is free) - **Availability:** available (sandbox and live) **Request fields** | Field | Type | Required | Notes | |---|---|---|---| | `image` | string | yes | Selfie as base64 JPEG/PNG, or an https URL when image_type is url | | `image_type` | one of "base64", "url" | no | Default `"base64"`. | | `bvn` | string `^\d{11}$` | no | Compare the selfie against the photo on this BVN. Omit for liveness only. | | `liveness_threshold` | number | no | Default `0.6`. | | `face_match_threshold` | number | no | Default `0.9`. | | `order` | one of "pl_fc_serial", "fc_pl_serial", "fc_pl_parallel" | no | Liveness first, comparison first, or both in parallel. Default `"pl_fc_serial"`. | | `os_type` | one of "ios", "android", "desktop", "unknown" | no | Default `"unknown"`. | | `reason` | reason enum (38 values) | yes | Purpose of the check. One of the 38 NIMC purpose codes (GET /v1/reasons). | **Example request** ```json { "image": "data-LIVE-selfie-0001", "bvn": "22222222221", "reason": "financialProducts" } ``` **Example response (sandbox, full tier), HTTP 200** ```json { "reference_id": "spm_72e4effa7755aa2fe64edd1c", "object": "verification", "product": "face", "method": "face_liveness_match", "status": "verified", "match": true, "field_matches": 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": "6068b045-0752-4d34-b2c9-5d25d293d982", "thresholds": { "liveness": 0.6, "face_match": 0.9 } }, "consent": { "mechanism": "in_person_selfie", "consent_id": null, "timestamp": "2026-10-20T03:15:25.188Z" }, "source": "api", "error": null, "billing": { "amount_kobo": 0, "currency": "NGN" }, "latency_ms": 0, "created_at": "2026-10-20T03:15:25.188Z", "updated_at": "2026-10-20T03:15:25.192Z", "provider": "sandbox", "provider_reference": "6068b045-0752-4d34-b2c9-5d25d293d982", "data": { "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": "6068b045-0752-4d34-b2c9-5d25d293d982", "thresholds": { "liveness": 0.6, "face_match": 0.9 } } } ``` **Sandbox test values** | Scenario | Request | |---|---| | Live and matching | `{"image": "data-LIVE-selfie-0001", "bvn": "22222222221", "reason": "financialProducts"}` | | Spoof (fails liveness) | `{"image": "data-SPOOF-photo-of-photo", "reason": "financialProducts"}` | | Different person | `{"image": "data-MISMATCH-selfie", "bvn": "22222222221", "reason": "financialProducts"}` | ### Phone Identity Lookup (`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. - **Endpoint:** `POST https://api.speedmap.ng/v1/verify/phone` - **Category:** identity - **Price (live):** NGN 50.00 per completed check (sandbox is free) - **Availability:** available (sandbox and live) **Request fields** | Field | Type | Required | Notes | |---|---|---|---| | `phone` | string `^(\+?234|0)[789][01]\d{8}$` | yes | | | `reason` | reason enum (38 values) | yes | Purpose of the check. One of the 38 NIMC purpose codes (GET /v1/reasons). | | `consent` | object | yes | Your attestation that you obtained the data subject's consent for this purpose. Shape: `{"obtained": 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. Fields: first_name, middle_name, last_name, date_of_birth | **Example request** ```json { "phone": "08031234567", "reason": "financialProducts", "consent": { "obtained": true }, "match": { "last_name": "Griffin" } } ``` **Example response (assertion view; `data` is appended for full-tier REST accounts), HTTP 200** ```json { "reference_id": "spm_c7004543bb53f4853b350e61", "object": "verification", "product": "phone", "method": "phone_lookup", "status": "verified", "match": true, "field_matches": { "last_name": true }, "mode": "sandbox", "reason": "financialProducts", "subject": "*******4567", "summary": { "first_name": "A*****", "last_name": "O*****" }, "consent": { "mechanism": "client_attested", "consent_id": null, "timestamp": "2026-10-20T03:15:25.194Z" }, "source": "api", "error": null, "billing": { "amount_kobo": 0, "currency": "NGN" }, "latency_ms": 0, "created_at": "2026-10-20T03:15:25.194Z", "updated_at": "2026-10-20T03:15:25.195Z" } ``` **Sandbox test values** | Scenario | Request | |---|---| | Linked number | `{"phone": "08031234567", "reason": "financialProducts", "consent": {"obtained": true}}` | | Unlinked number | `{"phone": "08000000000", "reason": "financialProducts", "consent": {"obtained": true}}` | ### Business Verification (KYB) (`business`) CAC company verification through NIBSS CIV: status, registration, directors, shareholders, share capital and winding-up. - **Endpoint:** `POST https://api.speedmap.ng/v1/verify/business` - **Category:** business - **Price (live):** NGN 500.00 per completed check (sandbox is free) - **Availability:** available (sandbox and live) **Request fields** | Field | Type | Required | Notes | |---|---|---|---| | `rc_number` | string | no | CAC RC or BN number, e.g. RC1234567 | | `tin` | string | no | | | `company_name` | string | no | | | `include` | array of "directors", "shareholders", "share_capital", "line_of_business", "affiliates", "status_report", "winding_up", "certificate" | no | Default `["directors", "shareholders", "share_capital", "status_report"]`. | | `reason` | reason enum (38 values) | no | Purpose of the check. One of the 38 NIMC purpose codes (GET /v1/reasons). | | `match` | object | no | Fields: company_name | **Example request** ```json { "rc_number": "RC1234567", "include": [ "directors", "shareholders", "share_capital", "status_report" ] } ``` **Example response (sandbox, full tier), HTTP 200** ```json { "reference_id": "spm_d166637fc490e592a47e2a29", "object": "verification", "product": "business", "method": "cac_rc", "status": "verified", "match": true, "field_matches": 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-20T03:15:25.198Z" }, "source": "api", "error": null, "billing": { "amount_kobo": 0, "currency": "NGN" }, "latency_ms": 0, "created_at": "2026-10-20T03:15:25.198Z", "updated_at": "2026-10-20T03:15:25.199Z", "provider": "sandbox", "provider_reference": "CIVD166954F88", "data": { "company_name": "SPEEDILY SERVICES LIMITED", "rc_number": "RC1234567", "tin": "12345678-0001", "type": "Private Company Limited by Shares", "status": "ACTIVE", "registered_at": "2016-05-12", "address": { "street": "12 Adetokunbo Ademola Crescent, Wuse II", "city": "Abuja", "state": "FCT" }, "email": "info@speedily.ng", "share_capital": { "authorized": 10000000, "issued": 10000000, "currency": "NGN" }, "directors": [ { "name": "THELONIOUS JAHA", "role": "Director", "appointedAt": "2016-05-12", "nationality": "Nigerian" }, { "name": "CLARKE GRIFFIN", "role": "Secretary", "appointedAt": "2018-02-01", "nationality": "Nigerian" } ], "shareholders": [ { "name": "THELONIOUS JAHA", "shares": 7000000, "percentage": 70 }, { "name": "BELLAMY BLAKE", "shares": 3000000, "percentage": 30 } ], "status_report": { "status": "ACTIVE", "active": true, "last_checked": "2026-10-20T03:15:25.199Z" } } } ``` **Sandbox test values** | Scenario | Request | |---|---| | Active company | `{"rc_number": "RC1234567"}` | | Wound-up company | `{"rc_number": "RC1111111", "include": ["directors", "winding_up"]}` | | Business name | `{"rc_number": "BN2345678"}` | | By TIN | `{"tin": "23456789-0001"}` | ### Bank Account Verification (`account`) Resolve and verify the holder name on a Nigerian bank account via NIBSS ICAD Name Enquiry. Batch-capable. - **Endpoint:** `POST https://api.speedmap.ng/v1/verify/account` - **Category:** financial - **Price (live):** NGN 25.00 per completed check (sandbox is free) - **Availability:** available (sandbox and live) **Request fields** | Field | Type | Required | Notes | |---|---|---|---| | `account_number` | string `^\d{10}$` | yes | | | `bank_code` | string `^\d{3,6}$` | yes | | | `match` | object | no | Fields: name | **Example request** ```json { "account_number": "0123456789", "bank_code": "058", "match": { "name": "Clarke Griffin" } } ``` **Example response (sandbox, full tier), HTTP 200** ```json { "reference_id": "spm_821c53fe76fbfcb4867af1b9", "object": "verification", "product": "account", "method": "account_name_enquiry", "status": "verified", "match": true, "field_matches": 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-20T03:15:25.200Z" }, "source": "api", "error": null, "billing": { "amount_kobo": 0, "currency": "NGN" }, "latency_ms": 0, "created_at": "2026-10-20T03:15:25.200Z", "updated_at": "2026-10-20T03:15:25.200Z", "provider": "sandbox", "provider_reference": "ICAD9E09E6F80F", "data": { "account_number": "0123456789", "bank_code": "058", "bank_name": "Guaranty Trust Bank", "account_name": "CLARKE ELIZA GRIFFIN", "bvn_linked": true, "name_match": true } } ``` **Sandbox test values** | Scenario | Request | |---|---| | Name matches | `{"account_number": "0123456789", "bank_code": "058", "match": {"name": "Clarke Griffin"}}` | | Name mismatch | `{"account_number": "1234567890", "bank_code": "044", "match": {"name": "John Doe"}}` | | Corporate account | `{"account_number": "4567890123", "bank_code": "011"}` | ### Fraud Signals (`signals`) One risk score from SIM-swap recency, BVN demographic changes and watchlist status. Built for account-takeover and onboarding fraud. - **Endpoint:** `POST https://api.speedmap.ng/v1/signals` - **Category:** fraud - **Price (live):** NGN 200.00 per completed check (sandbox is free) - **Availability:** available (sandbox and live) **Request fields** | Field | Type | Required | Notes | |---|---|---|---| | `phone` | string `^(\+?234|0)[789][01]\d{8}$` | no | | | `bvn` | string `^\d{11}$` | no | | | `reason` | reason enum (38 values) | yes | Purpose of the check. One of the 38 NIMC purpose codes (GET /v1/reasons). | | `consent` | object | yes | Your attestation that you obtained the data subject's consent for this purpose. Shape: `{"obtained": true}` | **Example request** ```json { "phone": "08099990000", "bvn": "22222222225", "reason": "financialProducts", "consent": { "obtained": true } } ``` **Example response (sandbox, full tier), HTTP 200** ```json { "reference_id": "spm_ff80c8882a0fd159b5753365", "object": "verification", "product": "signals", "method": "risk_signals", "status": "verified", "match": null, "field_matches": 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-20T03:15:25.202Z" }, "source": "api", "error": null, "billing": { "amount_kobo": 0, "currency": "NGN" }, "latency_ms": 0, "created_at": "2026-10-20T03:15:25.202Z", "updated_at": "2026-10-20T03:15:25.202Z", "provider": "sandbox", "provider_reference": null, "data": { "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, "sim_swap": { "swapped": true, "daysSinceSwap": 2, "lastSwapAt": "2026-10-18T03:15:25.202Z" }, "bvn_changes": [ { "field": "phone_number1", "from": "0803*****11", "to": "0809*****00", "institution": "Zenith Bank", "at": "2026-10-10T09:14:00Z" }, { "field": "last_name", "from": "MURRAY", "to": "MURPHY", "institution": "Zenith Bank", "at": "2026-10-11T15:02:00Z" } ] } } ``` **Sandbox test values** | Scenario | Request | |---|---| | High risk | `{"phone": "08099990000", "bvn": "22222222225", "reason": "financialProducts", "consent": {"obtained": true}}` | | Medium risk (recent SIM swap) | `{"phone": "08187654321", "bvn": "22222222226", "reason": "financialProducts", "consent": {"obtained": true}}` | | Low risk | `{"phone": "08031234567", "bvn": "22222222221", "reason": "financialProducts", "consent": {"obtained": true}}` | ### International Passport (`passport`) Verify a international passport against the Nigeria Immigration Service record. Sandbox only until the Nigeria Immigration Service data agreement is live. - **Endpoint:** `POST https://api.speedmap.ng/v1/verify/passport` - **Category:** document - **Price (live):** NGN 200.00 per completed check (sandbox is free) - **Availability:** preview. Works with sandbox keys; live access follows the provider data agreement. **Request fields** | Field | Type | Required | Notes | |---|---|---|---| | `passport_number` | string `^[A-Za-z]\d{8}$` | yes | | | `reason` | reason enum (38 values) | yes | Purpose of the check. One of the 38 NIMC purpose codes (GET /v1/reasons). | | `consent` | object | yes | Your attestation that you obtained the data subject's consent for this purpose. Shape: `{"obtained": 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. Fields: first_name, middle_name, last_name, date_of_birth | **Example request** ```json { "passport_number": "A12345678", "reason": "financialProducts", "consent": { "obtained": true } } ``` **Example response (assertion view; `data` is appended for full-tier REST accounts), HTTP 200** ```json { "reference_id": "spm_536aab5e071ec6185c624890", "object": "verification", "product": "passport", "method": "passport", "status": "verified", "match": true, "field_matches": 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-20T03:15:25.204Z" }, "source": "api", "error": null, "billing": { "amount_kobo": 0, "currency": "NGN" }, "latency_ms": 0, "created_at": "2026-10-20T03:15:25.204Z", "updated_at": "2026-10-20T03:15:25.205Z" } ``` **Sandbox test values** | Scenario | Request | |---|---| | Valid document | `{"passport_number": "A12345678", "reason": "financialProducts", "consent": {"obtained": true}}` | | Not found | `{"passport_number": "A00000000", "reason": "financialProducts", "consent": {"obtained": true}}` | ### Driver's Licence (`drivers_license`) Verify a driver's licence against the FRSC record. Sandbox only until the FRSC data agreement is live. - **Endpoint:** `POST https://api.speedmap.ng/v1/verify/drivers-license` - **Category:** document - **Price (live):** NGN 200.00 per completed check (sandbox is free) - **Availability:** preview. Works with sandbox keys; live access follows the provider data agreement. **Request fields** | Field | Type | Required | Notes | |---|---|---|---| | `license_number` | string `^[A-Za-z]{3}[A-Za-z0-9]{6,12}$` | yes | | | `reason` | reason enum (38 values) | yes | Purpose of the check. One of the 38 NIMC purpose codes (GET /v1/reasons). | | `consent` | object | yes | Your attestation that you obtained the data subject's consent for this purpose. Shape: `{"obtained": 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. Fields: first_name, middle_name, last_name, date_of_birth | **Example request** ```json { "license_number": "LAG12345AA01", "reason": "financialProducts", "consent": { "obtained": true } } ``` **Example response (assertion view; `data` is appended for full-tier REST accounts), HTTP 200** ```json { "reference_id": "spm_c1f44901a8a6912ee250c252", "object": "verification", "product": "drivers_license", "method": "drivers_license", "status": "verified", "match": true, "field_matches": 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-20T03:15:25.208Z" }, "source": "api", "error": null, "billing": { "amount_kobo": 0, "currency": "NGN" }, "latency_ms": 0, "created_at": "2026-10-20T03:15:25.208Z", "updated_at": "2026-10-20T03:15:25.213Z" } ``` **Sandbox test values** | Scenario | Request | |---|---| | Valid document | `{"license_number": "LAG12345AA01", "reason": "financialProducts", "consent": {"obtained": true}}` | | Not found | `{"license_number": "LAG00000AA00", "reason": "financialProducts", "consent": {"obtained": true}}` | ### Voter's Card (`voters_card`) Verify a voter's card against the INEC record. Sandbox only until the INEC data agreement is live. - **Endpoint:** `POST https://api.speedmap.ng/v1/verify/voters-card` - **Category:** document - **Price (live):** NGN 150.00 per completed check (sandbox is free) - **Availability:** preview. Works with sandbox keys; live access follows the provider data agreement. **Request fields** | Field | Type | Required | Notes | |---|---|---|---| | `vin` | string `^[A-Za-z0-9]{19}$` | yes | | | `reason` | reason enum (38 values) | yes | Purpose of the check. One of the 38 NIMC purpose codes (GET /v1/reasons). | | `consent` | object | yes | Your attestation that you obtained the data subject's consent for this purpose. Shape: `{"obtained": 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. Fields: first_name, middle_name, last_name, date_of_birth | **Example request** ```json { "vin": "90F5B1A2C3D4E5F6071", "reason": "financialProducts", "consent": { "obtained": true } } ``` **Example response (assertion view; `data` is appended for full-tier REST accounts), HTTP 200** ```json { "reference_id": "spm_0f396b51f406085e468b8f58", "object": "verification", "product": "voters_card", "method": "voters_card", "status": "verified", "match": true, "field_matches": 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-20T03:15:25.214Z" }, "source": "api", "error": null, "billing": { "amount_kobo": 0, "currency": "NGN" }, "latency_ms": 0, "created_at": "2026-10-20T03:15:25.214Z", "updated_at": "2026-10-20T03:15:25.215Z" } ``` **Sandbox test values** | Scenario | Request | |---|---| | Valid document | `{"vin": "90F5B1A2C3D4E5F6071", "reason": "financialProducts", "consent": {"obtained": true}}` | | Not found | `{"vin": "00F0B0A0C0D0E0F0000", "reason": "financialProducts", "consent": {"obtained": true}}` | ## Purpose codes (`reason`) Every NIN, BVN, phone, face, signals and document check must declare one of these 38 NIMC purpose codes. They are the same enum on the REST API (`GET /v1/reasons`) and on every MCP tool. Misdeclaring purpose is a licence-level violation, so an assistant should ask the user rather than guess. Business (CAC) and account checks do not require a reason. | Key | Label | |---|---| | `taxationEnrollment` | Taxation Enrollment | | `taxationAssessment` | Taxation Assessment | | `taxationEnforcement` | Taxation Enforcement | | `corporateAffairsDirector` | Corporate Affairs Director | | `corporateAffairsShareholder` | Corporate Affairs Shareholder | | `corporateAffairsTrustee` | Corporate Affairs Trustee | | `medical` | Medical | | `educationExam` | Education Exam | | `educationAdmission` | Education Admission | | `educationPromotion` | Education Promotion | | `employmentRecruitment` | Employment Recruitment | | `employmentEnrollment` | Employment Enrollment | | `employmentDismissal` | Employment Dismissal | | `creditBackgroundCheck` | Credit Background Check | | `financialProducts` | Financial Products | | `insurance` | Insurance | | `clearanceForPoliticalOffice` | Clearance For Political Office | | `legalCourts` | Legal Courts | | `legalEnrollment` | Legal Enrollment | | `crimePrisonIncarceration` | Crime Prison Incarceration | | `passportImmigration` | Passport Immigration | | `pensionEnrollment` | Pension Enrollment | | `socioCulturalEnrollment` | Socio-cultural Enrollment | | `entertainment` | Entertainment | | `physicalAccess` | Physical Access | | `logicalVirtualAccess` | Logical Virtual Access | | `telecommunicationSimReg` | Telecommunication Sim Registration | | `telecommunicationIotReg` | Telecommunication Iot Registration | | `aviationTransport` | Aviation Transport | | `roadTransport` | Road Transport | | `maritimeTransport` | Maritime Transport | | `railwayTransport` | Railway Transport | | `leaCheck` | Lea Check | | `defence` | Defence | | `nyscCheck` | NYSC Check | | `efccCheck` | EFCC Check | | `blacklistPolitical` | Blacklist Political | | `blacklistSocial` | Blacklist Social | ## Bank codes (`bank_code`) Use these CBN codes on `POST /v1/verify/account`. The live list is `GET /v1/banks` (REST) or the `list_banks` MCP tool. | Code | Bank | |---|---| | `044` | Access Bank | | `023` | Citibank Nigeria | | `050` | Ecobank Nigeria | | `070` | Fidelity Bank | | `011` | First Bank of Nigeria | | `214` | First City Monument Bank | | `058` | Guaranty Trust Bank | | `030` | Heritage Bank | | `301` | Jaiz Bank | | `082` | Keystone Bank | | `50211` | Kuda Microfinance Bank | | `999992` | OPay | | `999991` | PalmPay | | `076` | Polaris Bank | | `101` | Providus Bank | | `221` | Stanbic IBTC Bank | | `068` | Standard Chartered Bank | | `232` | Sterling Bank | | `032` | Union Bank of Nigeria | | `033` | United Bank for Africa | | `215` | Unity Bank | | `035` | Wema Bank | | `057` | Zenith Bank | | `090267` | Loma Microfinance Bank | ## Consent Speedmap blocks execution until consent is satisfied. There are four mechanisms: | Mechanism | When | How it is expressed | |---|---|---| | Client attestation | NIN (raw), BVN boolean, phone, signals, documents | `"consent": { "obtained": true }` in the request. You attest that you obtained the data subject's consent for the declared `reason`. | | Subject-approved consent (NIBSS Consent Hub / iGree) | BVN `core` (full record) | Create a consent with `POST /v1/consents`, the subject approves by link, email or USSD, then pass `consent_id` (`cns_...`). | | In-person selfie | NIN `in_person`, face checks | The live selfie is the consent. | | NINAuth share code | NIN `share_code` | The subject generated the code; the code is the consent. | Business (CAC) checks query a public registry and account name enquiry needs no consent (`mechanism: not_required`). ### Create a consent request `POST /v1/consents` ```json { "identifier_type": "bvn", "identifier": "22222222221", "categories": "YYNN", "channel": "redirect", "reason": "financialProducts" } ``` - `identifier_type`: `bvn` or `nin`. `identifier`: 11 digits. - `categories`: four-character Y/N mask over Personal identification, Contact, Demographic, Geographic data (data minimisation at the protocol level). `NNNN` is rejected. Default `YYNN`. - `channel`: `redirect` (link), `email` (requires `email`; the subject need not be present), or `ussd` (works on feature phones; returns a `ussd_code`). Response, HTTP 201: ```json { "id": "cns_39ea82687ea7f6418f34ba4dcd9a8c37", "object": "consent", "identifier_type": "bvn", "subject": "*******2221", "categories": "YYNN", "category_labels": [ "Personal identification", "Contact information" ], "channel": "redirect", "status": "pending", "reason": "financialProducts", "authorization_url": "https://speedmap.ng/consent/cns_39ea82687ea7f6418f34ba4dcd9a8c37", "ussd_code": null, "redirect_url": null, "granted_at": null, "expires_at": "2026-10-21T03:15:25.216Z", "created_at": "2026-10-20T03:15:25.216Z", "mode": "sandbox" } ``` Then: `GET /v1/consents/{id}` to poll `status` (`pending`, `granted`, `denied`, `revoked`, `expired`), `POST /v1/consents/{id}/revoke`, and `GET /v1/consents` to list. The `consent.updated` webhook fires on every status change. **Sandbox only:** `POST /v1/sandbox/consents/{id}/approve` or `/deny` acts as the data subject so you can test the full BVN `core` flow end to end. ## Bulk verification Up to 10,000 items of one product per job. Rows are never returned inline from the create call; you get a job handle. Each item uses exactly the same fields as the single-check request for that product. `POST /v1/bulk` ```json { "product": "account", "name": "Payout batch 42", "items": [ { "account_number": "0123456789", "bank_code": "058", "match": { "name": "Clarke Griffin" } } ] } ``` Response, HTTP 202: ```json { "id": "job_44eda372f80fc760", "object": "bulk_job", "product": "account", "name": "Payout batch 42", "status": "running", "total": 1, "processed": 0, "counts": { "verified": 0, "not_found": 0, "failed": 0 }, "progress": 0, "mode": "sandbox", "created_at": "2026-10-20T03:15:25.217Z", "completed_at": null } ``` `GET /v1/bulk/{id}` for progress and counts: ```json { "id": "job_44eda372f80fc760", "object": "bulk_job", "product": "account", "name": "Payout batch 42", "status": "completed", "total": 1, "processed": 1, "counts": { "verified": 1, "not_found": 0, "failed": 0 }, "progress": 100, "mode": "sandbox", "created_at": "2026-10-20T03:15:25.217Z", "completed_at": "2026-10-20T03:15:25.221Z" } ``` `GET /v1/bulk/{id}/results` for PII-free rows (status, match, masked subject, reference). Fetch an individual record by `reference_id` if you are entitled to it: ```json { "object": "list", "data": [ { "row": 1, "reference_id": "spm_c12fbdcc634c7f7e9910dcd6", "status": "verified", "match": true, "subject": "******6789" } ] } ``` `GET /v1/bulk` lists jobs. The `bulk.completed` webhook fires with the counts when the job finishes. Over MCP, `bulk_verify` and `get_bulk_job` expose the same behaviour and never return rows. ## Retrieving verifications - `GET /v1/verifications/{reference_id}`: one verification. A `pending` result is re-queried with the provider on read, so poll this endpoint (or wait for the `verification.completed` webhook) when you receive HTTP 202. - `GET /v1/verifications?limit=20&product=nin&status=verified&before=`: assertion-only list, newest first, cursor-paginated with `has_more`. - Pending sandbox value to practise with: NIN `99999999999` returns 202 and resolves on requery. ## Webhooks Configure endpoints per mode in the dashboard (Developers > Webhooks). Events: | Event | Payload `data` | |---|---| | `verification.completed` | The assertion view of the Verification object (no `data` record). | | `bulk.completed` | The bulk job with final counts. | | `consent.updated` | The consent object after it was granted, denied, revoked or expired. | Envelope: ```json { "id": "evt_...", "type": "verification.completed", "mode": "sandbox", "created_at": "2026-10-20T02:59:21.087Z", "data": { "...": "..." } } ``` Every delivery carries `Speedmap-Signature: t=,v1=.">` using the endpoint's secret, and `User-Agent: Speedmap-Webhooks/1.0`. Verify by recomputing the HMAC over `t + "." + body` and comparing in constant time; reject stale `t`. Non-2xx responses are retried after 5 s, 30 s and 120 s (four attempts total), with a 10 s timeout per attempt. Use the dashboard's "send test event" (`type: webhook.test`) to check your handler. ## Errors Errors are JSON with a stable `code`: ```json { "error": { "code": "invalid_request", "message": "...", "reference_id": null, "details": [ { "path": "bvn", "message": "..." } ] } } ``` | HTTP | Code(s) | Meaning | |---|---|---| | 400 | `invalid_request`, `invalid_idempotency_key`, `invalid_categories`, `too_many_items` | Validation failed; see `details`. | | 401 | `unauthorized` | Missing or invalid API key. | | 402 | `insufficient_balance` | Live wallet balance too low. Top up in the dashboard. | | 403 | `not_entitled`, `product_not_live`, `live_access_required`, `sandbox_only`, `account_suspended` | Product not enabled for your org, not yet live, live access not approved, or a sandbox-only endpoint called with a live key. | | 404 | `not_found`, `consent_not_found`, `unknown_product` | Unknown reference, job, consent or product. | | 409 | `idempotency_key_reused`, `consent_not_pending` | Same `Idempotency-Key` with a different body; or a consent decision on a consent that is no longer pending. | | 412 | `consent_not_granted`, `consent_expired`, `consent_subject_mismatch` | Consent pending or denied, expired, or issued for a different identifier. | | 422 | `provider_rejected_request` | The identity provider rejected the request as sent. | | 429 | `rate_limited` | Slow down and retry with backoff. | | 502 | `consent_hub_error` | NIBSS Consent Hub returned an error; retry later. | | 503 | `verification_unavailable`, `provider_unavailable` | Temporary provider or Speedmap-side issue. Safe to retry with the same or a new `Idempotency-Key`. | Examples captured from the sandbox: ```json { "error": { "code": "unauthorized", "message": "Missing or invalid API key. Send Authorization: Bearer sk_test_... or sk_live_..." } } ``` ```json { "error": { "code": "invalid_request", "message": "Request validation failed", "details": [ { "path": "method", "message": "Invalid discriminator value. Expected 'boolean' | 'core'" } ] } } ``` ```json { "error": { "code": "consent_not_found", "message": "No consent with that consent_id" } } ``` ## Speedmap Connect (MCP) Speedmap Connect is a remote MCP server over Streamable HTTP at `https://api.speedmap.ng/mcp`, authenticated with the same bearer API key. It is stateless (POST only), returns JSON responses, and exposes tools only (no resources or prompts). Server name: `speedmap-connect`. ### Tools | Tool | Arguments | Returns | |---|---|---| | `verify_nin` | `reason` (required), `consent_obtained: true` (required), `method` (`nin` default, or `share_code`), `nin` (11 digits), `share_code`, optional `first_name`, `last_name`, `date_of_birth` (YYYY-MM-DD) | verified / not_found, `match`, `field_matches`, masked name, `reference_id`, `dashboard_url`. Never the record. | | `verify_bvn` | `bvn` (11 digits, required), `reason`, `consent_obtained: true`, optional `first_name`, `last_name`, `date_of_birth` | NIBSS boolean match / no-match. No PII. | | `verify_business` | one of `rc_number`, `tin`, `company_name`; optional `expected_name` | Company name, RC, status (`ACTIVE`, etc.), type, registration date, state, `name_match`. | | `verify_account` | `account_number` (10 digits), `bank_code`, optional `expected_name` | `name_match`, masked account name, bank name. | | `face_match` | `image_url` (HTTPS selfie URL), `reason`, optional `bvn` | `liveness_passed`, `face_match_passed`, confidence and quality scores, `session_code`. | | `check_signals` | `reason`, `consent_obtained: true`, optional `phone`, `bvn` | `risk_score` 0-100, `risk_level`, weighted `factors`, `sim_swap_recent`, `watchlisted`, `bvn_demographic_changes`. | | `bulk_verify` | `product` (`nin`, `bvn`, `phone`, `business`, `account`, `signals`), `items` (1 to 10,000 REST-shaped objects), optional `name` | `job_id`, `status`, `total`, `dashboard_url`. Never rows. | | `get_bulk_job` | `job_id` | Progress and counts. Never rows. | | `get_verification` | `reference_id` (`spm_...`) | Status and assertion for an earlier check; resolves pending results. | | `request_consent` | `identifier_type` (`bvn` or `nin`), `identifier` (11 digits), `reason`, optional `categories` (`YYNN` default), `channel` (`redirect`, `email`, `ussd`), `email` | `consent_id`, `authorization_url`, `ussd_code`, `expires_at`. | | `list_reasons` | none | The 38 purpose codes with labels. | | `list_banks` | none | Bank names and CBN codes. | Tool results carry both a one-line human summary and `structuredContent` with the same JSON. Example `verify_nin` result: ```json { "reference_id": "spm_9912d557ef4ce8ec0825b2e3", "product": "nin", "status": "verified", "match": true, "field_matches": { "last_name": true }, "subject": "*******2109", "summary": { "first_name": "A*****", "last_name": "O*****" }, "mode": "sandbox", "dashboard_url": "https://speedmap.ng/dashboard/verifications/spm_9912d557ef4ce8ec0825b2e3" } ``` ### Rules an assistant should follow 1. Ask for the purpose and pick a `reason` from `list_reasons`; never guess. 2. Confirm the data subject consented before setting `consent_obtained: true`. 3. Prefer boolean checks (`verify_bvn`, `verify_nin` with expected names) over anything that would reveal a record. The server will not return records anyway. 4. Do not echo full NINs, BVNs or account numbers back into chat, logs or files; refer to the masked `subject` and the `reference_id`. 5. For more than a handful of checks use `bulk_verify` and report the `job_id` and `dashboard_url`. 6. On `pending`, call `get_verification` with the `reference_id` rather than re-running the check. ### Setup Remote (no install), any client that supports remote MCP servers: URL `https://api.speedmap.ng/mcp`, header `Authorization: Bearer sk_test_...`. Claude Code: ```bash claude mcp add --transport http speedmap https://api.speedmap.ng/mcp --header "Authorization: Bearer sk_test_..." ``` Claude Desktop and claude.ai (custom connector): Settings > Connectors > Add custom connector > URL `https://api.speedmap.ng/mcp`. If your plan requires OAuth-only connectors, use the stdio bridge in Claude Desktop (`claude_desktop_config.json`): ```json { "mcpServers": { "speedmap": { "command": "npx", "args": ["-y", "@speedmap/mcp"], "env": { "SPEEDMAP_API_KEY": "sk_test_..." } } } } ``` ChatGPT: Settings > Connectors > Create (Developer mode) > MCP server URL `https://api.speedmap.ng/mcp`, authentication: API key / bearer token. A Custom GPT with Actions is also available: `https://speedmap.ng/docs/chatgpt`. Cursor (`.cursor/mcp.json`): ```json { "mcpServers": { "speedmap": { "url": "https://api.speedmap.ng/mcp", "headers": { "Authorization": "Bearer ${env:SPEEDMAP_API_KEY}" } } } } ``` VS Code (`.vscode/mcp.json`): ```json { "inputs": [{ "type": "promptString", "id": "speedmap-key", "description": "Speedmap API key", "password": true }], "servers": { "speedmap": { "type": "http", "url": "https://api.speedmap.ng/mcp", "headers": { "Authorization": "Bearer ${input:speedmap-key}" } } } } ``` Windsurf, Zed, OpenAI Agents SDK, LangChain, Vercel AI SDK and n8n examples: `https://github.com/speedily/speedmap/tree/main/integrations`. Discovery files: `https://speedmap.ng/.well-known/mcp.json`, `https://speedmap.ng/.well-known/agent.json`, `https://speedmap.ng/.well-known/ai-plugin.json`. Official MCP registry name: `ng.speedmap/connect`. npm bridge: `@speedmap/mcp`. ## SDKs Both SDKs are thin, typed wrappers over the REST API described above: same field names, same Verification object, same errors (raised as exceptions with `code`, `status` and `reference_id`). ### Node.js: `@speedmap/node` ```bash npm install @speedmap/node ``` ```ts import { Speedmap } from '@speedmap/node'; const speedmap = new Speedmap(process.env.SPEEDMAP_API_KEY!); // sk_test_... or sk_live_... const result = await speedmap.verify.nin({ method: 'nin', nin: '98765432109', reason: 'financialProducts', consent: { obtained: true }, match: { last_name: 'Griffin' }, }, { idempotencyKey: 'onboarding-42' }); console.log(result.status, result.match, result.subject); // verified true *******2109 const job = await speedmap.bulk.create({ product: 'account', name: 'Payout batch 42', items: rows }); const done = await speedmap.bulk.waitFor(job.id); const consent = await speedmap.consents.create({ identifier_type: 'bvn', identifier: '22222222221', reason: 'financialProducts' }); // Webhooks: Speedmap.webhooks.constructEvent(rawBody, req.headers['speedmap-signature'], secret) ``` ### Python: `speedmap` ```bash pip install speedmap ``` ```python from speedmap import Speedmap client = Speedmap(api_key=os.environ["SPEEDMAP_API_KEY"]) result = client.verify.bvn({ "method": "boolean", "bvn": "22222222221", "reason": "financialProducts", "consent": {"obtained": True}, "match": {"first_name": "Clarke", "last_name": "Griffin"}, }) print(result["status"], result["match"]) # verified True job = client.bulk.create("account", rows, name="Payout batch 42") job = client.bulk.wait_for(job) # Webhooks: speedmap.Webhook.construct_event(raw_body, sig_header, secret) ``` Both SDKs retry on 429/503 and network errors, auto-generate an Idempotency-Key for verification calls so retries are safe, and raise `SpeedmapError` with the API `code`. Full guides: https://speedmap.ng/docs/sdks/node and https://speedmap.ng/docs/sdks/python. ## Compliance and security - **Licences and certifications held by Speedily Services Limited:** NIMC Front-End Processor licence; NDPA certification; NIBSS agreement. The platform is designed in line with the Nigeria Data Protection Act 2023 (NDPA). - **Data minimisation:** the assertion tier, MCP tools, webhooks, lists and bulk results never carry raw identity records. Consent requests scope data categories with a Y/N mask. NIN and BVN are masked in logs and dashboards. - **Lawful basis:** every check records `reason` (one of 38 NIMC purpose codes), the consent mechanism and timestamp, and is written to an immutable audit log. Dashboard "reveal" of a full record is role-gated and audited. - **Controls:** TLS 1.2+ in transit, AES-256 at rest for stored records, provider credentials server-side only, role-based access, idempotent billing, defined retention with deletion on request where legally permissible. - **No reverse lookup:** nothing accepts a phone, name or date of birth and returns a NIN or BVN. - **Report a vulnerability:** `security@speedmap.ng`; policy at `https://speedmap.ng/security`; `https://speedmap.ng/.well-known/security.txt`. - **Privacy and terms:** `https://speedmap.ng/privacy`, `https://speedmap.ng/terms`. ## Links - Docs: https://speedmap.ng/docs - Quickstart: https://speedmap.ng/docs/quickstart - Sandbox test data: https://speedmap.ng/docs/sandbox - Products: https://speedmap.ng/docs/products - Consent: https://speedmap.ng/docs/consent - Bulk: https://speedmap.ng/docs/bulk - Webhooks: https://speedmap.ng/docs/webhooks - Errors: https://speedmap.ng/docs/errors - Speedmap Connect (MCP): https://speedmap.ng/connect and https://speedmap.ng/docs/mcp - OpenAPI: https://api.speedmap.ng/openapi.json - Public catalog (JSON): https://api.speedmap.ng/public/catalog - npm: https://www.npmjs.com/package/@speedmap/mcp and https://www.npmjs.com/package/@speedmap/node - PyPI: https://pypi.org/project/speedmap/ - Source and integrations: https://github.com/speedily/speedmap - Contact: developers@speedmap.ng