Verify Identity (OCR)
API reference for extracting text from documents and verifying identity information against a profile with graded reliability and optional regional context.
Extract text from a document image and compare structured name (and optional ID) fields from the scan against the profile you supply. Responses use reliability tiers (FULL, HIGH, MEDIUM, LOW, NONE) — not a single legacy boolean — plus advisories for secondary checks (BVN, phone OTP, clearer scan, etc.).
Endpoint
POST /api/v1/ocr/verify-identity
Auth: Service API key (x-api-key). Same envelope as other B2B routes: { "success": true, "data": { ... } }.
Request body
| Field | Type | Required | Description |
|---|---|---|---|
documentUrl | string | Conditional | HTTPS URL of the document image. Required if documentBase64 is omitted. |
documentBase64 | string | Conditional | Base64-encoded image. Required if documentUrl is omitted. |
profileName | object | Yes | Name fields to match against OCR output |
profileName.firstName | string | Yes | Expected first / given name |
profileName.lastName | string | Yes | Expected surname |
profileName.middleName | string | No | Expected middle name |
profileName.idNumber | string | No | If set, compared to extracted ID number |
profileName.matchingContext | object | No | Optional hints (see below) |
expectedDocumentType | string | No | Hint for document type (e.g. NIN, PASSPORT, BVN). Auto-detected if omitted |
profileName.matchingContext (all optional)
| Field | Type | Description |
|---|---|---|
maritalStatus | 'single' | 'married' | 'unknown' | Soft prior for cross-tribal surname patterns |
stateOfOriginCode | string | 2-letter Nigeria state code (e.g. DE Delta, AN Anambra). Not required — used only as a soft signal when names are ambiguous |
lgaCode | string | Reserved for finer LGA-level disambiguation |
geoSignalSource | 'user' | 'document' | 'unknown' | How state/LGA were obtained; self-report is weighted slightly more conservatively |
Provide at least one of documentUrl or documentBase64. If both are sent, the service processes base64 first (implementation order).
Response (data object)
Top-level fields mirror VerifyIdentityResult from the service:
| Field | Type | Description |
|---|---|---|
success | boolean | Pipeline completed (OCR ran; see matchResult for match quality) |
isVerified | boolean | Strict pass: name reliability is FULL and document OCR confidence ≥ 0.85 |
matchResult | object | null | Full match payload: scores, reliabilityLevel, reliabilityScore, issues, advisories, details |
nameReliabilityLevel | string | null | Same as matchResult.reliabilityLevel when present |
requiresSecondaryValidation | boolean | true unless FULL reliability and strong OCR — use BVN/NIN/phone/manual review |
verificationAdvisories | string[] | Suggested next steps |
mismatchReasons | string[] | Machine codes (e.g. OCR_LOW_CONFIDENCE, NAMES_SWAPPED) |
userFriendlyMessages | string[] | Messages derived from mismatch reasons |
ocrConfidence | number | Overall OCR confidence 0–1 |
extractedData | object | null | firstName, lastName, middleName, fullName, idNumber, dateOfBirth, etc. |
detectedDocumentType | string | null | Detected credential type (e.g. NIN, PASSPORT) |
matchResult highlights
| Field | Description |
|---|---|
matched | Legacy structural flag; prefer reliabilityLevel for policy |
reliabilityLevel | FULL | HIGH | MEDIUM | LOW | NONE |
reliabilityScore | 0–100, aligned with tier |
score | Internal composite 0–1-style score |
issues | Same codes as mismatchReasons when applicable |
advisories | Human-readable guidance |
details.firstNameMatchQuality / lastNameMatchQuality | exact, alias_variation, fuzzy_similarity, full_name_token, etc. |
details.nigerianNameAffinity | Soft morphology priors (dominant, scores) — not a user ethnicity label |
details.regionalGeo | Present when stateOfOriginCode was sent: coarse cluster (e.g. delta_igbo), alignedWithInferredAffinity, stateCode |
isVerified: true is intentionally narrow: it requires maximum name reliability (FULL) and strong OCR (≥ 0.85). Many acceptable real-world matches return isVerified: false with HIGH or MEDIUM — use requiresSecondaryValidation and verificationAdvisories for product flows.
Example response
{
"success": true,
"data": {
"success": true,
"isVerified": true,
"nameReliabilityLevel": "FULL",
"requiresSecondaryValidation": false,
"ocrConfidence": 0.91,
"detectedDocumentType": "NIN",
"mismatchReasons": [],
"userFriendlyMessages": [],
"verificationAdvisories": [],
"extractedData": {
"fullName": "OKONKWO ADAEZE NGOZI",
"firstName": "ADAEZE",
"lastName": "OKONKWO",
"middleName": "NGOZI",
"idNumber": "12345678901",
"dateOfBirth": "1990-05-15",
"expiryDate": null,
"gender": "F",
"confidence": 0.91
},
"matchResult": {
"matched": true,
"score": 1,
"issues": [],
"reliabilityLevel": "FULL",
"reliabilityScore": 100,
"requiresSecondaryValidation": false,
"advisories": [],
"details": {
"firstNameMatch": true,
"lastNameMatch": true,
"middleNameIssue": false,
"possibleSwap": false,
"idNumberMatch": true,
"firstNameMatchQuality": "exact",
"lastNameMatchQuality": "exact",
"middleNameMatchQuality": "exact",
"matchedViaFullNameTokens": false,
"ocrConfidenceUsed": 0.91,
"nigerianNameAffinity": {
"dominant": "igbo",
"scores": { "yoruba": 0.1, "igbo": 0.7, "hausa": 0.1, "edo": 0.1 }
}
}
}
}
}
Examples
curl
curl -X POST https://api.platformxe.com/api/v1/ocr/verify-identity \
-H "Content-Type: application/json" \
-H "x-api-key: pxk_live_your_api_key_here" \
-d '{
"documentUrl": "https://example.com/documents/nin-slip.jpg",
"profileName": {
"firstName": "Adaeze",
"lastName": "Okonkwo",
"middleName": "Ngozi",
"idNumber": "12345678901",
"matchingContext": {
"stateOfOriginCode": "DE",
"geoSignalSource": "user"
}
},
"expectedDocumentType": "NIN"
}'
TypeScript SDK (field names align with REST)
import { PlatformXe } from '@caldera/platformxe-sdk';
const px = new PlatformXe({ apiKey: 'pxk_live_your_api_key_here' });
const result = await px.ocr.verifyIdentity({
documentUrl: 'https://example.com/documents/nin-slip.jpg',
profileName: {
firstName: 'Adaeze',
lastName: 'Okonkwo',
middleName: 'Ngozi',
idNumber: '12345678901',
matchingContext: {
stateOfOriginCode: 'DE',
geoSignalSource: 'user',
},
},
expectedDocumentType: 'NIN',
});
const { data } = result;
if (data.isVerified) {
console.log('Automatic verification OK');
} else if (data.nameReliabilityLevel === 'HIGH' || data.nameReliabilityLevel === 'MEDIUM') {
console.log('Needs secondary step:', data.verificationAdvisories);
}
Base64 document
import { readFileSync } from 'fs';
const documentBase64 = readFileSync('./passport-scan.jpg').toString('base64');
const result = await px.ocr.verifyIdentity({
documentBase64,
profileName: {
firstName: 'Chukwudi',
lastName: 'Eze',
},
expectedDocumentType: 'PASSPORT',
});
Error responses
| Code | Description |
|---|---|
BAD_REQUEST | Invalid JSON, missing profileName.firstName / lastName, or neither documentUrl nor documentBase64 |
UNPROCESSABLE | Document could not be read (OCR failure, unreadable image, or extraction confidence too low for matching) |
FORBIDDEN | Invalid API key |
RATE_LIMITED | Rate limit exceeded |
See also
- OCR overview — capabilities and thresholds