> ## 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.

# Faces and Biometrics

> How Didit attaches face biometrics to vendor User entities — 1:N search, duplicate detection, face blocklist enforcement, and manual upload.

export const AgentPromptAccordion = ({prompt, title = "AI Agent Integration Prompt"}) => {
  const [copied, setCopied] = React.useState(false);
  const handleCopy = e => {
    e.stopPropagation();
    if (!prompt) return;
    navigator.clipboard.writeText(prompt.trim()).then(() => {
      setCopied(true);
      setTimeout(() => setCopied(false), 2000);
    });
  };
  const agents = ["Claude Code", "Codex", "Cursor", "Devin", "Windsurf", "GitHub Copilot"];
  return <div className="didit-agent-card">
      {}
      <div className="didit-agent-titlebar">
        <div className="didit-agent-dots" aria-hidden="true">
          <span className="didit-agent-dot didit-agent-dot-red"></span>
          <span className="didit-agent-dot didit-agent-dot-yellow"></span>
          <span className="didit-agent-dot didit-agent-dot-green"></span>
        </div>
        <span className="didit-agent-filename">{title}</span>
        <button type="button" className={`didit-agent-copy ${copied ? "didit-agent-copy-copied" : ""}`} onClick={handleCopy} title="Copy prompt to clipboard" aria-label={copied ? "Copied!" : "Copy prompt to clipboard"}>
          {copied ? <>
              <svg width="13" height="13" viewBox="0 0 16 16" fill="none">
                <path d="M3 8.5l3.5 3.5L13 4" stroke="currentColor" strokeWidth="2" strokeLinecap="round" strokeLinejoin="round" />
              </svg>
              <span>Copied</span>
            </> : <>
              <svg width="13" height="13" viewBox="0 0 16 16" fill="none">
                <rect x="5" y="5" width="9" height="9" rx="1.5" stroke="currentColor" strokeWidth="1.5" />
                <path d="M11 5V3.5A1.5 1.5 0 0 0 9.5 2h-6A1.5 1.5 0 0 0 2 3.5v6A1.5 1.5 0 0 0 3.5 11H5" stroke="currentColor" strokeWidth="1.5" />
              </svg>
              <span>Copy</span>
            </>}
        </button>
      </div>

      {}
      <pre className="didit-agent-body"><code>{prompt.trim()}</code></pre>

      {}
      <div className="didit-agent-footer">
        <span className="didit-agent-footer-label">Paste into</span>
        <div className="didit-agent-chips">
          {agents.map(name => <span key={name} className="didit-agent-chip">{name}</span>)}
        </div>
      </div>
    </div>;
};

<AgentPromptAccordion
  title="Face Biometrics Prompt"
  prompt={`# Goal — search, blocklist, and act on User face biometrics

Faces are captured automatically during liveness inside a verification session, and can also be uploaded to an existing User profile when you are migrating users from another provider. You interact with biometrics through three action surfaces: **profile face upload** (attach an imported face to a User), **face search** (find a user by face), and the **Lists API** (blocklist a face so future sessions are auto-declined).

## 1) Upload a face to an imported User profile

Use this when you already created or imported a User with \`vendor_data\` and have a trusted face image from your previous provider.

\`\`\`bash
# 1. Get the Didit internal User id from vendor_data
curl https://verification.didit.me/v3/users/user-42/ \\
-H "x-api-key: $DIDIT_API_KEY"

# 2. Upload a base64-encoded face to that profile
curl -X POST https://verification.didit.me/v3/organization/$ORGANIZATION_ID/application/$APPLICATION_ID/vendor-users/by-id/$DIDIT_INTERNAL_ID/faces/upload/ \\
-H "x-api-key: $DIDIT_API_KEY" \\
-H "Content-Type: application/json" \\
-d '{"image": "<base64-encoded-jpg-or-png>", "comment": "Imported from previous KYC provider"}'
\`\`\`

Limits: max 5 faces per User, max 2 MB per image, and the image must contain a detectable face. This enrolls the face for profile evidence and future duplicate/face-search matching. It does not blocklist the face.

## 2) Face search — 1:N

Use to detect duplicate accounts, identify a face from a CCTV frame, or recognise a returning customer before running new KYC.

\`\`\`bash
curl -X POST https://verification.didit.me/v3/face-search/ \\
-H "x-api-key: $DIDIT_API_KEY" \\
-F "user_image=@./selfie.jpg"
\`\`\`

Response includes match candidates with similarity scores. Wire your workflow (in the console) to auto-decline / flag / warn on duplicate hits, or call this endpoint directly from your backend before creating a session.

## 3) Face blocklist — block a user by face

To block a known bad actor going forward, upload their face to a **face-type list** in your application. Any future session whose liveness selfie matches the embedding is auto-declined.

\`\`\`bash
# Upload a face directly to a face-type list (no session needed)
curl -X POST https://verification.didit.me/v3/lists/$LIST_UUID/entries/face-upload/ \\
-H "x-api-key: $DIDIT_API_KEY" \\
-H "Content-Type: application/json" \\
-d '{"image": "<base64-encoded-jpg-or-png>", "reason": "confirmed_fraud"}'
\`\`\`

If the face you want to block came from an existing session, use \`POST /v3/lists/{list_uuid}/entries/\` with \`reference_session_id\` instead — the backend extracts the biometric and links the entry back to the session.

Remove a face from the list with the Delete entry endpoint:

\`\`\`bash
curl -X DELETE https://verification.didit.me/v3/lists/$LIST_UUID/entries/$ENTRY_UUID/ \\
-H "x-api-key: $DIDIT_API_KEY"
\`\`\`

The Lists API supports face, document, email, phone, and IBAN list types. See /management-api/lists/overview for the full catalog.

## 4) Auto-blocklist from declines

When a session is \`DECLINED\` and your workflow has auto-blocklist enabled, the associated face is added to the system face list automatically and the User entity transitions to \`BLOCKED\` per policy. No manual call needed.

## 5) Where the face lives on the User

- \`portrait_image\` on the User entity is a signed URL to the latest approved selfie.
- Manually uploaded profile faces are listed under the User profile face collection and returned by the profile face list endpoint.
- \`approved_duplicate_status\` on each face is one of \`NOT_DUPLICATED\` / \`DUPLICATED\`.
- Embeddings (numeric vectors) are stored separately from the image; both are deleted together on GDPR / CCPA right-to-be-forgotten.

## Failure modes

- \`400\` from face-search / upload-face — image has no detectable face, multiple faces, or is too low-quality. Re-capture and retry.
- \`404\` on Lists API — wrong \`list_id\` or list deleted.
- \`403\` — your role lacks permission on the \`lists\` resource.
- \`429\` — rate-limited.

## Response shape

See /standalone-apis/face-search and /management-api/lists/upload-face for full schemas. Match objects use \`similarity\` (0–1) and reference the linked User \`vendor_data\` when known.

## Sources of truth

- /management-api/users/upload-face — attach an imported face to a User profile
- /standalone-apis/face-search — face search request / response
- /management-api/lists/upload-face — face blocklist upload
- /management-api/lists/delete-entry — remove from list
- /core-technology/face-search/overview — search behaviour
- /core-technology/liveness/overview — face capture during sessions
- /integration/integration-prompt — canonical end-to-end prompt
`}
/>

