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

# Face Match (Document + Selfie)

> Verify that a selfie and a document photo belong to the same person via a single API call — in the gu1 KYC API for identity verification flows.

## Overview

Face Match is a **Gu1 verification service** that compares two images: a **selfie** (current photo of the person) and a **document reference image** (e.g. the portrait from an ID). The API returns whether the faces match (same person) and a similarity score. No hosted session or real-time liveness is involved—you send the images and get the result in one call.

<Note>
  **When to use which**

  * **Session-based KYC** (`POST /api/kyc/validations`): Full flow with hosted URL, live selfie, liveness, and document capture. Best for onboarding and regulated flows.
  * **Face Match** (`POST /api/kyc/face-match`): You send two images (as files or base64); you get back match + score. Best for validating document + selfie you already have (e.g. from your own app).
</Note>

## Prerequisites

Before using Face Match:

1. **Face Match integration enabled**: Your organization must have the Face Match KYC integration activated (e.g. in Marketplace / Integrations).
2. **Credentials configured**: API key and webhook secret for the Face Match integration must be set in your organization's KYC settings (configured by the Gu1 administrator).
3. **Threshold (optional)**: The minimum score required to approve (0–100) is configured in **Organization Settings → KYC** (Face Match card). If not set, the default is 30. Only your organization's admins can change this value.

<Info>
  Face Match is a Gu1 service. All verification is performed by Gu1 infrastructure.
</Info>

## Request

### Endpoint

```
POST https://api.gu1.ai/api/kyc/face-match
```

### Content-Type

**Only `multipart/form-data`** is accepted. You can send images in either of two ways (or mix):

1. **As file parts**: attach image files to the form fields `documentImage` and `selfieImage`.
2. **As base64 strings**: send the same field names with base64-encoded image strings (with or without `data:image/...;base64,` prefix).

If a **required** image is missing (no file and no base64 for that field), the API returns **400**. For each image, if you send both a file and a base64 string, the file takes precedence.

### Headers

```
Authorization: Bearer YOUR_API_KEY
Content-Type: multipart/form-data; boundary=----...
```

Do **not** set `Content-Type` manually when using FormData; the client will set it with the correct boundary.

If your account uses organization scoping, include:

```
X-Organization-Id: YOUR_ORGANIZATION_ID
```

### Form Fields

<ParamField body="documentImage" type="file | string" required>
  Reference image (e.g. portrait from ID document). Send as a **file part** (recommended) or as a **string** (base64, with or without `data:image/...;base64,` prefix). Formats: JPEG, PNG, WebP, TIFF. Max 5MB.
</ParamField>

<ParamField body="selfieImage" type="file | string" required>
  Selfie image. Same as `documentImage`: file part or base64 string. Required.
</ParamField>

<ParamField body="entityId" type="string">
  Optional UUID of the person entity to associate with this check. Used for audit and for listing verifications by entity.
</ParamField>

<ParamField body="scoreThreshold" type="number">
  Optional. Score threshold 0–100; results below are declined. If omitted, the organization's configured threshold (or default 30) is used.
</ParamField>

<ParamField body="vendorData" type="string">
  Optional reference (e.g. your user ID) for tracking. Stored with the audit record.
</ParamField>

<ParamField query="doubleCheckRenaper" type="boolean">
  When `true`, after Gu1 face match approves, runs RENAPER **biometric** (`validate-dni` with the selfie) and **data** (`renaper/data`: DNI + trámite) checks. Requires org RENAPER credentials **and** active Marketplace integrations: `ar_renaper_data_enrichment` and `ar_renaper_validate_dni_enrichment`. Query overrides body if both are sent.
</ParamField>

<ParamField body="doubleCheckRenaper" type="boolean">
  Same as query param `doubleCheckRenaper`.
</ParamField>

<ParamField body="documentNumber" type="string">
  DNI for RENAPER when `doubleCheckRenaper=true`. Optional if `entityId` resolves `taxId` or `person.idNumber`.
</ParamField>

<ParamField body="gender" type="string">
  Gender for RENAPER (`M`, `F`, `male`, `female`). Required for double-check unless resolved from linked entity.
</ParamField>

<ParamField body="personalNumber" type="string" required>
  Número de trámite for RENAPER data check. **Required** when `doubleCheckRenaper=true`.
</ParamField>

The **minimum score (threshold)** can be sent in the form; if not sent, it is read from your organization's KYC configuration. The response includes the threshold that was used for the call.

## Response

### Success Response (200 OK)

