curl -X POST 'https://verification.didit.me/v3/face-search/' \
-H 'x-api-key: YOUR_API_KEY' \
-F 'user_image=@./selfie.jpg' \
-F 'search_type=most_similar' \
-F 'save_api_request=true' \
-F 'vendor_data=user-123'import requests
url = 'https://verification.didit.me/v3/face-search/'
headers = {'x-api-key': 'YOUR_API_KEY'}
with open('selfie.jpg', 'rb') as f:
files = {'user_image': ('selfie.jpg', f, 'image/jpeg')}
data = {
'search_type': 'most_similar',
'save_api_request': 'true',
'vendor_data': 'user-123',
}
resp = requests.post(url, headers=headers, files=files, data=data, timeout=60)
resp.raise_for_status()
body = resp.json()
print('status:', body['face_search']['status'])
print('matches:', body['face_search']['total_matches'])
for m in body['face_search']['matches']:
print(f" session={m['session_id']} similarity={m['similarity_percentage']} blocklisted={m['is_blocklisted']}")import fs from 'node:fs';
const form = new FormData();
form.append('user_image', new Blob([fs.readFileSync('./selfie.jpg')]), 'selfie.jpg');
form.append('search_type', 'most_similar');
form.append('save_api_request', 'true');
form.append('vendor_data', 'user-123');
const response = await fetch('https://verification.didit.me/v3/face-search/', {
method: 'POST',
headers: { 'x-api-key': 'YOUR_API_KEY' },
body: form,
});
if (!response.ok) throw new Error(`Face search failed: ${response.status}`);
const body = await response.json();
console.log('status:', body.face_search.status, 'matches:', body.face_search.total_matches);
body.face_search.matches.forEach(m =>
console.log(` session=${m.session_id} similarity=${m.similarity_percentage} blocklisted=${m.is_blocklisted}`)
);<?php
$curl = curl_init();
curl_setopt_array($curl, [
CURLOPT_URL => "https://verification.didit.me/v3/face-search/",
CURLOPT_RETURNTRANSFER => true,
CURLOPT_ENCODING => "",
CURLOPT_MAXREDIRS => 10,
CURLOPT_TIMEOUT => 30,
CURLOPT_HTTP_VERSION => CURL_HTTP_VERSION_1_1,
CURLOPT_CUSTOMREQUEST => "POST",
CURLOPT_POSTFIELDS => "-----011000010111000001101001\r\nContent-Disposition: form-data; name=\"user_image\"; filename=\"example-file\"\r\nContent-Type: application/octet-stream\r\n\r\n(binary JPEG/PNG selfie)\r\n-----011000010111000001101001\r\nContent-Disposition: form-data; name=\"search_type\"\r\n\r\nmost_similar\r\n-----011000010111000001101001\r\nContent-Disposition: form-data; name=\"rotate_image\"\r\n\r\nfalse\r\n-----011000010111000001101001\r\nContent-Disposition: form-data; name=\"save_api_request\"\r\n\r\ntrue\r\n-----011000010111000001101001\r\nContent-Disposition: form-data; name=\"vendor_data\"\r\n\r\nuser-123\r\n-----011000010111000001101001\r\nContent-Disposition: form-data; name=\"metadata\"\r\n\r\n{\r\n \"flow\": \"dedup_check\"\r\n}\r\n-----011000010111000001101001--",
CURLOPT_HTTPHEADER => [
"Content-Type: multipart/form-data; boundary=---011000010111000001101001",
"x-api-key: <api-key>"
],
]);
$response = curl_exec($curl);
$err = curl_error($curl);
curl_close($curl);
if ($err) {
echo "cURL Error #:" . $err;
} else {
echo $response;
}package main
import (
"fmt"
"strings"
"net/http"
"io"
)
func main() {
url := "https://verification.didit.me/v3/face-search/"
payload := strings.NewReader("-----011000010111000001101001\r\nContent-Disposition: form-data; name=\"user_image\"; filename=\"example-file\"\r\nContent-Type: application/octet-stream\r\n\r\n(binary JPEG/PNG selfie)\r\n-----011000010111000001101001\r\nContent-Disposition: form-data; name=\"search_type\"\r\n\r\nmost_similar\r\n-----011000010111000001101001\r\nContent-Disposition: form-data; name=\"rotate_image\"\r\n\r\nfalse\r\n-----011000010111000001101001\r\nContent-Disposition: form-data; name=\"save_api_request\"\r\n\r\ntrue\r\n-----011000010111000001101001\r\nContent-Disposition: form-data; name=\"vendor_data\"\r\n\r\nuser-123\r\n-----011000010111000001101001\r\nContent-Disposition: form-data; name=\"metadata\"\r\n\r\n{\r\n \"flow\": \"dedup_check\"\r\n}\r\n-----011000010111000001101001--")
req, _ := http.NewRequest("POST", url, payload)
req.Header.Add("x-api-key", "<api-key>")
req.Header.Add("Content-Type", "multipart/form-data; boundary=---011000010111000001101001")
res, _ := http.DefaultClient.Do(req)
defer res.Body.Close()
body, _ := io.ReadAll(res.Body)
fmt.Println(string(body))
}HttpResponse<String> response = Unirest.post("https://verification.didit.me/v3/face-search/")
.header("x-api-key", "<api-key>")
.header("Content-Type", "multipart/form-data; boundary=---011000010111000001101001")
.body("-----011000010111000001101001\r\nContent-Disposition: form-data; name=\"user_image\"; filename=\"example-file\"\r\nContent-Type: application/octet-stream\r\n\r\n(binary JPEG/PNG selfie)\r\n-----011000010111000001101001\r\nContent-Disposition: form-data; name=\"search_type\"\r\n\r\nmost_similar\r\n-----011000010111000001101001\r\nContent-Disposition: form-data; name=\"rotate_image\"\r\n\r\nfalse\r\n-----011000010111000001101001\r\nContent-Disposition: form-data; name=\"save_api_request\"\r\n\r\ntrue\r\n-----011000010111000001101001\r\nContent-Disposition: form-data; name=\"vendor_data\"\r\n\r\nuser-123\r\n-----011000010111000001101001\r\nContent-Disposition: form-data; name=\"metadata\"\r\n\r\n{\r\n \"flow\": \"dedup_check\"\r\n}\r\n-----011000010111000001101001--")
.asString();require 'uri'
require 'net/http'
url = URI("https://verification.didit.me/v3/face-search/")
http = Net::HTTP.new(url.host, url.port)
http.use_ssl = true
request = Net::HTTP::Post.new(url)
request["x-api-key"] = '<api-key>'
request["Content-Type"] = 'multipart/form-data; boundary=---011000010111000001101001'
request.body = "-----011000010111000001101001\r\nContent-Disposition: form-data; name=\"user_image\"; filename=\"example-file\"\r\nContent-Type: application/octet-stream\r\n\r\n(binary JPEG/PNG selfie)\r\n-----011000010111000001101001\r\nContent-Disposition: form-data; name=\"search_type\"\r\n\r\nmost_similar\r\n-----011000010111000001101001\r\nContent-Disposition: form-data; name=\"rotate_image\"\r\n\r\nfalse\r\n-----011000010111000001101001\r\nContent-Disposition: form-data; name=\"save_api_request\"\r\n\r\ntrue\r\n-----011000010111000001101001\r\nContent-Disposition: form-data; name=\"vendor_data\"\r\n\r\nuser-123\r\n-----011000010111000001101001\r\nContent-Disposition: form-data; name=\"metadata\"\r\n\r\n{\r\n \"flow\": \"dedup_check\"\r\n}\r\n-----011000010111000001101001--"
response = http.request(request)
puts response.read_body{
"request_id": "a1b2c3d4-e5f6-7890-1234-567890abcdef",
"face_search": {
"status": "Declined",
"total_matches": 1,
"matches": [
{
"session_id": "882c42d5-8a4d-4d20-8080-a22f57822c86",
"session_number": 323442,
"similarity_percentage": 99.99,
"source": "session",
"vendor_data": "user-1",
"verification_date": "2025-01-01T00:00:00Z",
"user_details": {
"full_name": "Jane Marie Doe",
"document_type": "ID",
"document_number": "X1234567"
},
"match_image_url": "https://<media-host>/face/3f6a1c2e/reference.jpg",
"status": "Approved",
"is_blocklisted": true,
"is_allowlisted": false,
"api_service": null,
"vendor_user_id": "2f7c1a9e-8b4d-4c6a-9e1f-3d5b7a9c1e2f",
"biometric_template_id": null
}
],
"user_image": {
"entities": [
{
"bbox": [
40,
40,
120,
120
],
"confidence": 0.732973
}
],
"best_angle": 0
},
"warnings": [
{
"risk": "FACE_IN_BLOCKLIST",
"feature": "LIVENESS",
"additional_data": {
"blocklisted_session_id": "882c42d5-8a4d-4d20-8080-a22f57822c86",
"blocklisted_session_number": 323442,
"api_service": null
},
"log_type": "error",
"short_description": "Face in blocklist",
"long_description": "The system identified a face in the blocklist, which means the face is not allowed to be verified."
}
]
},
"vendor_data": "user-123",
"metadata": null,
"created_at": "2026-06-12T01:04:42.763237+00:00"
}Face Search
Search a face image (1:N) against your application’s face search index — faces enrolled from verification sessions, saved standalone API calls, direct user-profile face uploads, and your block/allow lists — and get back ranked similarity matches plus blocklist and duplicate warnings.
How it works. The largest detected face in user_image is embedded and searched against the index, scoped to your application. Up to 5 matches above the similarity floor are returned in matches, each with similarity_percentage, the originating session (session_id, session_number, status, vendor_data), user_details extracted during that session’s document verification, is_blocklisted / is_allowlisted flags, and a source discriminator (session, imported, or list_entry).
search_type=most_similar(default) ranks every enrolled face by similarity — use it for deduplication and fraud-ring investigation.search_type=blocklisted_or_approvedrestricts candidates to blocklisted faces, allowlisted faces, faces from approved sessions, and imported user-profile faces, ranking blocklisted entries first — use it when the primary goal is blocklist screening.
status is Declined only when a FACE_IN_BLOCKLIST or POSSIBLE_FACE_IN_BLOCKLIST warning fires; DUPLICATED_FACE / POSSIBLE_DUPLICATED_FACE (information) and MULTIPLE_FACES_DETECTED (warning) never decline. Unlike Passive Liveness, this endpoint does not exclude prior sessions with the same vendor_data from matching. A passive-liveness analysis also runs internally and is stored with the persisted session, but its result is not part of this response.
Billing. Each 200 response consumes one Face Search API credit (standalone APIs have no free tier). Insufficient balance returns 403 before any image processing.
Session persistence and face enrollment (save_api_request, default true). When true, the call is persisted as an API-type session (Business Console, GET /v3/session/{sessionId}/decision/ via the returned request_id, status.updated webhook) and the searched face is enrolled into your face search index. Faces enrolled by Face Search calls are excluded from future Face Search results, so repeated searches never match each other. When false, the call is one-shot: nothing is stored, the face is not enrolled, and match_image_url values are internal storage paths rather than downloadable URLs.
Sandbox. Sandbox API keys skip all processing and billing: after request validation (malformed input still returns 400), the endpoint returns a static Approved mock payload, no session is persisted, and no credits are consumed.
Authentication. Send your application’s API key in the x-api-key header. Missing or invalid credentials return 403 ({"detail": "You do not have permission to perform this action."}) — this API never returns 401.
Rate limit. Shared write budget of 300 requests/min per API key across all POST/PATCH/DELETE endpoints; exceeding it returns 429.
curl -X POST 'https://verification.didit.me/v3/face-search/' \
-H 'x-api-key: YOUR_API_KEY' \
-F 'user_image=@./selfie.jpg' \
-F 'search_type=most_similar' \
-F 'save_api_request=true' \
-F 'vendor_data=user-123'import requests
url = 'https://verification.didit.me/v3/face-search/'
headers = {'x-api-key': 'YOUR_API_KEY'}
with open('selfie.jpg', 'rb') as f:
files = {'user_image': ('selfie.jpg', f, 'image/jpeg')}
data = {
'search_type': 'most_similar',
'save_api_request': 'true',
'vendor_data': 'user-123',
}
resp = requests.post(url, headers=headers, files=files, data=data, timeout=60)
resp.raise_for_status()
body = resp.json()
print('status:', body['face_search']['status'])
print('matches:', body['face_search']['total_matches'])
for m in body['face_search']['matches']:
print(f" session={m['session_id']} similarity={m['similarity_percentage']} blocklisted={m['is_blocklisted']}")import fs from 'node:fs';
const form = new FormData();
form.append('user_image', new Blob([fs.readFileSync('./selfie.jpg')]), 'selfie.jpg');
form.append('search_type', 'most_similar');
form.append('save_api_request', 'true');
form.append('vendor_data', 'user-123');
const response = await fetch('https://verification.didit.me/v3/face-search/', {
method: 'POST',
headers: { 'x-api-key': 'YOUR_API_KEY' },
body: form,
});
if (!response.ok) throw new Error(`Face search failed: ${response.status}`);
const body = await response.json();
console.log('status:', body.face_search.status, 'matches:', body.face_search.total_matches);
body.face_search.matches.forEach(m =>
console.log(` session=${m.session_id} similarity=${m.similarity_percentage} blocklisted=${m.is_blocklisted}`)
);<?php
$curl = curl_init();
curl_setopt_array($curl, [
CURLOPT_URL => "https://verification.didit.me/v3/face-search/",
CURLOPT_RETURNTRANSFER => true,
CURLOPT_ENCODING => "",
CURLOPT_MAXREDIRS => 10,
CURLOPT_TIMEOUT => 30,
CURLOPT_HTTP_VERSION => CURL_HTTP_VERSION_1_1,
CURLOPT_CUSTOMREQUEST => "POST",
CURLOPT_POSTFIELDS => "-----011000010111000001101001\r\nContent-Disposition: form-data; name=\"user_image\"; filename=\"example-file\"\r\nContent-Type: application/octet-stream\r\n\r\n(binary JPEG/PNG selfie)\r\n-----011000010111000001101001\r\nContent-Disposition: form-data; name=\"search_type\"\r\n\r\nmost_similar\r\n-----011000010111000001101001\r\nContent-Disposition: form-data; name=\"rotate_image\"\r\n\r\nfalse\r\n-----011000010111000001101001\r\nContent-Disposition: form-data; name=\"save_api_request\"\r\n\r\ntrue\r\n-----011000010111000001101001\r\nContent-Disposition: form-data; name=\"vendor_data\"\r\n\r\nuser-123\r\n-----011000010111000001101001\r\nContent-Disposition: form-data; name=\"metadata\"\r\n\r\n{\r\n \"flow\": \"dedup_check\"\r\n}\r\n-----011000010111000001101001--",
CURLOPT_HTTPHEADER => [
"Content-Type: multipart/form-data; boundary=---011000010111000001101001",
"x-api-key: <api-key>"
],
]);
$response = curl_exec($curl);
$err = curl_error($curl);
curl_close($curl);
if ($err) {
echo "cURL Error #:" . $err;
} else {
echo $response;
}package main
import (
"fmt"
"strings"
"net/http"
"io"
)
func main() {
url := "https://verification.didit.me/v3/face-search/"
payload := strings.NewReader("-----011000010111000001101001\r\nContent-Disposition: form-data; name=\"user_image\"; filename=\"example-file\"\r\nContent-Type: application/octet-stream\r\n\r\n(binary JPEG/PNG selfie)\r\n-----011000010111000001101001\r\nContent-Disposition: form-data; name=\"search_type\"\r\n\r\nmost_similar\r\n-----011000010111000001101001\r\nContent-Disposition: form-data; name=\"rotate_image\"\r\n\r\nfalse\r\n-----011000010111000001101001\r\nContent-Disposition: form-data; name=\"save_api_request\"\r\n\r\ntrue\r\n-----011000010111000001101001\r\nContent-Disposition: form-data; name=\"vendor_data\"\r\n\r\nuser-123\r\n-----011000010111000001101001\r\nContent-Disposition: form-data; name=\"metadata\"\r\n\r\n{\r\n \"flow\": \"dedup_check\"\r\n}\r\n-----011000010111000001101001--")
req, _ := http.NewRequest("POST", url, payload)
req.Header.Add("x-api-key", "<api-key>")
req.Header.Add("Content-Type", "multipart/form-data; boundary=---011000010111000001101001")
res, _ := http.DefaultClient.Do(req)
defer res.Body.Close()
body, _ := io.ReadAll(res.Body)
fmt.Println(string(body))
}HttpResponse<String> response = Unirest.post("https://verification.didit.me/v3/face-search/")
.header("x-api-key", "<api-key>")
.header("Content-Type", "multipart/form-data; boundary=---011000010111000001101001")
.body("-----011000010111000001101001\r\nContent-Disposition: form-data; name=\"user_image\"; filename=\"example-file\"\r\nContent-Type: application/octet-stream\r\n\r\n(binary JPEG/PNG selfie)\r\n-----011000010111000001101001\r\nContent-Disposition: form-data; name=\"search_type\"\r\n\r\nmost_similar\r\n-----011000010111000001101001\r\nContent-Disposition: form-data; name=\"rotate_image\"\r\n\r\nfalse\r\n-----011000010111000001101001\r\nContent-Disposition: form-data; name=\"save_api_request\"\r\n\r\ntrue\r\n-----011000010111000001101001\r\nContent-Disposition: form-data; name=\"vendor_data\"\r\n\r\nuser-123\r\n-----011000010111000001101001\r\nContent-Disposition: form-data; name=\"metadata\"\r\n\r\n{\r\n \"flow\": \"dedup_check\"\r\n}\r\n-----011000010111000001101001--")
.asString();require 'uri'
require 'net/http'
url = URI("https://verification.didit.me/v3/face-search/")
http = Net::HTTP.new(url.host, url.port)
http.use_ssl = true
request = Net::HTTP::Post.new(url)
request["x-api-key"] = '<api-key>'
request["Content-Type"] = 'multipart/form-data; boundary=---011000010111000001101001'
request.body = "-----011000010111000001101001\r\nContent-Disposition: form-data; name=\"user_image\"; filename=\"example-file\"\r\nContent-Type: application/octet-stream\r\n\r\n(binary JPEG/PNG selfie)\r\n-----011000010111000001101001\r\nContent-Disposition: form-data; name=\"search_type\"\r\n\r\nmost_similar\r\n-----011000010111000001101001\r\nContent-Disposition: form-data; name=\"rotate_image\"\r\n\r\nfalse\r\n-----011000010111000001101001\r\nContent-Disposition: form-data; name=\"save_api_request\"\r\n\r\ntrue\r\n-----011000010111000001101001\r\nContent-Disposition: form-data; name=\"vendor_data\"\r\n\r\nuser-123\r\n-----011000010111000001101001\r\nContent-Disposition: form-data; name=\"metadata\"\r\n\r\n{\r\n \"flow\": \"dedup_check\"\r\n}\r\n-----011000010111000001101001--"
response = http.request(request)
puts response.read_body{
"request_id": "a1b2c3d4-e5f6-7890-1234-567890abcdef",
"face_search": {
"status": "Declined",
"total_matches": 1,
"matches": [
{
"session_id": "882c42d5-8a4d-4d20-8080-a22f57822c86",
"session_number": 323442,
"similarity_percentage": 99.99,
"source": "session",
"vendor_data": "user-1",
"verification_date": "2025-01-01T00:00:00Z",
"user_details": {
"full_name": "Jane Marie Doe",
"document_type": "ID",
"document_number": "X1234567"
},
"match_image_url": "https://<media-host>/face/3f6a1c2e/reference.jpg",
"status": "Approved",
"is_blocklisted": true,
"is_allowlisted": false,
"api_service": null,
"vendor_user_id": "2f7c1a9e-8b4d-4c6a-9e1f-3d5b7a9c1e2f",
"biometric_template_id": null
}
],
"user_image": {
"entities": [
{
"bbox": [
40,
40,
120,
120
],
"confidence": 0.732973
}
],
"best_angle": 0
},
"warnings": [
{
"risk": "FACE_IN_BLOCKLIST",
"feature": "LIVENESS",
"additional_data": {
"blocklisted_session_id": "882c42d5-8a4d-4d20-8080-a22f57822c86",
"blocklisted_session_number": 323442,
"api_service": null
},
"log_type": "error",
"short_description": "Face in blocklist",
"long_description": "The system identified a face in the blocklist, which means the face is not allowed to be verified."
}
]
},
"vendor_data": "user-123",
"metadata": null,
"created_at": "2026-06-12T01:04:42.763237+00:00"
}Authorizations
Body
Front-facing face image to search with. Allowed extensions: tiff, jpg, jpeg, png, webp. Maximum upload size: 5 MB (larger files are rejected with 400). Images are automatically compressed to ~0.5 MB before processing, so very high resolutions do not improve accuracy. The image must contain at least one detectable face — otherwise the endpoint returns 400. When several faces are present, the largest one is searched and a MULTIPLE_FACES_DETECTED warning is added.
Search policy. most_similar (default) ranks every enrolled face in your application by similarity. blocklisted_or_approved restricts candidates to blocklisted faces, allowlisted faces, faces from approved sessions, and imported user-profile faces — with blocklisted entries ranked first.
most_similar, blocklisted_or_approved "most_similar"
When true, the service tries 90-degree rotations of the input and uses the orientation that yields the best face detection. Useful when EXIF orientation is missing. Adds latency.
false
When true (default), persists the call as an API-type session, emits a status.updated webhook, presigns match_image_url values, and enrolls the searched face into your face search index (Face Search enrollments are excluded from future searches). When false, the search is one-shot — no session is stored and the face is not enrolled.
true
Optional opaque identifier (your user id, email, UUID…) stored on the persisted session and echoed back. It does not exclude same-user faces from the search results.
"user-123"
Optional JSON object stored with the session (when save_api_request=true) and echoed back. In multipart requests, send it as a JSON-encoded string field — it is parsed into an object.
{ "flow": "dedup_check" }
Response
Face search completed. face_search.matches holds up to 5 similar faces ordered by similarity; an empty array means no enrolled face exceeded the similarity floor. status is Declined only on blocklist hits — duplicate matches alone return Approved, so inspect matches and warnings, not just status. When save_api_request=true, request_id is the persisted session id. Each match carries source (session, imported, list_entry, or retained_template), vendor_user_id, and biometric_template_id; a retained_template match comes from an image-free biometric template kept after the source session was deleted and has null session fields, match_image_url, and user_details.
Persisted session id when save_api_request=true (usable with GET /v3/session/{sessionId}/decision/); otherwise a transient correlation UUID.
Show child attributes
Show child attributes
Echo of the vendor_data you sent, or null.
Echo of the metadata object you sent, or null.
ISO 8601 timestamp (UTC) of when the response was generated, e.g. 2026-06-12T01:04:42.763237+00:00.