Face biometrics are central to every User entity. Didit captures a face during [liveness detection](/core-technology/liveness/overview), generates a vector embedding, and stores it for four downstream features: **1:N face search**, **duplicate detection**, **blocklist enforcement**, and **biometric re-authentication**.

<Note>
  Stored faces double as the reference for [Biometric Authentication](/core-technology/biometric-auth/overview): create a biometric-auth session with the user's `vendor_data` and omit `portrait_image`, and Didit reuses the stored face automatically (approved liveness face first, then the ePassport chip photo, then the ID document portrait, then a manually enrolled profile face).
</Note>

## How faces attach to a user

<Steps>
  <Step title="Captured during liveness">
    When a User goes through a session with liveness enabled, Didit captures a selfie, runs passive liveness detection, extracts the largest face, and generates a vector embedding for search.
  </Step>

  <Step title="Stored on the user profile">
    The face is linked to the session and the parent User entity. The portrait URL surfaces on the user profile as `portrait_image`.
  </Step>

  <Step title="Indexed for search">
    The embedding is added to the search index. Subsequent sessions can run 1:N searches against this index.
  </Step>
</Steps>

## Face search (1:N)

Face search lets you find a user by uploading an image and matching it against every enrolled face in your application.

Use-cases:

* **Duplicate detection** at onboarding — prevent the same person from creating two accounts.
* **Reverse lookup** — identify the user behind a CCTV frame or an image in a fraud investigation.
* **Repeat customer recognition** — skip KYC for a returning customer.

Face search runs automatically during new sessions when your workflow has duplicate detection enabled. It is also exposed as a standalone API:

* `POST /v3/face-search/` — [standalone face search](/standalone-apis/face-search)

## Duplicate detection

Every new face can be compared against existing enrolled faces to surface duplicates. Didit assigns each face an `approved_duplicate_status`:

| Status           | Meaning                                                       |
| ---------------- | ------------------------------------------------------------- |
| `NOT_DUPLICATED` | No matches found above the similarity threshold               |
| `DUPLICATED`     | Face matches one or more faces on an already-approved session |

Workflows can be configured to:

* **Decline** duplicate-flagged sessions automatically.
* **Flag for review** — route to a human analyst.
* **Warn only** — allow but add a risk tag.

Configure this in the console at *Workflows → \[your workflow] → Face Match / Liveness → Duplicate detection*.

## Face blocklist

When a session is declined (by rule, analyst, or workflow configuration), the associated face is automatically added to the **Face Blocklist** for your application. Future sessions with a matching face are rejected.

## Upload an imported face to a User profile

If you are migrating from another KYC provider, you can create the User first and then attach a trusted face image to that profile. The User is still keyed by your `vendor_data`, but the face upload endpoint uses Didit's internal User id (`didit_internal_id`) so the target is unambiguous.