| Field                   | Type                 | Description                                                                                                                                                                                                            |
| ----------------------- | -------------------- | ---------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- |
| `match`                 | boolean              | Whether the faces were considered a match (score ≥ threshold).                                                                                                                                                         |
| `score`                 | number               | Similarity score 0–100.                                                                                                                                                                                                |
| `status`                | string               | `"approved"` \| `"declined"` \| `"in_review"`.                                                                                                                                                                         |
| `verificationId`        | string               | ID of the audit record in `face_match_verifications` (for support and listing).                                                                                                                                        |
| `scoreDeclineThreshold` | number               | The threshold (0–100) that was used for this verification (from org settings).                                                                                                                                         |
| `requestId`             | string (optional)    | Internal request ID (for support).                                                                                                                                                                                     |
| `warnings`              | string\[] (optional) | Array of **risk codes** from the verification (e.g. `LOW_FACE_MATCH_SIMILARITY`, `NO_REFERENCE_IMAGE`). See [Warning risk codes](/en/use-cases/kyc/warning-risk-codes) for all possible values.                        |
| `doubleCheckRenaper`    | boolean (optional)   | `true` when RENAPER double-check was requested.                                                                                                                                                                        |
| `responseDoubleChecks`  | object (optional)    | When `doubleCheckRenaper=true`: `renaper` with data/trámite and nested `renaper.renaperBiometric` (ABIS). Same shape as [KYC validations RENAPER](/en/use-cases/kyc/create-validation#renaper-double-check-argentina). |

If RENAPER double-check fails after provider approval, `match` becomes `false`, `status` becomes `"declined"`, and RENAPER codes (e.g. `RENAPER_BIOMETRIC_NOT_MATCH`) are added to `warnings`. Allow **60+ seconds** HTTP timeout when using double-check (biometric polling).

**Example:**

```json theme={null}
{
  "match": true,
  "score": 85.5,
  "status": "approved",
  "verificationId": "a1b2c3d4-e5f6-7890-abcd-ef1234567890",
  "scoreDeclineThreshold": 30,
  "requestId": "req_xyz",
  "warnings": []
}
```

## Example Request

<CodeGroup>
  ```javascript Node.js (files) theme={null}
  const form = new FormData();
  form.append('documentImage', documentFile); // File object
  form.append('selfieImage', selfieFile);
  form.append('entityId', '123e4567-e89b-12d3-a456-426614174000');

  const response = await fetch('https://api.gu1.ai/api/kyc/face-match', {
    method: 'POST',
    headers: { 'Authorization': 'Bearer YOUR_API_KEY' },
    body: form,
  });
  const result = await response.json();
  console.log('Match:', result.match, 'Score:', result.score);
  ```

  ```javascript Node.js (base64 in form fields) theme={null}
  const form = new FormData();
  form.append('documentImage', 'data:image/jpeg;base64,/9j/4AAQ...');
  form.append('selfieImage', 'data:image/jpeg;base64,/9j/4AAQ...');
  form.append('entityId', '123e4567-e89b-12d3-a456-426614174000');

  const response = await fetch('https://api.gu1.ai/api/kyc/face-match', {
    method: 'POST',
    headers: { 'Authorization': 'Bearer YOUR_API_KEY' },
    body: form,
  });
  const result = await response.json();
  ```

  ```python Python (files) theme={null}
  import requests

  with open('document.jpg', 'rb') as doc, open('selfie.jpg', 'rb') as selfie:
      files = {
          'documentImage': ('document.jpg', doc, 'image/jpeg'),
          'selfieImage': ('selfie.jpg', selfie, 'image/jpeg'),
      }
      data = {'entityId': '123e4567-e89b-12d3-a456-426614174000'}
      response = requests.post(
          'https://api.gu1.ai/api/kyc/face-match',
          headers={'Authorization': 'Bearer YOUR_API_KEY'},
          files=files,
          data=data,
      )
  result = response.json()
  print('Match:', result['match'], 'Score:', result['score'])
  ```

  ```python Python (base64 in form fields) theme={null}
  import requests
  import base64

  with open('document.jpg', 'rb') as f:
      doc_b64 = 'data:image/jpeg;base64,' + base64.b64encode(f.read()).decode()
  with open('selfie.jpg', 'rb') as f:
      selfie_b64 = 'data:image/jpeg;base64,' + base64.b64encode(f.read()).decode()

  response = requests.post(
      'https://api.gu1.ai/api/kyc/face-match',
      headers={'Authorization': 'Bearer YOUR_API_KEY'},
      data={
          'documentImage': doc_b64,
          'selfieImage': selfie_b64,
          'entityId': '123e4567-e89b-12d3-a456-426614174000',
      },
  )
  result = response.json()
  ```

  ```curl cURL (files) theme={null}
  curl -X POST https://api.gu1.ai/api/kyc/face-match \
    -H "Authorization: Bearer YOUR_API_KEY" \
    -F "documentImage=@document.jpg" \
    -F "selfieImage=@selfie.jpg" \
    -F "entityId=123e4567-e89b-12d3-a456-426614174000"
  ```

  ```curl cURL (base64) theme={null}
  curl -X POST https://api.gu1.ai/api/kyc/face-match \
    -H "Authorization: Bearer YOUR_API_KEY" \
    -F "documentImage=data:image/jpeg;base64,/9j/4AAQ..." \
    -F "selfieImage=data:image/jpeg;base64,/9j/4AAQ..." \
    -F "entityId=123e4567-e89b-12d3-a456-426614174000"
  ```
</CodeGroup>

## Error Responses

Common error codes:

| Code                  | HTTP | Meaning                                                                                                                         |
| --------------------- | ---- | ------------------------------------------------------------------------------------------------------------------------------- |
| `NOT_CONFIGURED`      | 403  | Face Match credentials not set in organization KYC settings.                                                                    |
| `NOT_ENABLED`         | 403  | Face Match integration is not enabled for this organization.                                                                    |
| `INVALID_REQUEST`     | 400  | Content-Type not multipart/form-data, or missing/invalid images (e.g. no file and no base64 for a required field, format/size). |
| `UNAUTHORIZED`        | 401  | Invalid or missing API/verification credentials.                                                                                |
| `QUOTA_EXCEEDED`      | 403  | Verification could not be completed (e.g. quota/credits).                                                                       |
| `SERVICE_UNAVAILABLE` | 503  | Verification service temporarily unavailable.                                                                                   |
| `VERIFICATION_FAILED` | 500  | Verification could not be completed; retry or try other images.                                                                 |

## Audit and Listing Verifications

Every Face Match request (success or failure) is stored in **`face_match_verifications`** for audit and compliance. The response includes **`verificationId`** (the audit record ID). Each stored record also saves the **threshold** that was used at the time of the call (so you can show it in history even if the org threshold changes later).

### List Audit Records

```
GET https://api.gu1.ai/api/kyc/face-match/verifications?entityId={entityId}&limit=50&offset=0
```

**Query parameters:**

| Parameter  | Type          | Description                                                                 |
| ---------- | ------------- | --------------------------------------------------------------------------- |
| `entityId` | string (UUID) | Optional. Filter by person entity.                                          |
| `status`   | string        | Optional. Filter by status (`approved`, `declined`, `in_review`, `failed`). |
| `limit`    | number        | Max items (default 50, max 100).                                            |
| `offset`   | number        | Pagination offset.                                                          |

**Response:** `{ "verifications": [ ... ], "scoreDeclineThreshold": 30, "pagination": { "limit", "offset", "total" } }`. Each verification includes `id`, `entityId`, `status`, `match`, `score`, `threshold` (the value used for that run), `createdAt`, and optional image paths for audit.

### Get Verification by ID

Retrieve a single Face Match audit record by its `verificationId` (returned in the POST response or in the list).

```
GET https://api.gu1.ai/api/kyc/face-match/verifications/:id
```

**Path parameter:** `id` — UUID of the verification (same as `verificationId` from the POST or list).

**Response (200):** One verification object with the same fields as each item in the list: `id`, `organizationId`, `entityId`, `status`, `match`, `score`, `requestId`, `vendorData`, `errorMessage`, `triggeredByUserId`, `createdAt`, `documentStoragePath`, `selfieStoragePath`, `storageProvider`, `threshold`.

**Errors:** `404 NOT_FOUND` if the ID does not exist or belongs to another organization; `401 UNAUTHORIZED` if not authenticated.

## Coexistence with Session-Based KYC

* **Session flow** creates a `kyc_validations` record, sends the user to a hosted URL, and updates status via webhooks. The stored decision includes document, liveness, and face match from the live session.
* **Face Match** creates an audit record in `face_match_verifications` and returns the result in the same request. It does **not** create a `kyc_validations` record. Use it when you need a one-off check (e.g. "are these two images the same person?") without a hosted session.
* Both use the same organization KYC configuration. The threshold for Face Match is set in Organization Settings → KYC (Face Match card).

## Next Steps

<CardGroup cols={2}>
  <Card title="Create KYC Validation" icon="play" href="/en/use-cases/kyc/create-validation">
    Session-based verification with hosted URL
  </Card>

  <Card title="List Validations" icon="list" href="/en/use-cases/kyc/list-validations">
    List KYC validations by entity
  </Card>
</CardGroup>
