Skip to main content

Overview

The Hyparrow Identity Verification (KYC) API lets you confirm the identity of people and businesses and resolve bank account details in real time. It wraps our verification network behind a single, consistent interface so you can:
  • Verify individuals — BVN, NIN, voter’s card, phone number, driver’s license, and international passport.
  • Verify businesses — CAC company lookups, shareholders, directors, secretary, and TIN.
  • Run risk checks — domestic and global AML screening, credit history, and face comparison.
  • Resolve bank accounts — NUBAN account-name lookup, lookup by bank code, and bank guessing.
Verifications are exposed under three groups, all under the base path /api/v1:
The /kyc/identity and /kyc/verify groups are simply two verification options offered by our verification network. Some products (for example BVN and NIN) appear in both — pick the one whose response shape and pricing best fits your use case.

Authentication

Every request must be authenticated with your API key and secret, sent as headers:
The base URL is:
Sandbox testing. Point your integration at https://sandbox.hyparrow.cloud/api/v1 and use a test key (prefixed pk_test_ / sk_test_). Sandbox calls are not charged and never hit the live verification network. In the sandbox:
  • Any identifier of all ones (11111111111) → the not-found failure path.
  • Any other identifier, including all zeros, → a success, with an identity deterministically derived from the value you sent.
These magic values are a sandbox-only feature. Production has no test identifiers: every BVN or NIN you send there is a live lookup against the verification network, and unknown values fail like any other. See Sandbox for the full list.
There is deliberately no production identifier that returns a synthetic success. Exercise your success path in the sandbox — do not use a real person’s BVN as a test fixture.

Billing model

Verification calls are billed in one of two ways. On every call we authorize billing before hitting the verification network:
  1. Subscription quota. If you have an active subscription whose plan covers the API you’re calling and quota remains, the call consumes one unit of quota and costs nothing extra.
  2. Pay-as-you-go (PAYG). If you have no subscription covering that API, the per-call price is deducted from your wallet balance (in NGN).

How fallthrough works

When a subscription’s quota for a specific API is exhausted, the plan’s overflowBehavior decides what happens:
  • hard_block (default) → the call is rejected with 402 Payment Required (Quota exceeded for this API on your current plan). Subscribe again or wait for the period to renew.
  • payg_fallthrough → the call falls through to PAYG and the per-call price is charged to your wallet.
If you hold any active subscription but none of your plans cover the API you called, the request is hard-blocked with a 402 — you need a plan that includes that API. If you have no subscriptions at all, every call is PAYG.
Automatic refunds. Billing is authorized up front. If the verification network then returns anything other than a record — an unknown identifier (404) as much as a genuine outage (502) — we roll the charge back. You are never charged for a failed verification, however many times you repeat it. A request rejected by validation (400) is never authorized in the first place.
  • PAYG — the amount is credited back to your wallet as a transaction referenced VER-REFUND-<apiCode>-<timestamp>, visible on GET /transactions and described as Verification refund: <apiCode> (upstream failure).
  • Subscription — the consumed quota unit is credited back to the subscription, so quotaUsed returns to its previous value.
The original call still appears in GET /kyc/history with "status": "failed", so a refunded attempt is auditable from both sides.

Quota alerts

Subscriptions send email alerts at 80% and 100% quota usage for each API so you’re never surprised by a hard block.

Typical flow

1

Check your balance (and optionally subscribe)

Call GET /kyc/balance to see your wallet balance. Browse GET /kyc/plans and GET /kyc/pricing to compare a subscription against PAYG. If a plan fits your volume, subscribe with POST /kyc/subscribe.
2

Call a verification endpoint

Send a POST to the verification you need — for example /kyc/identity/bvn/basic. Billing is handled automatically: quota first, then PAYG.
3

Read the result

A successful response returns { "success": true, "data": { ... } }, where data is the verification network’s own reply forwarded unmodified — so the identity fields sit one level deeper, at data.data. See Response envelope before you write your parser. Use GET /kyc/history to audit past calls, what they cost, and how they were billed.

Account & billing endpoints

All billing endpoints are authenticated with your API key and secret.

Check balance

Response

List plans

Response
The quotas object maps each API code (the internal identifier you also see in /kyc/pricing and /kyc/history) to the number of included calls for the plan period.

Subscribe to a plan

string
required
The UUID of the plan to subscribe to. Get valid IDs from GET /kyc/plans.
Response
Subscribing debits the plan price from your wallet. If your balance is too low you get a 402:
402 Response
Subscriptions are stackable — you can hold several at once, and quota is drawn from whichever active plan covers the API being called. Use GET /kyc/subscriptions to see them all.

Call history

GET /kyc/history supports page (default 1) and limit (default 20, max 100) query parameters.
Response
billingType is sub when the call drew from quota (then amountCharged is 0) or payg when it was charged to the wallet. group is the endpoint group that served the call — identity, verify or nuban. status is success or failed; a failed call is always refunded, so it will have a matching VER-REFUND- entry on GET /transactions.

Response envelope

Identity fields are nested two levels deep, not one. Every verification endpoint forwards the verification network’s own response as the data field of the Hyparrow envelope. Read the record at data.data — reading data gives you the network’s status envelope, and a client that expects the fields there reads undefined for all of them and silently records an empty identity.
Every verification response has this two-layer structure:
The outer success and the HTTP status are Hyparrow’s contract and are stable. Everything inside data originates upstream: the envelope keys above are consistent in practice, but the fields of the innermost data vary by product (BVN basic returns different fields to a CAC lookup or an AML screen), and field names are the network’s, not ours. Treat unknown keys as additive.