<Steps>
  <Step title="Create or retrieve the User">
    Call [`POST /v3/users/create/`](/management-api/users/create) with your stable `vendor_data`, or call `GET /v3/users/{vendor_data}/` if the User already exists.
  </Step>

  <Step title="Read didit_internal_id">
    The User response includes `didit_internal_id`. Keep using `vendor_data` in your own system; use `didit_internal_id` only for profile attachment endpoints.
  </Step>

  <Step title="Upload the face">
    Call [`POST /v3/organization/{organization_id}/application/{application_id}/vendor-users/by-id/{didit_internal_id}/faces/upload/`](/management-api/users/upload-face) with a base64-encoded face image.
  </Step>
</Steps>

```bash theme={null}
# 1. Create or retrieve the User keyed by your vendor_data
curl -X POST https://verification.didit.me/v3/users/create/ \
  -H "x-api-key: YOUR_API_KEY" \
  -H "Content-Type: application/json" \
  -d '{
    "vendor_data": "user-42",
    "display_name": "Jane Doe",
    "metadata": { "previous_provider": "legacy-kyc-vendor" }
  }'

# Response includes:
# { "didit_internal_id": "11111111-2222-3333-4444-555555555555", ... }

# 2. Upload the imported face to the User profile
curl -X POST https://verification.didit.me/v3/organization/$ORGANIZATION_ID/application/$APPLICATION_ID/vendor-users/by-id/11111111-2222-3333-4444-555555555555/faces/upload/ \
  -H "x-api-key: YOUR_API_KEY" \
  -H "Content-Type: application/json" \
  -d '{
    "image": "<base64-encoded-jpg-or-png>",
    "comment": "Imported from previous KYC provider"
  }'
```

Imported profile faces are enrolled into the same face index used for duplicate detection and face search. They are not automatically blocklisted. To block a known bad actor, upload the face to a face-type list instead.

<Warning>
  Only upload faces you are allowed to process under your own privacy notice and migration agreement. Keep source-provider identifiers in `metadata` or `comment` only when they are safe to expose to your compliance operators.
</Warning>

### How blocklisting happens

| Trigger                                             | Action                                                      |
| --------------------------------------------------- | ----------------------------------------------------------- |
| Session `DECLINED` and auto-blocklist enabled       | Face added to the system face list, user status → `BLOCKED` |
| Analyst manually blocklists a face from the console | Face added, user status updated per policy                  |
| Programmatic add via the Lists API                  | Upload the face image to a face-type blocklist              |

### Uploading a face to the blocklist

To block a user by face programmatically, upload the face image to a face-type blocklist via the dedicated [Upload face to list](/management-api/lists/upload-face) endpoint. Didit extracts the biometric embedding and inserts it into the matching index — any future session whose liveness selfie matches is auto-declined.

<Note>
  Profile face upload and face blocklist upload are different operations. Use profile upload to attach a trusted imported face to a User for evidence, duplicate detection, and face search. Use the [Lists API](/management-api/lists/overview) to block a face so future matching sessions are rejected.
</Note>

### Removing a face from the blocklist

Remove the entry from the console at *Blocklist → Faces → row actions → Remove*, or use the [Delete entry](/management-api/lists/delete-entry) endpoint.

## Face lifecycle summary

```mermaid theme={null}
stateDiagram-v2
    [*] --> Enrolled: Face captured during liveness
    Enrolled --> Approved: Session approved
    Enrolled --> Declined: Session declined
    Declined --> Blocklisted: Auto-blocklist enabled
    Enrolled --> Duplicated: 1:N search finds match
    Duplicated --> Approved: Allowed by workflow
    Duplicated --> Declined: Rejected by workflow
    Blocklisted --> [*]: Manually removed
```

## Upload a face from the console

Compliance operators can upload faces manually:

1. Navigate to *Users → \[user] → Actions → Upload face*.
2. Select an image; Didit extracts the largest face and stores a new Face record linked to the user.
3. The face is enrolled for profile evidence, duplicate detection, and face search. Use the Blocklist area when the goal is to reject future matching sessions.

This is useful when:

* You have a known portrait of a customer verified through an offline channel.
* You want to pre-seed a blocklist with known bad actors from your fraud database.

## Privacy and retention

* Face embeddings are numerical vectors, not images. Images themselves are stored in encrypted object storage with signed-URL access.
* Retention follows your application's [data retention](/console/data-retention) policy.
* GDPR / CCPA "right to be forgotten" deletion removes both the image and the embedding from the search index.

## Next steps

<CardGroup cols={3}>
  <Card title="Face Search" icon="magnifying-glass" href="/core-technology/face-search/overview">
    How 1:N face search works.
  </Card>

  <Card title="Liveness" icon="face-viewfinder" href="/core-technology/liveness/overview">
    Liveness detection that captures the face.
  </Card>

  <Card title="Blocklist API" icon="ban" href="/management-api/lists/upload-face">
    Upload a face to a blocklist.
  </Card>

  <Card title="User blocklist" icon="user-slash" href="/entities/users/blocklist">
    How blocklisting a user works.
  </Card>
</CardGroup>
