> ## Documentation Index
> Fetch the complete documentation index at: https://docs.didit.me/llms.txt
> Use this file to discover all available pages before exploring further.

# Face search report

> Read the Face Search 1:N response: matched sessions, similarity percentages, blocklist and allowlist flags, and user details for fraud investigation and duplicate detection.

## Overview

Face Search is a **1:N** biometric search: Didit takes the largest detected face in your `user_image` and searches it 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. It is distinct from Face Match, which is **1:1** — Face Match compares a selfie against the portrait on a specific document, while Face Search asks "have we seen this person anywhere before?".

Two behaviors are worth understanding before you parse the report:

* **Only blocklist matches decline.** `face_search.status` is `Declined` only when a `FACE_IN_BLOCKLIST` or `POSSIBLE_FACE_IN_BLOCKLIST` warning fires. Duplicate matches alone return `Approved` — inspect `matches` and `warnings`, not just `status`.
* **Optional persistence and enrollment.** When `save_api_request=true` (the default), the call is persisted as an API-type session 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. Pass `save_api_request=false` for a one-shot query: nothing is stored and the face is not enrolled.

## Where the results appear

Face Search is a standalone API. The search results are returned **synchronously, in the `POST /v3/face-search/` response only**, as a singular `face_search` object at the top level:

```text theme={null}
POST /v3/face-search/  ──▶  { "request_id": …, "face_search": { … }, "vendor_data": …, "metadata": …, "created_at": … }
```

There is no plural `face_searches[]` array anywhere — the plural-array convention used by `id_verifications[]`, `aml_screenings[]`, and `liveness_checks[]` on the session decision endpoint does not apply.

When `save_api_request=true` (the default), the call is also persisted as an API-type session:

* **Business Console** — the session appears in your sessions list with the search image, matches, and warnings.
* **`GET /v3/session/{sessionId}/decision/`** — retrievable using the returned `request_id` as the session id. The persisted data surfaces as a `FACE_SEARCH` entry in `features[]` and as a `liveness_checks[]` item carrying the stored `matches` (with freshly signed `match_image_url` values) and `warnings` — **not** as a `face_search` object.

When `save_api_request=false`, `request_id` is a transient correlation UUID: the session is never stored, so it cannot be fetched from the Console or the decision endpoint.

## Schema