Verification option A — /kyc/identity/*

All endpoints are POST and return { "success": true, "data": { ... } } with the record at data.data, as described above.

Example: BVN basic

Response
string
The primary phone number on the BVN record. Note the trailing 1 — there is no field named phoneNumber.
string
A secondary phone number, when the BVN record carries one. Frequently an empty string — most records have only one number. Always present in the response.
string
A base64-encoded JPEG passport photograph of the BVN holder, returned on every successful lookup. It is typically around 18KB of characters and dominates the response size.
This is biometric personal data. It arrives whether or not you asked for it, and there is currently no way to opt out. If you do not need it, drop it before it reaches your logs, your audit tables or your database — persisting it carries retention and data-protection obligations you may not intend to take on.
/kyc/identity/bvn/advanced returns the same envelope with a larger inner record.

Example: NIN

Response
The inner data fields above are representative, not exhaustive — the NIN record is passed through from the verification network and may include additional fields (including a base64 image). Run the call once in the sandbox against a real-shaped payload before you rely on a specific field.

Verification option B — /kyc/verify/*

All endpoints are POST and return { "success": true, "data": { ... } }.

Boolean name match

The nin and bvn/boolean-match endpoints in this group perform a name-match check rather than returning a full record:
Response
Some BVN products require the user’s explicit consent via OTP. The iGree flow is a sequence:
1

Initiate

POST /kyc/verify/bvn/igree/initiate with { "bvn": "..." } returns a sessionId.
2

Request OTP

POST /kyc/verify/bvn/igree/otp with { "sessionId": "...", "method": "sms" } delivers an OTP to the BVN holder.
3

Fetch details

POST /kyc/verify/bvn/igree/fetch with { "sessionId": "...", "otp": "..." } returns the consented BVN record.

Account lookup — /kyc/nuban/*

Example: NUBAN lookup

Response
To pin the lookup to one bank, use /kyc/nuban/lookup-with-bank and supply a bankCode from GET /kyc/nuban/banks. Not sure which bank an account belongs to? /kyc/nuban/guess-banks returns the likely candidates.

Errors & edge cases

Every non-2xx response carries a code — a stable, machine-readable classification — alongside a human-readable error. Switch on code. The error string is prose intended for your logs and may be reworded at any time; it is not part of the contract.
Changed for existing integrations. An identifier with no record used to return 502 with the reason buried in prose. It now returns 404 with "code": "RECORD_NOT_FOUND", and 502 is reserved for genuine upstream failures.If you branch on the HTTP status, add the 404 case. If you match on the text of error or data.message to detect a missing record — switch to code; the strings have changed and were never stable. Success responses are unaffected.
404 and 502 are deliberately different statuses. A mistyped BVN is a client error — nothing is broken — so it is a 404 and a conventional “retry on 5xx” policy leaves it alone. A 502 means the network genuinely failed and retrying is the right thing to do. You never need to inspect prose to tell them apart.
RECORD_NOT_FOUND
The verification network has no record for the identifier you supplied. You are not charged — the authorization is rolled back.
data.message is the verification network’s own explanation, always a string and safe to log. It is descriptive, not a contract — branch on code, not on this text. data is null when the network offered no explanation.
UPSTREAM_ERROR
The verification network failed, timed out, or was unreachable — or rejected us rather than your input. Nothing is wrong with your request; retry with backoff. You are not charged.
VALIDATION_ERROR
The request body was rejected before the verification network was called, so the call is free — a malformed identifier never costs you a lookup. field names the offending JSON property when the failure is attributable to one.
BVNs and NINs are validated as exactly 11 digits before dispatch, so {"bvn":"1234"} and {"bvn":"abcdefghijk"} are caught here rather than being spent upstream.A 400 is also returned if the authenticated identity has no API client to bill against:
INSUFFICIENT_BALANCE / QUOTA_EXCEEDED
Either a PAYG call your wallet balance cannot cover, or a subscription whose quota for that API is used up (on a hard_block plan) or which doesn’t cover the API at all.
Authentication required
The API key/secret headers are missing, or don’t match a key.
Wrong environment or inactive key
A test key used against production or a live key against the sandbox, or a key that is disabled, expired, or calling from a non-allow-listed IP.

Handling failures

Driving these paths in the sandbox. An identifier of all ones (11111111111) returns the RECORD_NOT_FOUND 404; any other well-formed value succeeds. Send a short or non-numeric BVN for the 400. Fund and drain a test wallet to exercise the 402 paths. The magic values do not exist in production.

Canonical paths

The verification groups are /kyc/identity/*, /kyc/verify/* and /kyc/nuban/*. These are the only supported paths and the only ones that appear in the API reference.
On an older path? Integrations written before these routes were introduced may be calling a legacy prefix named after the underlying provider. Those aliases still work and return byte-identical responses, but they are deprecated: they are not documented, they will not gain new endpoints, and they leak an implementation detail into your codebase.Migrate by swapping the prefix — the rest of the path, the request body and the response are unchanged. If you are unsure which prefix you are on, or want a removal timeline before you plan the work, contact support@hyparrow.com.