PlatformXeDocs
Get API Key

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

FieldTypeRequiredDescription
documentUrlstringConditionalHTTPS URL of the document image. Required if documentBase64 is omitted.
documentBase64stringConditionalBase64-encoded image. Required if documentUrl is omitted.
profileNameobjectYesName fields to match against OCR output
profileName.firstNamestringYesExpected first / given name
profileName.lastNamestringYesExpected surname
profileName.middleNamestringNoExpected middle name
profileName.idNumberstringNoIf set, compared to extracted ID number
profileName.matchingContextobjectNoOptional hints (see below)
expectedDocumentTypestringNoHint for document type (e.g. NIN, PASSPORT, BVN). Auto-detected if omitted

profileName.matchingContext (all optional)

FieldTypeDescription
maritalStatus'single' | 'married' | 'unknown'Soft prior for cross-tribal surname patterns
stateOfOriginCodestring2-letter Nigeria state code (e.g. DE Delta, AN Anambra). Not required — used only as a soft signal when names are ambiguous
lgaCodestringReserved 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:

FieldTypeDescription
successbooleanPipeline completed (OCR ran; see matchResult for match quality)
isVerifiedbooleanStrict pass: name reliability is FULL and document OCR confidence ≥ 0.85
matchResultobject | nullFull match payload: scores, reliabilityLevel, reliabilityScore, issues, advisories, details
nameReliabilityLevelstring | nullSame as matchResult.reliabilityLevel when present
requiresSecondaryValidationbooleantrue unless FULL reliability and strong OCR — use BVN/NIN/phone/manual review
verificationAdvisoriesstring[]Suggested next steps
mismatchReasonsstring[]Machine codes (e.g. OCR_LOW_CONFIDENCE, NAMES_SWAPPED)
userFriendlyMessagesstring[]Messages derived from mismatch reasons
ocrConfidencenumberOverall OCR confidence 0–1
extractedDataobject | nullfirstName, lastName, middleName, fullName, idNumber, dateOfBirth, etc.
detectedDocumentTypestring | nullDetected credential type (e.g. NIN, PASSPORT)

matchResult highlights

FieldDescription
matchedLegacy structural flag; prefer reliabilityLevel for policy
reliabilityLevelFULL | HIGH | MEDIUM | LOW | NONE
reliabilityScore0–100, aligned with tier
scoreInternal composite 0–1-style score
issuesSame codes as mismatchReasons when applicable
advisoriesHuman-readable guidance
details.firstNameMatchQuality / lastNameMatchQualityexact, alias_variation, fuzzy_similarity, full_name_token, etc.
details.nigerianNameAffinitySoft morphology priors (dominant, scores) — not a user ethnicity label
details.regionalGeoPresent 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

CodeDescription
BAD_REQUESTInvalid JSON, missing profileName.firstName / lastName, or neither documentUrl nor documentBase64
UNPROCESSABLEDocument could not be read (OCR failure, unreadable image, or extraction confidence too low for matching)
FORBIDDENInvalid API key
RATE_LIMITEDRate limit exceeded

See also