See [Face Search in the Data Models reference](/reference/data-models#face-search) for the canonical field tables. The shape below reflects what the endpoint actually returns at runtime.

```typescript theme={null}
interface FaceSearchResponse {
  request_id: string;            // UUID — the persisted session id when save_api_request=true
  face_search: {
    status: 'Approved' | 'Declined';
    total_matches: number;       // 0-5, length of matches[]
    matches: FaceSearchMatch[];  // capped at 5, ordered by similarity
    user_image: {                // always present
      entities: {
        bbox: [number, number, number, number];
        confidence: number;      // 0-1, one entry per detected face
      }[];
      best_angle: 0 | 90 | 180 | 270;
    };
    warnings: Warning[];
  };
  vendor_data: string | null;    // echoed from the request
  metadata: object | null;       // echoed from the request
  created_at: string;            // ISO 8601, e.g. "2026-06-12T01:04:42.763237+00:00"
}

interface FaceSearchMatch {
  session_id: string | null;     // UUID of the matched session; null for non-session matches
  session_number: number | null; // Console-friendly number; null for non-session matches
  similarity_percentage: number; // 0-100
  source: 'session' | 'imported' | 'list_entry';
  vendor_data: string | null;    // the matched session's (or imported profile's) vendor_data
  verification_date: string | null;  // "YYYY-MM-DDThh:mm:ssZ"; null for list_entry matches
  user_details: {
    full_name: string | null;
    document_type: string | null;
    document_number: string | null;
  } | null;                      // null when the matched session has no document data
  match_image_url: string;       // signed URL when save_api_request=true; raw storage path otherwise
  status: 'Approved' | 'Declined' | 'In Review' | null;  // the matched session's status
  is_blocklisted: boolean;
  is_allowlisted: boolean;
  api_service: 'ID_VERIFICATION' | 'FACE_MATCH' | 'AGE_ESTIMATION' | 'POA' | 'AML'
             | 'PASSIVE_LIVENESS' | 'DATABASE_VALIDATION' | 'PHONE_VERIFICATION'
             | 'EMAIL_VERIFICATION' | null;  // null when the match comes from a workflow session
}

interface Warning {
  risk: string;
  feature: 'LIVENESS';           // always LIVENESS on this endpoint
  additional_data: object | null;
  log_type: 'error' | 'warning' | 'information';
  short_description: string;
  long_description: string;
}
```

Field notes:

* **`matches[]` is capped at 5** entries above the similarity floor, ordered by descending similarity (`search_type=blocklisted_or_approved` ranks blocklisted entries first, then allowlisted, then similarity).
* **`source`** tells you where the matched face was enrolled from: `session` — a verification session or a saved standalone API call; `imported` — a face uploaded directly to a user profile; `list_entry` — a face attached to one of your lists. `session_id`, `session_number`, `status`, and `api_service` are `null` for `imported` and `list_entry` matches.
* **`api_service`** is the matched session's standalone API type (uppercase, e.g. `PASSIVE_LIVENESS`) and `null` when the matched face came from a regular workflow verification session.
* **`user_details`** comes from the matched session's document verification. For `source: imported` matches it is populated from the user profile instead (`full_name` set, `document_type`/`document_number` null); it is `null` when no identity data exists (for example liveness-only sessions).
* **Warnings use a reduced shape** compared with workflow warnings: there is no `node_id`. The documented response contract (`FaceSearchResponseSerializer`) declares the five fields `risk`, `additional_data`, `log_type`, `short_description`, `long_description`; the runtime payload additionally carries `feature`, which is always `"LIVENESS"` here. See [Face Search warnings](/core-technology/face-search/warnings-face-search).
* **Same-user matches are not excluded** — unlike workflow duplicate detection, the standalone endpoint does not filter out prior sessions with the same `vendor_data`, so a returning legitimate user matches their own earlier sessions.

## Status values

The top-level `face_search.status` is derived from `warnings[]`. The match-level `status` reflects the **matched session's** verification outcome and is independent.

| Value      | When it appears                                                                                                                        |
| ---------- | -------------------------------------------------------------------------------------------------------------------------------------- |
| `Approved` | No blocklist warning fired. Includes the case where duplicate matches were found — duplicates are informational signals, not declines. |
| `Declined` | A `FACE_IN_BLOCKLIST` or `POSSIBLE_FACE_IN_BLOCKLIST` warning fired — the search image matched a face in your face blocklist.          |

Note: when the search image contains no detectable face the API returns HTTP `400` with `{"error": "No face detected in the image"}` **before** a `face_search` object is built — there is no `Declined` status for that case. Insufficient credits return HTTP `403` before any image processing.

## Examples

### Approved — duplicate match, no blocklist

```json theme={null}
{
  "request_id": "5e0c3a1f-7b2d-4c8e-9f10-2a3b4c5d6e7f",
  "face_search": {
    "status": "Approved",
    "total_matches": 1,
    "matches": [
      {
        "session_id": "1f2e3d4c-5b6a-7980-1122-334455667788",
        "session_number": 1024,
        "similarity_percentage": 87.42,
        "source": "session",
        "vendor_data": "user-9",
        "verification_date": "2025-11-20T09:15:00Z",
        "user_details": {
          "full_name": "Jane Marie Doe",
          "document_type": "Passport",
          "document_number": "X1234567"
        },
        "match_image_url": "https://<media-host>/face/9c8b7a6d/reference.jpg?signature=...",
        "status": "Approved",
        "is_blocklisted": false,
        "is_allowlisted": false,
        "api_service": null
      }
    ],
    "user_image": {
      "entities": [
        {
          "bbox": [40, 40, 120, 120],
          "confidence": 0.732973
        }
      ],
      "best_angle": 0
    },
    "warnings": [
      {
        "risk": "DUPLICATED_FACE",
        "feature": "LIVENESS",
        "additional_data": {
          "duplicated_session_id": "1f2e3d4c-5b6a-7980-1122-334455667788",
          "duplicated_session_number": 1024,
          "api_service": null
        },
        "log_type": "information",
        "short_description": "Duplicated face from other approved session",
        "long_description": "The system identified a duplicated face from another approved session, requiring further investigation."
      }
    ]
  },
  "vendor_data": "user-123",
  "metadata": null,
  "created_at": "2026-06-12T01:04:42.763237+00:00"
}
```

### Declined — blocklist match

```json theme={null}
{
  "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?signature=...",
        "status": "Approved",
        "is_blocklisted": true,
        "is_allowlisted": false,
        "api_service": "PASSIVE_LIVENESS"
      }
    ],
    "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": "PASSIVE_LIVENESS"
        },
        "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"
}
```

## Related

* [Face Search warnings](/core-technology/face-search/warnings-face-search) — every warning code the search can emit
* [Face Search API reference](/standalone-apis/face-search) — request parameters and `search_type` modes
* [Data models — Face Search](/reference/data-models#face-search) — canonical schema
* [Webhooks](/integration/webhooks) — `status.updated` payloads for persisted sessions

<Note>
  When `save_api_request=true`, `match_image_url` values are signed, short-lived URLs — re-fetch the session decision endpoint with the returned `request_id` to get fresh ones. When `save_api_request=false`, they are internal storage paths and are not directly downloadable. Store only the matched session id, status, and `similarity_percentage` on your side — do not retain the underlying biometric image.
</Note>
