Face Matching
curl --request POST \
--url https://api.example.com/kyc/facematchimport requests
url = "https://api.example.com/kyc/facematch"
response = requests.post(url)
print(response.text)const options = {method: 'POST'};
fetch('https://api.example.com/kyc/facematch', options)
.then(res => res.json())
.then(res => console.log(res))
.catch(err => console.error(err));<?php
$curl = curl_init();
curl_setopt_array($curl, [
CURLOPT_URL => "https://api.example.com/kyc/facematch",
CURLOPT_RETURNTRANSFER => true,
CURLOPT_ENCODING => "",
CURLOPT_MAXREDIRS => 10,
CURLOPT_TIMEOUT => 30,
CURLOPT_HTTP_VERSION => CURL_HTTP_VERSION_1_1,
CURLOPT_CUSTOMREQUEST => "POST",
]);
$response = curl_exec($curl);
$err = curl_error($curl);
curl_close($curl);
if ($err) {
echo "cURL Error #:" . $err;
} else {
echo $response;
}package main
import (
"fmt"
"net/http"
"io"
)
func main() {
url := "https://api.example.com/kyc/facematch"
req, _ := http.NewRequest("POST", url, nil)
res, _ := http.DefaultClient.Do(req)
defer res.Body.Close()
body, _ := io.ReadAll(res.Body)
fmt.Println(string(body))
}HttpResponse<String> response = Unirest.post("https://api.example.com/kyc/facematch")
.asString();require 'uri'
require 'net/http'
url = URI("https://api.example.com/kyc/facematch")
http = Net::HTTP.new(url.host, url.port)
http.use_ssl = true
request = Net::HTTP::Post.new(url)
response = http.request(request)
puts response.read_bodyCompliance Screening
Face Matching
POST
/
kyc
/
facematch
Face Matching
curl --request POST \
--url https://api.example.com/kyc/facematchimport requests
url = "https://api.example.com/kyc/facematch"
response = requests.post(url)
print(response.text)const options = {method: 'POST'};
fetch('https://api.example.com/kyc/facematch', options)
.then(res => res.json())
.then(res => console.log(res))
.catch(err => console.error(err));<?php
$curl = curl_init();
curl_setopt_array($curl, [
CURLOPT_URL => "https://api.example.com/kyc/facematch",
CURLOPT_RETURNTRANSFER => true,
CURLOPT_ENCODING => "",
CURLOPT_MAXREDIRS => 10,
CURLOPT_TIMEOUT => 30,
CURLOPT_HTTP_VERSION => CURL_HTTP_VERSION_1_1,
CURLOPT_CUSTOMREQUEST => "POST",
]);
$response = curl_exec($curl);
$err = curl_error($curl);
curl_close($curl);
if ($err) {
echo "cURL Error #:" . $err;
} else {
echo $response;
}package main
import (
"fmt"
"net/http"
"io"
)
func main() {
url := "https://api.example.com/kyc/facematch"
req, _ := http.NewRequest("POST", url, nil)
res, _ := http.DefaultClient.Do(req)
defer res.Body.Close()
body, _ := io.ReadAll(res.Body)
fmt.Println(string(body))
}HttpResponse<String> response = Unirest.post("https://api.example.com/kyc/facematch")
.asString();require 'uri'
require 'net/http'
url = URI("https://api.example.com/kyc/facematch")
http = Net::HTTP.new(url.host, url.port)
http.use_ssl = true
request = Net::HTTP::Post.new(url)
response = http.request(request)
puts response.read_bodyBiometric face matching API for identity verification using AWS Rekognition. Supports 1:1 face verification, 1:N face search, face registration, and blacklist management.
Equivalent body flag:
Contact support to modify these settings for your tenant.
Overview
The Face Matching API provides comprehensive biometric verification capabilities:- 1:1 Verification: Compare two face images to verify identity
- 1:N Search: Search for a face in the registered collection
- Face Registration: Register faces in tenant-specific collections
- Blacklist Management: Flag faces for fraud prevention
- Presigned Uploads: Secure direct-to-S3 image uploads
Face matching must be enabled for your tenant. Contact support to enable this feature.
Endpoints
| Endpoint | Method | Description |
|---|---|---|
/kyc/facematch/verify | POST | 1:1 face verification |
/kyc/facematch/register | POST | Register face in collection |
/kyc/facematch/search | POST | 1:N face search |
/kyc/facematch/blacklist | POST | Add face to blacklist |
/kyc/facematch/faces/{face_id} | DELETE | Delete face from collection |
/kyc/facematch/collections/status | GET | Get collection status |
/kyc/facematch/upload-url | POST | Generate presigned upload URL |
Authentication
All endpoints require Bearer token authentication. Required permissions vary by endpoint:| Endpoint | Required Permission |
|---|---|
| Verify | kyc:read |
| Register | kyc:create |
| Search | kyc:read |
| Blacklist | kyc:create |
| Delete | kyc:create |
| Collection Status | kyc:read |
| Upload URL | kyc:create |
1:1 Face Verification
Compare two face images to determine if they belong to the same person.POST /kyc/facematch/verify
Request Parameters
Request Example
curl -X POST https://stg.kyc.legaltalent.ai/kyc/facematch/verify \
-H "Authorization: Bearer YOUR_TOKEN" \
-H "Content-Type: application/json" \
-d '{
"source_image": "base64_encoded_image...",
"target_image": "base64_encoded_image...",
"threshold": 0.7,
"image_format": "base64"
}'
Using S3 Images
curl -X POST https://stg.kyc.legaltalent.ai/kyc/facematch/verify \
-H "Authorization: Bearer YOUR_TOKEN" \
-H "Content-Type: application/json" \
-d '{
"source_image": "s3://bucket-name/path/to/source.jpg",
"target_image": "s3://bucket-name/path/to/target.jpg",
"image_format": "s3"
}'
Response
{
"status": "success",
"data": {
"is_match": true,
"similarity_score": 0.9523,
"confidence": 0.8765,
"processing_time_ms": 1250
}
}
Response Fields
| Field | Type | Description |
|---|---|---|
is_match | boolean | Whether faces match (similarity >= threshold) |
similarity_score | number | Similarity score (0.0-1.0) |
confidence | number | Confidence level of the comparison (0.0-1.0) |
processing_time_ms | integer | Processing time in milliseconds |
Zero Data Retention (ephemeral mode)
1:1 verification supports ephemeral data retention: the comparison runs in memory and no images, biometric templates, or match results are persisted. Only an aggregate, non-PII usage event is emitted for billing. Enable it per request with a header or body flag (or tenant-wide via your data processing configuration — contact support):curl -X POST https://stg.kyc.legaltalent.ai/kyc/facematch/verify \
-H "Authorization: Bearer YOUR_TOKEN" \
-H "X-Data-Retention: ephemeral" \
-H "Content-Type: application/json" \
-d '{
"source_image": "base64_encoded_image...",
"target_image": "base64_encoded_image...",
"image_format": "base64"
}'
"data_retention": "ephemeral".
Per-request ephemeral must be enabled for your account first: the endpoint identifier facematch.verify (or the wildcard *) has to appear in your tenant’s data_processing_config.allowed_ephemeral_endpoints, otherwise the request is rejected with 400 ZDR_NOT_ENABLED_FOR_TENANT. Tenants with data_processing_config.mode set to ephemeral run every verification ephemerally without needing the allowlist. Contact support to enable either option.
Restrictions in ephemeral mode:
| Rule | Behavior |
|---|---|
image_format must be base64 | "s3" returns 400 ZDR_UNSUPPORTED_IMAGE_FORMAT — S3-referenced images would persist in storage |
Only /kyc/facematch/verify supports it | register, search, blacklist, and upload-url return 400 ZDR_UNSUPPORTED_FOR_ENDPOINT (they persist biometric artifacts by design) |
| Invalid mode value | 400 INVALID_DATA_RETENTION — accepted values are standard and ephemeral |
Face Registration
Register a face in the tenant’s collection for future searches.POST /kyc/facematch/register
Request Parameters
Request Example
curl -X POST https://stg.kyc.legaltalent.ai/kyc/facematch/register \
-H "Authorization: Bearer YOUR_TOKEN" \
-H "Content-Type: application/json" \
-d '{
"image": "base64_encoded_image...",
"session_id": "sess_123456",
"metadata": {
"full_name": "John Doe",
"document_type": "passport"
},
"is_reference": true,
"image_format": "base64"
}'
Response
{
"status": "success",
"data": {
"face_id": "face_abc123def456",
"rekognition_face_id": "xxxxxxxx-xxxx-xxxx-xxxx-xxxxxxxxxxxx",
"s3_key": "faces/tenant_123/sess_123456/face_abc123def456.jpg",
"message": "Face registered successfully"
}
}
Response Fields
| Field | Type | Description |
|---|---|---|
face_id | string | Unique face identifier |
rekognition_face_id | string | AWS Rekognition face ID |
s3_key | string | S3 key where face image is stored |
message | string | Success message |
1:N Face Search
Search for a face across all registered faces in the tenant’s collection.POST /kyc/facematch/search
Request Parameters
Request Example
curl -X POST https://stg.kyc.legaltalent.ai/kyc/facematch/search \
-H "Authorization: Bearer YOUR_TOKEN" \
-H "Content-Type: application/json" \
-d '{
"image": "base64_encoded_image...",
"max_results": 5,
"threshold": 0.85,
"image_format": "base64"
}'
Response
{
"status": "success",
"data": {
"matches": [
{
"face_id": "face_abc123def456",
"session_id": "sess_123456",
"similarity": 0.9876,
"metadata": {
"full_name": "John Doe",
"document_type": "passport"
},
"is_blacklisted": false
},
{
"face_id": "face_xyz789ghi012",
"session_id": "sess_789012",
"similarity": 0.8532,
"metadata": {},
"is_blacklisted": true,
"blacklist_reason": "Suspected fraud"
}
],
"total_matches": 2,
"search_time_ms": 850
}
}
Response Fields
| Field | Type | Description |
|---|---|---|
matches | array | List of matching faces |
total_matches | integer | Total number of matches found |
search_time_ms | integer | Search time in milliseconds |
Match Fields
| Field | Type | Description |
|---|---|---|
face_id | string | Unique face identifier |
session_id | string | Associated KYC session ID |
similarity | number | Similarity score (0.0-1.0) |
metadata | object | Stored metadata |
is_blacklisted | boolean | Whether face is blacklisted |
blacklist_reason | string | Reason for blacklisting (if applicable) |
Blacklisted faces are included in search results (marked with
is_blacklisted: true) to alert you when a flagged person is detected.Add to Blacklist
Mark a registered face as blacklisted for fraud prevention.POST /kyc/facematch/blacklist
Request Parameters
Request Example
curl -X POST https://stg.kyc.legaltalent.ai/kyc/facematch/blacklist \
-H "Authorization: Bearer YOUR_TOKEN" \
-H "Content-Type: application/json" \
-d '{
"face_id": "face_abc123def456",
"reason": "Suspected identity fraud"
}'
Response
{
"status": "success",
"data": {
"message": "Face blacklisted successfully"
}
}
Delete Face
Remove a registered face from the collection.DELETE /kyc/facematch/faces/{face_id}
Path Parameters
| Parameter | Type | Description |
|---|---|---|
face_id | string | Face ID to delete |
Request Example
curl -X DELETE https://stg.kyc.legaltalent.ai/kyc/facematch/faces/face_abc123def456 \
-H "Authorization: Bearer YOUR_TOKEN"
Response
{
"status": "success",
"data": {
"message": "Face deleted successfully"
}
}
Collection Status
Get status information about the tenant’s face collection.GET /kyc/facematch/collections/status
Request Example
curl -X GET https://stg.kyc.legaltalent.ai/kyc/facematch/collections/status \
-H "Authorization: Bearer YOUR_TOKEN"
Response
{
"status": "success",
"data": {
"collection_name": "kyc-faces-tenant_123-prod",
"face_count": 1542,
"status": "ACTIVE"
}
}
Response Fields
| Field | Type | Description |
|---|---|---|
collection_name | string | AWS Rekognition collection name |
face_count | integer | Number of registered faces |
status | string | Collection status (ACTIVE, CREATING, etc.) |
Generate Upload URL
Generate a presigned URL for direct S3 image upload.POST /kyc/facematch/upload-url
Request Parameters
Request Example
curl -X POST https://stg.kyc.legaltalent.ai/kyc/facematch/upload-url \
-H "Authorization: Bearer YOUR_TOKEN" \
-H "Content-Type: application/json" \
-d '{
"session_id": "sess_123456",
"expires_in": 900
}'
Response
{
"status": "success",
"data": {
"upload_url": "https://bucket.s3.amazonaws.com/path?...",
"s3_uri": "s3://bucket-name/faces/tenant_123/sess_123456/uuid.jpg",
"s3_key": "faces/tenant_123/sess_123456/uuid.jpg",
"expires_at": 1732281600
}
}
Response Fields
| Field | Type | Description |
|---|---|---|
upload_url | string | Presigned URL for uploading |
s3_uri | string | S3 URI for use in other endpoints |
s3_key | string | S3 key where image will be stored |
expires_at | integer | Unix timestamp when URL expires |
Upload Workflow
- Request a presigned upload URL
- Upload image directly to S3 using the presigned URL
- Use the returned
s3_uriin register/verify/search endpoints withimage_format: "s3"
# Step 1: Get upload URL
UPLOAD_RESPONSE=$(curl -s -X POST https://stg.kyc.legaltalent.ai/kyc/facematch/upload-url \
-H "Authorization: Bearer YOUR_TOKEN" \
-H "Content-Type: application/json" \
-d '{"session_id": "sess_123"}')
UPLOAD_URL=$(echo $UPLOAD_RESPONSE | jq -r '.data.upload_url')
S3_URI=$(echo $UPLOAD_RESPONSE | jq -r '.data.s3_uri')
# Step 2: Upload image to S3
curl -X PUT "$UPLOAD_URL" \
-H "Content-Type: image/jpeg" \
--data-binary @photo.jpg
# Step 3: Use S3 URI in verification
curl -X POST https://stg.kyc.legaltalent.ai/kyc/facematch/verify \
-H "Authorization: Bearer YOUR_TOKEN" \
-H "Content-Type: application/json" \
-d "{
\"source_image\": \"$S3_URI\",
\"target_image\": \"$S3_URI_2\",
\"image_format\": \"s3\"
}"
Error Responses
400 Bad Request
{
"status": "error",
"error": "Invalid input: source_image is required"
}
403 Forbidden - Feature Not Enabled
{
"status": "error",
"error": "Face matching is not enabled for this tenant"
}
403 Forbidden - Permission Denied
{
"status": "error",
"error": "Permission denied. Required: kyc:create"
}
404 Not Found
{
"status": "error",
"error": "Face not found"
}
400 Bad Request - No Face Detected
{
"status": "error",
"error": "No face detected in one or both images"
}
400 Bad Request - Max Faces Exceeded
{
"status": "error",
"error": "Maximum 5 faces allowed per session"
}
Status Codes
| Code | Description |
|---|---|
| 200 | Success |
| 201 | Created (registration) |
| 400 | Bad Request - Invalid parameters |
| 401 | Unauthorized - Missing or invalid token |
| 403 | Forbidden - Feature not enabled or insufficient permissions |
| 404 | Not Found - Face not found |
| 500 | Internal Server Error |
Usage Examples
Python Example - Complete Verification Flow
import requests
import base64
BASE_URL = "https://stg.kyc.legaltalent.ai"
TOKEN = "YOUR_TOKEN"
headers = {
"Authorization": f"Bearer {TOKEN}",
"Content-Type": "application/json"
}
def encode_image(file_path):
"""Encode image file to base64."""
with open(file_path, "rb") as f:
return base64.b64encode(f.read()).decode("utf-8")
def verify_faces(source_path, target_path, threshold=0.7):
"""Verify if two face images match."""
response = requests.post(
f"{BASE_URL}/kyc/facematch/verify",
headers=headers,
json={
"source_image": encode_image(source_path),
"target_image": encode_image(target_path),
"threshold": threshold,
"image_format": "base64"
}
)
return response.json()
def register_face(image_path, session_id, metadata=None):
"""Register a face in the collection."""
response = requests.post(
f"{BASE_URL}/kyc/facematch/register",
headers=headers,
json={
"image": encode_image(image_path),
"session_id": session_id,
"metadata": metadata or {},
"image_format": "base64"
}
)
return response.json()
def search_face(image_path, threshold=0.8, max_results=10):
"""Search for matching faces in collection."""
response = requests.post(
f"{BASE_URL}/kyc/facematch/search",
headers=headers,
json={
"image": encode_image(image_path),
"threshold": threshold,
"max_results": max_results,
"image_format": "base64"
}
)
return response.json()
# Example usage
if __name__ == "__main__":
# 1:1 Verification
result = verify_faces("id_photo.jpg", "selfie.jpg", threshold=0.7)
print(f"Match: {result['data']['is_match']}")
print(f"Similarity: {result['data']['similarity_score']}")
# Register face
register_result = register_face(
"id_photo.jpg",
"sess_123456",
metadata={"full_name": "John Doe", "document_type": "passport"}
)
print(f"Face ID: {register_result['data']['face_id']}")
# Search for face
search_result = search_face("new_photo.jpg", threshold=0.85)
print(f"Found {search_result['data']['total_matches']} matches")
for match in search_result['data']['matches']:
if match['is_blacklisted']:
print(f"⚠️ BLACKLISTED: {match['face_id']} - {match['blacklist_reason']}")
else:
print(f"Match: {match['face_id']} ({match['similarity']:.2%})")
JavaScript Example
const BASE_URL = 'https://stg.kyc.legaltalent.ai';
const TOKEN = 'YOUR_TOKEN';
const headers = {
'Authorization': `Bearer ${TOKEN}`,
'Content-Type': 'application/json'
};
async function verifyFaces(sourceBase64, targetBase64, threshold = 0.7) {
const response = await fetch(`${BASE_URL}/kyc/facematch/verify`, {
method: 'POST',
headers,
body: JSON.stringify({
source_image: sourceBase64,
target_image: targetBase64,
threshold,
image_format: 'base64'
})
});
return response.json();
}
async function searchFace(imageBase64, threshold = 0.8, maxResults = 10) {
const response = await fetch(`${BASE_URL}/kyc/facematch/search`, {
method: 'POST',
headers,
body: JSON.stringify({
image: imageBase64,
threshold,
max_results: maxResults,
image_format: 'base64'
})
});
return response.json();
}
// Example: File input handler
async function handleFileUpload(idFile, selfieFile) {
const toBase64 = file => new Promise((resolve, reject) => {
const reader = new FileReader();
reader.readAsDataURL(file);
reader.onload = () => resolve(reader.result.split(',')[1]);
reader.onerror = reject;
});
const idBase64 = await toBase64(idFile);
const selfieBase64 = await toBase64(selfieFile);
const result = await verifyFaces(idBase64, selfieBase64);
if (result.data.is_match) {
console.log(`✅ Face verified! Similarity: ${(result.data.similarity_score * 100).toFixed(1)}%`);
} else {
console.log(`❌ Face mismatch. Similarity: ${(result.data.similarity_score * 100).toFixed(1)}%`);
}
return result;
}
Tenant Configuration
Face matching behavior is configured per tenant:| Setting | Default | Description |
|---|---|---|
enabled | false | Whether face matching is enabled |
verification_threshold | 0.6 | Default threshold for 1:1 verification |
search_threshold | 0.8 | Default threshold for 1:N search |
max_faces_per_user | 5 | Maximum faces per session |
enable_blacklist | true | Enable blacklist functionality |
model_version | Rekognition | Face detection model |
Best Practices
Image Quality
- Use high-quality images (minimum 640x480 pixels)
- Ensure good lighting with face clearly visible
- Avoid heavily compressed images
- Face should occupy at least 20% of the image
Security
- Use presigned URLs for large images to avoid base64 overhead
- Store face IDs securely - they link to biometric data
- Implement rate limiting on verification endpoints
- Review blacklist matches with human oversight
Performance
- Use S3 image format for large images (>1MB)
- Keep
max_resultsreasonable in searches - Cache collection status if needed frequently
- Batch registrations when onboarding multiple users
Compliance
- Inform users about biometric data collection
- Implement data retention policies
- Provide mechanism for users to request data deletion
- Log all face matching operations for audit trails