Face Recognition
/v1/face/recognitionsEnhanced due diligenceMatch a live face against the enrolled faces of the RDCPASS national digital identity. Use it to confirm that the person in front of your camera is who they claim to be, or to establish who an undocumented person is. On a match, RDCPASS returns the same identity data as KYC Validation, filtered by the scopes your application holds. Every call requires the authentication stack described in Authentication.
At a glance
- Endpoints:
POST /v1/face/recognitions(single) andPOST /v1/face/recognitions/batches(batch). - Two modes: 1:N identification (image only) and 1:1 verification (image +
identifier). - Passive liveness detection runs by default — no head movements or blinking required from the user.
- Default similarity
thresholdof0.85, adjustable per request. - A human-readable
reasonis mandatory on every call and is stored in RDCPASS’s audit trail. - Result
matchreturnsaccount,kyc.basicand your granted scopes;no_matchreturns no identity data. - Single, batch, synchronous and asynchronous modes; async results arrive on the
face_recognition.completedwebhook. - Production access requires enhanced due diligence — see Production requirements below.
Identification (1:N) vs verification (1:1)
The same endpoint serves two distinct questions. Which one you ask is decided solely by whether you send an identifier; the response reports it in mode.
| Mode | How to call | Question answered | Typical uses |
|---|---|---|---|
verification (1:1) | Send image and identifier. | Is this face the enrolled face of RDCPASS identity COD-2103-0214-4937? | Account opening, step-up authentication, SIM swap approval, cash-out at an agent, remote onboarding with a claimed ID. |
identification (1:N) | Send image only. | Which certified RDCPASS identity, if any, does this face belong to? | Identifying an unconscious patient, a beneficiary who lost their documents, or a suspected duplicate registration. |
Prefer verification whenever you know the claimed identity
A 1:1 comparison is more accurate, faster and far less intrusive than a search across the national population. Identification is reserved for situations where no claimed identity is available, and itsreason policy is reviewed more strictly during due diligence.Image requirements
Send the image either inline as base64 (image.data) or as an HTTPS URL RDCPASS can fetch (image.url). Images that fail these requirements are rejected with a 422 error and are not billed.
- Format: JPEG or PNG.
- Resolution: at least 480 × 640 pixels, portrait orientation recommended.
- Exactly one face, looking at the camera, fully visible from forehead to chin — no sunglasses, masks or hands over the face.
- Even, frontal lighting; avoid strong backlight and heavy compression.
- A photo taken live by your app or kiosk. Scans of identity documents and photos of screens fail liveness.
image.urlmust be publicly reachable over HTTPS for the duration of the request (a short-lived pre-signed URL is ideal).
Passive liveness
With liveness set to passive (the default), RDCPASS analyses the same single image for signs of a presentation attack — printed photos, screen replays, masks — before comparing faces. The user does nothing special. The outcome is reported in the liveness object (checked, passed, score). If the check fails, the call returns 422 liveness_failed and no comparison is performed.
Set liveness to none only when the image comes from a controlled capture where liveness is already guaranteed — for example an attended branch camera. The response then reports "checked": false with passed and score set to null. Your due-diligence dossier must justify every application that disables liveness.
Similarity threshold
similarity is a score from 0 to 1 expressing how closely the submitted face matches the enrolled face. The result is match when similarity is greater than or equal to threshold. The default of 0.85 balances false accepts and false rejects for most onboarding flows; tune it to your risk.
| threshold | Use when |
|---|---|
0.80 | Low-risk, high-volume checks where a false reject costs more than a false accept (e.g. event ticketing). |
0.85 (default) | Standard onboarding and step-up authentication. |
0.90 – 0.95 | High-value transactions, SIM swap approval, and every 1:N identification. |
The mandatory reason field
Biometric matching against the national identity is a significant intrusion into a citizen’s privacy, so every call must say why it is being made. reason is a free-text, human-readable justification written for an auditor, not for a machine. RDCPASS stores it with the request, the calling application, the member or system that triggered it and the result, and the audit trail is reviewed by RDCPASS and made available to the data-protection authority.
A good reason is specific and traceable
"Current account opening at Gombe branch, Kinshasa - applicant present at counter 3, ticket KIN-GOM-2026-004812""SIM swap request for +243812345678 at Vodacom shop, Lubumbashi centre - agent ID LUB-117"
Reasons that will be flagged in review
"test""KYC""verification"- The same static string on every request.
Request body
Plaintext JSON shape, before encryption. Common request fields are described once in KYC scopes & subscriptions.
| Field | Type | Required | Description |
|---|---|---|---|
| image | object | Yes | The face to match: { "data": "<base64>" } or { "url": "https://…" }. JPEG/PNG, at least 480 × 640, one face. |
| identifier | object | For 1:1 | The claimed identity, { "type", "value" } with type one of rdcpass_id, passport, ceni, driving_licence, national_id. Present ⇒ 1:1 verification; absent ⇒ 1:N identification. |
| liveness | string enum | No | passive (default) or none. See Passive liveness. |
| threshold | number | No | Minimum similarity for a match, from 0 to 1. Default 0.85. |
| reason | string | Yes | Human-readable justification for this specific check. Stored and audited. Maximum 500 characters. |
| purpose | string enum | Yes | One of the purposes approved for your application, e.g. customer_onboarding, transaction_authorization, fraud_prevention. |
| scopes | string[] | No | Subset of your application’s granted scopes to return on a match. Defaults to all granted scopes. |
| document_delivery | string enum | No | signed_url (default) or base64 for document images and biometric files. See Documents & biometrics. |
| reference | string | No | Your own identifier for this check, echoed back in the response and webhooks. |
{
"image": { "data": "/9j/4AAQSkZJRgABAQAAAQABAAD/2wBDAAgGBgcGBQgHBwcJCQgKDBQNDAsLDBkSEw8U..." },
"identifier": { "type": "rdcpass_id", "value": "COD-2103-0214-4937" },
"liveness": "passive",
"threshold": 0.85,
"reason": "Current account opening at Gombe branch, Kinshasa - applicant present at counter 3, ticket KIN-GOM-2026-004812",
"purpose": "customer_onboarding",
"scopes": ["kyc.documents.primary", "kyc.addresses", "kyc.phone_numbers", "kyc.biometrics.selfie"],
"document_delivery": "signed_url",
"reference": "cust-0001"
}{
"image": { "url": "https://uploads.example.cd/kyc/selfies/7f3a1c9e.jpg" },
"liveness": "passive",
"threshold": 0.90,
"reason": "Unconscious patient admitted to Hôpital du Cinquantenaire emergency ward without documents - identification requested by duty physician Dr. Mbuyi Ilunga Nsimba",
"purpose": "regulatory_compliance",
"reference": "adm-2026-09-26-0147"
}Response body
A successful call returns 200 OK. A no_match is a successful call, not an error.
| Field | Type | Description |
|---|---|---|
| id | string | Unique identifier of the check, prefixed face_. |
| object | string | Always face_recognition. |
| livemode | boolean | true in production, false in the sandbox. |
| reference | string | null | The reference you sent, or null. |
| status | string enum | Always completed for synchronous calls. Async and batch items can be failed. |
| mode | string enum | verification (1:1) or identification (1:N). |
| result | string enum | match or no_match. |
| similarity | number | null | Similarity score from 0 to 1. In 1:N identification, null on no_match so that no candidate is disclosed. |
| threshold | number | The threshold applied to this check. |
| liveness | object | { "checked", "passed", "score" } — the passive liveness outcome. |
| identifier | object | The identifier you sent (1:1 only). |
| account | object | Match only. RDCPASS account: rdcpass_id, status, certified, created_at, level_of_assurance. |
| kyc | object | Match only. basic is always present; every other block depends on the scopes applied. |
| scopes_applied | string[] | Scopes whose data is included, always starting with kyc.basic on a match. Empty on no_match. |
| scopes_withheld | string[] | Requested and granted scopes with no data on record for this person — omitted, not an error. |
| purpose | string | The purpose you declared. |
| reason | string | The reason you sent, exactly as recorded in the audit trail. |
| created_at | string | When the check was performed (ISO 8601, UTC). |
| result | Meaning | Identity data returned |
|---|---|---|
| match | The face matches a certified RDCPASS identity with similarity ≥ threshold (the claimed one in 1:1 mode). | account, kyc.basic and every applied scope. |
| no_match | 1:1 — the face does not match the claimed identity, or that identity has no certified account. 1:N — no certified identity reaches the threshold. | None. |
On a match, the KYC scope model applies unchanged
Thekyc block is built exactly as for KYC Validation: basic KYC is always returned, and additional blocks (documents, addresses, phone numbers, biometrics…) only for scopes your organization subscribes to, your application has selected, and your request did not narrow away. Read KYC scopes & subscriptions for the full model.{
"id": "face_3c9a7e1f52",
"object": "face_recognition",
"livemode": true,
"reference": "cust-0001",
"status": "completed",
"mode": "verification",
"result": "match",
"similarity": 0.96,
"threshold": 0.85,
"liveness": { "checked": true, "passed": true, "score": 0.97 },
"identifier": { "type": "rdcpass_id", "value": "COD-2103-0214-4937" },
"account": {
"rdcpass_id": "COD-2103-0214-4937",
"status": "active",
"certified": true,
"created_at": "2025-03-14T09:22:41Z",
"level_of_assurance": "LOA3"
},
"kyc": {
"basic": {
"full_name": "Kabeya Mwamba Tshisekedi",
"first_name": "Kabeya",
"last_name": "Tshisekedi",
"date_of_birth": "1988-04-12",
"age": 38,
"gender": "male",
"nationality": "COD"
},
"documents": {
"primary": {
"type": "passport",
"number": "OB1234567",
"issued_at": "2022-06-02",
"expires_at": "2032-06-01",
"issuing_authority": "Direction Générale de Migration",
"status": "valid",
"image": {
"content_type": "image/jpeg",
"url": "https://files.rdcpass.cd/d/9f2c41e7b0?sig=Kx82hQ",
"expires_at": "2026-09-26T10:20:02Z"
}
},
"others": [
{
"type": "ceni",
"number": "1234567890123",
"issued_at": "2023-02-17",
"expires_at": null,
"issuing_authority": "Commission Électorale Nationale Indépendante",
"status": "valid",
"image": {
"content_type": "image/jpeg",
"url": "https://files.rdcpass.cd/d/4b7d02a9c1?sig=Pm31tZ",
"expires_at": "2026-09-26T10:20:02Z"
}
}
]
},
"addresses": [
{
"type": "residential",
"province": "Kinshasa",
"city": "Kinshasa",
"commune": "Gombe",
"quartier": "Golf",
"avenue": "Avenue de la Justice",
"number": "45",
"is_primary": true,
"verified_at": "2025-03-14T09:40:12Z"
}
],
"emails": [
{ "address": "kabeya.tshisekedi@example.cd", "is_primary": true, "verified": true }
],
"phone_numbers": [
{ "number": "+243812345678", "operator": "Vodacom", "is_primary": true, "verified": true },
{ "number": "+243970112233", "operator": "Airtel", "is_primary": false, "verified": true }
],
"professions": [
{ "title": "Ingénieur réseaux", "employer": "Société Nationale d'Électricité", "sector": "energy", "since": "2016-09-01" }
],
"place_of_birth": { "country": "COD", "province": "Kasaï-Oriental", "city": "Mbuji-Mayi" },
"marital_status": "married",
"languages": ["fra", "lin", "lua"],
"religion": "catholic",
"ethnicity": "Luba",
"biometrics": {
"selfie": {
"captured_at": "2025-03-14T09:31:55Z",
"file": {
"content_type": "image/jpeg",
"url": "https://files.rdcpass.cd/d/c81e5f3a20?sig=Wq07vB",
"expires_at": "2026-09-26T10:20:02Z"
}
},
"fingerprints": [
{
"format": "ISO_19794_2",
"finger": "right_index",
"file": {
"content_type": "application/octet-stream",
"url": "https://files.rdcpass.cd/d/e2a9d7b614?sig=Hn55cR",
"expires_at": "2026-09-26T10:20:02Z"
}
}
],
"iris": [
{
"format": "ISO_19794_6",
"eye": "left",
"file": {
"content_type": "application/octet-stream",
"url": "https://files.rdcpass.cd/d/07f4b3c8e9?sig=Lt62xD",
"expires_at": "2026-09-26T10:20:02Z"
}
}
]
}
},
"scopes_applied": [
"kyc.basic",
"kyc.documents.primary",
"kyc.documents.all",
"kyc.addresses",
"kyc.emails",
"kyc.phone_numbers",
"kyc.professions",
"kyc.place_of_birth",
"kyc.marital_status",
"kyc.languages",
"kyc.religion",
"kyc.ethnicity",
"kyc.biometrics.selfie",
"kyc.biometrics.fingerprint",
"kyc.biometrics.iris"
],
"scopes_withheld": [],
"purpose": "customer_onboarding",
"reason": "Current account opening at Gombe branch, Kinshasa - applicant present at counter 3, ticket KIN-GOM-2026-004812",
"created_at": "2026-09-26T10:15:02Z"
}{
"id": "face_8e2b5d0a74",
"object": "face_recognition",
"livemode": true,
"reference": "cust-0002",
"status": "completed",
"mode": "verification",
"result": "no_match",
"similarity": 0.41,
"threshold": 0.85,
"liveness": { "checked": true, "passed": true, "score": 0.95 },
"identifier": { "type": "rdcpass_id", "value": "COD-1907-5521-0386" },
"scopes_applied": [],
"scopes_withheld": [],
"purpose": "customer_onboarding",
"reason": "Mobile money wallet upgrade to tier 2 - selfie captured in app, session KIN-APP-88213",
"created_at": "2026-09-26T10:16:40Z"
}Example request
A 1:1 verification in eight languages. Header values are illustrative — see Authentication to compute a real signature, encrypt the body and set X-RDCPASS-IV.
# Plaintext body shown for readability - encrypt and sign it as described in /docs/authentication before sending.
IMAGE_B64=$(base64 < selfie.jpg | tr -d '\n')
curl https://api.rdcpass.cd/v1/face/recognitions \
-X POST \
-H "X-RDCPASS-Key-Id: key_live_8f2a1c0e9b" \
-H "X-RDCPASS-Timestamp: 1735689600" \
-H "X-RDCPASS-Nonce: 2d7e4b1a-9c3f-4a58-b6e2-7f0c1d9a3e55" \
-H "X-RDCPASS-IV: q3Jx8mT2vLp9Wc1R" \
-H "X-RDCPASS-Signature: 8b4e1f...c93a" \
-H "Content-Type: application/json" \
-d '{
"image": { "data": "'"$IMAGE_B64"'" },
"identifier": { "type": "rdcpass_id", "value": "COD-2103-0214-4937" },
"liveness": "passive",
"threshold": 0.85,
"reason": "Current account opening at Gombe branch, Kinshasa - applicant present at counter 3, ticket KIN-GOM-2026-004812",
"purpose": "customer_onboarding",
"reference": "cust-0001"
}'Request modes
Face Recognition supports all four request modes. Add Prefer: respond-async to run asynchronously and Idempotency-Key to retry safely. Details in Single, batch & async.
| Synchronous (default) | Asynchronous (Prefer: respond-async) | |
|---|---|---|
Single — POST /v1/face/recognitions | 200 with the result. | 202 with a job; result via the face_recognition.completed webhook or GET /v1/jobs/{job_id}. |
Batch — POST /v1/face/recognitions/batches | 200 with all results, up to 50 items. | 202 with a batch, up to 10,000 items; results via webhook or GET /v1/batches/{batch_id}/results. |
Batch example
Each item carries its own image, optional identifier and required reference. Batch-level purpose, reason, liveness and scopes apply to every item unless an item overrides them — an item-level reason is recommended whenever the justification differs per person. Prefer image.url in batches to keep payloads small. A failed item (for example no face in the image) does not fail the batch.
curl https://api.rdcpass.cd/v1/face/recognitions/batches \
-X POST \
-H "X-RDCPASS-Key-Id: key_live_8f2a1c0e9b" \
-H "X-RDCPASS-Timestamp: 1735689600" \
-H "X-RDCPASS-Nonce: 2d7e4b1a-9c3f-4a58-b6e2-7f0c1d9a3e55" \
-H "X-RDCPASS-IV: q3Jx8mT2vLp9Wc1R" \
-H "X-RDCPASS-Signature: 8b4e1f...c93a" \
-H "Idempotency-Key: 5b0e8c2a-71d4-4f93-a6b1-3e9d0c7f2a18" \
-H "Content-Type: application/json" \
--data-binary @batch.json # Plaintext body shown for readability - encrypt and sign it as described in /docs/authentication before sending.{
"purpose": "fraud_prevention",
"reason": "Quarterly re-verification of registered mobile money agents in Lubumbashi, compliance programme AGT-2026-Q3",
"liveness": "passive",
"items": [
{
"reference": "agent-LUB-0412",
"image": { "url": "https://uploads.example.cd/agents/LUB-0412.jpg" },
"identifier": { "type": "rdcpass_id", "value": "COD-2103-0214-4937" }
},
{
"reference": "agent-LUB-0413",
"image": { "url": "https://uploads.example.cd/agents/LUB-0413.jpg" },
"identifier": { "type": "rdcpass_id", "value": "COD-1907-5521-0386" }
},
{
"reference": "agent-LUB-0414",
"image": { "url": "https://uploads.example.cd/agents/LUB-0414.jpg" },
"identifier": { "type": "ceni", "value": "1234567890123" }
}
]
}{
"object": "list",
"data": [
{
"reference": "agent-LUB-0412",
"status": "succeeded",
"result": {
"id": "face_1a7c3e9b20",
"object": "face_recognition",
"mode": "verification",
"result": "match",
"similarity": 0.94,
"liveness": { "checked": true, "passed": true, "score": 0.96 },
"account": { "rdcpass_id": "COD-2103-0214-4937", "status": "active", "certified": true, "...": "..." },
"kyc": { "basic": { "full_name": "Kabeya Mwamba Tshisekedi", "...": "..." } },
"scopes_applied": ["kyc.basic"],
"scopes_withheld": []
}
},
{
"reference": "agent-LUB-0413",
"status": "succeeded",
"result": {
"id": "face_6d2f8a4c11",
"object": "face_recognition",
"mode": "verification",
"result": "no_match",
"similarity": 0.38,
"liveness": { "checked": true, "passed": true, "score": 0.93 },
"scopes_applied": [],
"scopes_withheld": []
}
},
{
"reference": "agent-LUB-0414",
"status": "failed",
"error": {
"code": "multiple_faces_detected",
"message": "The image contains 2 faces. Submit an image with exactly one face."
}
}
],
"next_cursor": null
}Asynchronous example
Send the same body with Prefer: respond-async. RDCPASS answers immediately with 202 Accepted and a job; when the check completes, the face_recognition.completed event is delivered to your application’s webhook URL with the full result embedded. You can also poll GET /v1/jobs/{job_id}. Asynchronous batches return a batch object and emit batch.completed or batch.completed_with_errors. See Webhooks for signature verification.
HTTP/1.1 202 Accepted
Content-Type: application/json
{
"id": "job_9e4c2a7b1d",
"object": "job",
"service": "face_recognition",
"status": "pending",
"created_at": "2026-09-26T10:15:00Z",
"result_url": "/v1/jobs/job_9e4c2a7b1d"
}{
"id": "evt_4d8b1e6f2a9c7035",
"object": "event",
"type": "face_recognition.completed",
"livemode": true,
"created_at": "2026-09-26T10:15:04Z",
"data": {
"object": {
"id": "job_9e4c2a7b1d",
"object": "job",
"service": "face_recognition",
"status": "completed",
"created_at": "2026-09-26T10:15:00Z",
"result_url": "/v1/jobs/job_9e4c2a7b1d",
"result": {
"id": "face_3c9a7e1f52",
"object": "face_recognition",
"reference": "cust-0001",
"status": "completed",
"mode": "verification",
"result": "match",
"similarity": 0.96,
"liveness": { "checked": true, "passed": true, "score": 0.97 },
"account": { "rdcpass_id": "COD-2103-0214-4937", "...": "..." },
"kyc": { "basic": { "...": "..." } },
"scopes_applied": ["kyc.basic", "kyc.documents.primary"],
"scopes_withheld": []
}
}
}
}Production requirements
Face Recognition requires enhanced due diligence
Sandbox access is immediate, but production keys for an application with Face Recognition enabled are issued only after RDCPASS approves an enhanced due-diligence dossier. Typical review time is 10 to 20 business days.In addition to the standard business and application due diligence, your compliance officer must provide:
- A documented reason policy — the exhaustive list of situations in which your staff or systems trigger a face check, who may trigger it, the reason format they use, and whether 1:N identification is needed at all. Production
reasonvalues are audited against this policy. - A data-protection impact assessment (DPIA) covering the capture, transmission, retention and deletion of face images and of any biometric data returned.
- Biometric-tier controls — the same rules as the
kyc.biometrics.*scopes: legal basis, a named data-protection officer, a penetration-test report, encryption at rest, and a retention policy under which submitted images are deleted once the check is complete. - An on-site or video audit of the capture process (kiosk, branch or app), including how the citizen is informed before their face is captured.
- Liveness justification for any flow that sets
livenesstonone.
The full process, statuses and document checklist are described in Going to production.
Errors
Errors use the standard format described in Errors. The codes below are the ones specific to, or most relevant for, Face Recognition. Rejected requests are not billed. A request without reason is rejected with 400 Bad Request.
| Code | HTTP | Cause | What to do |
|---|---|---|---|
| no_face_detected | 422 | No face was found in the image. | Recapture with the face centred and fully visible. |
| multiple_faces_detected | 422 | More than one face is visible. | Recapture with only the subject in frame. |
| image_quality_insufficient | 422 | Resolution below 480 × 640, blur, overexposure or excessive compression. | Recapture in better light; send the original, uncompressed image. |
| liveness_failed | 422 | The passive liveness check detected a presentation attack or could not confirm a live person. | Ask the user to retake a live photo. Repeated failures for the same identity may indicate fraud. |
| invalid_identifier | 400 | The identifier value is malformed for its type. | Check the format (e.g. COD-2103-0214-4937). |
| unsupported_identifier_type | 400 | The identifier type is not accepted. | Use rdcpass_id, passport, ceni, driving_licence or national_id. |
| scope_not_granted | 403 | A requested scope is not granted to your application. | Remove it from scopes, or have an administrator subscribe and select it. |
| purpose_not_approved | 403 | The purpose is not among those approved for your application. | Use an approved purpose or request an update in the console. |
| service_not_enabled | 403 | Face Recognition is not enabled on this application. | Enable the service on the application in the console. |
| production_access_required | 403 | Production keys used before enhanced due diligence was approved. | Complete the process described in Going to production. |
| batch_too_large | 413 | A synchronous batch contains more than 50 items. | Send it with Prefer: respond-async (up to 10,000 items). |
| insufficient_balance | 402 | Prepaid wallet is empty. | Top up the wallet; see Billing. |
| rate_limited | 429 | Burst rate limit exceeded. | Back off per retry_after and retry with the same Idempotency-Key. |
{
"error": "liveness_failed",
"message": "Passive liveness check failed (score 0.22). The image appears to be a photo of a screen or a printed picture."
}Next steps
- KYC scopes & subscriptions — what a match can return and how to subscribe.
- Documents & biometrics — handling signed URLs and biometric files.
- Going to production — prepare the enhanced due-diligence dossier.
- KYC Validation — verify an identity without a face.
- Webhooks — receive asynchronous results.