---
icon: verified
label: VerifEye Face API
order: 800
---

# VerifEye Face API

## Overview

The VerifEye Face API is the single entry point for face onboarding, identification and analysis. One call takes an image, works out who is on it against your own [face collection](/redirect/concepts/#face-collection), and — when you ask for it — returns the embedding, age, gender, emotions and attention of every face it answers about.

The VerifEye Face API enables you to:
- **Onboard** faces into a collection — every face is looked up and added when it is not already there
- **Identify** faces against a collection — every face is looked up and nothing is ever added
- Enrich each face with optional attributes: embedding, age, gender, emotions and attention
- Answer about several faces on one image in a single call
- Compare two embeddings directly, without a collection
- Manage collections (create, retrieve, update, reset, delete)
- Associate your own external keys (for example a customer or user identifier) with faces and look faces up by them

## Base URLs

| Region | Base URL |
|--------|----------|
| **EU** | `https://verifeye-face-api-eu.realeyes.ai/v1/` |
| **US** | `https://verifeye-face-api-us.realeyes.ai/v1/` |

---

## Onboard or Identify?

The two face operations take the same request and return the same response. They differ in one thing only: whether a face the collection does not hold yet is **added** to it.

| | [Onboard](#onboard) | [Identify](#identify) |
|---|---|---|
| Face already in the collection | returns its `faceId` (`faceStatus: Existing`) | returns its `faceId` (`faceStatus: Existing`) |
| Face **not** in the collection | **adds it** and returns the new `faceId` (`faceStatus: New`) | returns no `faceId` (`faceStatus: NotExisting`) — nothing is stored |
| Use it when | you want to keep the face: sign-up, enrollment, duplicate detection, building up the population you will recognise later | you only want to know who this is: authentication, re-verification, age or emotion analysis of an anonymous visitor |

**Rule of thumb:** if you want to keep the faces, call **Onboard**. For everything else, call **Identify**.

---

## Requests and responses

### Images

Supply the image either as a base64-encoded JPEG/PNG (`image.bytes`) or as a URL (`image.url`). When both are set, `bytes` wins. Only `http` and `https` URLs are accepted, and the host must resolve to a public address — private, loopback and link-local addresses are rejected with `400`.

See [Image Requirements](/cloud-apis-web-sdks/image-requirements/) for resolution, file size and face-position guidance.

### Optional attributes

Every attribute is off by default. Enable only what you need through the `include` object in the request body. The API returns the requested attributes for every face it answers about.

| Option | Adds to each face |
|--------|-------------------|
| `include.embedding` | `embedding` — a 512-value face embedding |
| `include.age` | `age` — the estimated age and its uncertainty |
| `include.gender` | `gender` — `Male` or `Female` |
| `include.emotions` | `emotions` — confusion, contempt, disgust, happiness, empathy, surprise |
| `include.attention` | `attention` — presence, eyes on screen, attention |

!!!info Only turn on what you actually use
Every option you enable is additional work per face, so each one adds latency to the request. Asking for everything on several faces is measurably slower than asking for what you need.

Ask for the attributes your flow reads and nothing more. `include` is per request, not per collection, so a flow that needs age only at sign-up can leave it off everywhere else.
!!!

### Face detection restrictions

Faces that the API cannot process with sufficient confidence are **omitted from the response** — they do not appear in `faces`. This is more common when the image contains multiple faces.

So `faces` can hold fewer entries than the image holds faces. Those omitted faces are **not** counted in `unprocessedFaceCount`; that field counts the faces the call never looked at — the ones `maxFaces` left out, plus any beyond the detector’s own limit. Do not rely on the position of a face in `faces` to match the position of a face on the image; match on `faces[].face.boundingBox` instead.

### Multiple faces

`maxFaces` (default `1`, maximum `4`) decides how many faces one call answers about. Faces are processed in order of dominance, and every face the limit left out is counted in `unprocessedFaceCount`.

---

## API Endpoints

### Onboard

Onboards the faces on the image into the collection: every face is looked up and, when it is not already there, added.

**Endpoint:** `POST /v1/face/onboard`

**Authentication:** API Key or Bearer Token

**Request Body:**

```json
{
  "image": {
    "bytes": "base64-encoded-image-string",
    "url": null
  },
  "collectionId": "my-collection",
  "faceMatchThreshold": 80,
  "maxFaces": 1,
  "include": {
    "embedding": false,
    "age": true,
    "gender": true,
    "emotions": false,
    "attention": false
  }
}
```

**Request Parameters:**

| Parameter | Type | Required | Description |
|-----------|------|----------|-------------|
| `image` | object | Yes | The image to onboard |
| `image.url` | string (nullable) | No | URL of a jpeg or png image. `http`/`https` only, and the host must resolve to a public address |
| `image.bytes` | string (nullable) | No | Base 64 string encoded binary jpeg or png image. Takes precedence over `image.url` |
| `collectionId` | string | Yes | The id of the collection to onboard into. Max length 512. May only contain letters, numbers, dashes (`-`), underscores (`_`) and dots (`.`) |
| `faceMatchThreshold` | integer | No | Minimum confidence in the face match to accept. Default value: **80**. Valid range: 0-100.<br><br>**Threshold reference** (computed using extensive in-the-wild datasets):<br>• **95** corresponds to FPR 1e-06<br>• **90** corresponds to FPR 1e-05<br>• **80** corresponds to FPR 1e-4<br>• **70** corresponds to FPR 1e-3 |
| `maxFaces` | integer | No | The maximum number of faces to answer about. Default value: **1**. Valid range: 1-4. Faces beyond this limit are counted in `unprocessedFaceCount` |
| `include` | object | No | Which optional attributes are returned for every face. All options default to `false`. Enable only the ones you read — see [Optional attributes](#optional-attributes) for the cost |
| `include.embedding` | boolean | No | Return the face embedding |
| `include.age` | boolean | No | Return the estimated age |
| `include.gender` | boolean | No | Return the detected gender |
| `include.emotions` | boolean | No | Return the detected emotions |
| `include.attention` | boolean | No | Return the detected presence and attention |

**Response Example:**

```json
{
  "faces": [
    {
      "face": {
        "confidence": 0.9987,
        "boundingBox": {
          "x": 120,
          "y": 80,
          "width": 200,
          "height": 250
        }
      },
      "faceId": "face_abc123xyz789",
      "externalKey": "user-123",
      "embedding": null,
      "age": {
        "prediction": 31.4,
        "uncertainty": 0.42
      },
      "gender": "Female",
      "emotions": null,
      "attention": null,
      "faceStatus": "Existing"
    }
  ],
  "unprocessedFaceCount": 0
}
```

**Response Fields:**

| Field Path | Type | Description |
|------------|------|-------------|
| `faces` | array | One entry per face this call answered about. Empty when no face was detected on the image, or when none of the detected faces could be answered for. A face the API cannot answer for with confidence has no entry — see [Face detection restrictions](#face-detection-restrictions) |
| `faces[].face` | object | Detected face information, always in the coordinates of the image you supplied |
| `faces[].face.confidence` | number | Face detection score with value range [0.0, 1.0] (higher is better) |
| `faces[].face.boundingBox` | object | Model for the bounding box of a detected face |
| `faces[].face.boundingBox.x` | integer | Horizontal position of the detected face bounding box |
| `faces[].face.boundingBox.y` | integer | Vertical position of the detected face bounding box |
| `faces[].face.boundingBox.width` | integer | Width of the detected face bounding box |
| `faces[].face.boundingBox.height` | integer | Height of the detected face bounding box |
| `faces[].faceId` | string (nullable) | The id of the face. Always present on onboard — a face that was not found is added, so it always has one |
| `faces[].externalKey` | string (nullable) | The external key associated with the face, when one has been set via the [External Key](#external-key-management) endpoints. `null` otherwise. A newly added face never has one |
| `faces[].embedding` | array (nullable) | 512 values. Returned only when `include.embedding` is `true` |
| `faces[].age` | object (nullable) | Returned only when `include.age` is `true` |
| `faces[].age.prediction` | number (nullable) | Estimated age |
| `faces[].age.uncertainty` | number (nullable) | Uncertainty score with value range [0.0, infinity]. We recommend rejecting everything higher than 1.0 — values above it can indicate a problem with the image quality |
| `faces[].gender` | string (nullable) | `Male` or `Female`. Returned only when `include.gender` is `true` |
| `faces[].emotions` | object (nullable) | Returned only when `include.emotions` is `true`. A `null` property means that state could not be determined reliably |
| `faces[].emotions.confusion` | boolean (nullable) | Whether the person shows confusion |
| `faces[].emotions.contempt` | boolean (nullable) | Whether the person shows contempt |
| `faces[].emotions.disgust` | boolean (nullable) | Whether the person shows disgust |
| `faces[].emotions.happy` | boolean (nullable) | Whether the person shows happiness |
| `faces[].emotions.empathy` | boolean (nullable) | Whether the person shows empathy |
| `faces[].emotions.surprise` | boolean (nullable) | Whether the person shows surprise |
| `faces[].attention` | object (nullable) | Returned only when `include.attention` is `true`. A `null` property means that state could not be determined reliably |
| `faces[].attention.presence` | boolean (nullable) | Whether a person is present in the image |
| `faces[].attention.eyesOnScreen` | boolean (nullable) | Whether the person's eyes are on the screen |
| `faces[].attention.attention` | boolean (nullable) | Whether the person is attentive |
| `faces[].faceStatus` | string | `Existing` (matched a face already in the collection) or `New` (added by this call). Onboard never returns `NotExisting` — see [Face status](#face-status) |
| `unprocessedFaceCount` | integer | Faces detected on the image but not answered about, because `maxFaces` limited how many are processed |

**Example Request:**

```bash
curl -X POST "https://verifeye-face-api-eu.realeyes.ai/v1/face/onboard" \
  -H "Authorization: ApiKey API-KEY-FROM-DEV-CONSOLE" \
  -H "Content-Type: application/json" \
  -d '{
    "image": {
      "bytes": "/9j/4AAQSkZJRgABAQEAYABgAAD..."
    },
    "collectionId": "my-collection",
    "include": {
      "age": true,
      "gender": true
    }
  }'
```

**Response Codes:**
- `200` - The faces were onboarded
- `400` - Validation failure
- `401` - Authentication failure
- `404` - Collection not found
- `504` - The request did not complete within the time budget

---

### Identify

Identifies the faces on the image against the collection: every face is looked up and **never** added.

**Endpoint:** `POST /v1/face/identify`

**Authentication:** API Key or Bearer Token

The request and the response are identical to [Onboard](#onboard), with one difference: a face the collection does not hold comes back with `faceStatus: NotExisting` and no `faceId`, and nothing is stored. Every attribute that was asked for is still returned for it — only the identity is missing.

**Request Body:**

```json
{
  "image": {
    "bytes": "base64-encoded-image-string",
    "url": null
  },
  "collectionId": "my-collection",
  "faceMatchThreshold": 80,
  "maxFaces": 2,
  "include": {
    "embedding": false,
    "age": false,
    "gender": false,
    "emotions": true,
    "attention": true
  }
}
```

**Response Example:**

```json
{
  "faces": [
    {
      "face": {
        "confidence": 0.9987,
        "boundingBox": {
          "x": 120,
          "y": 80,
          "width": 200,
          "height": 250
        }
      },
      "faceId": "face_abc123xyz789",
      "externalKey": "user-123",
      "embedding": null,
      "age": null,
      "gender": null,
      "emotions": {
        "confusion": false,
        "contempt": null,
        "disgust": false,
        "happy": true,
        "empathy": null,
        "surprise": false
      },
      "attention": {
        "presence": true,
        "eyesOnScreen": true,
        "attention": true
      },
      "faceStatus": "Existing"
    },
    {
      "face": {
        "confidence": 0.9721,
        "boundingBox": {
          "x": 430,
          "y": 96,
          "width": 180,
          "height": 224
        }
      },
      "faceId": null,
      "externalKey": null,
      "embedding": null,
      "age": null,
      "gender": null,
      "emotions": {
        "confusion": null,
        "contempt": null,
        "disgust": null,
        "happy": false,
        "empathy": null,
        "surprise": false
      },
      "attention": {
        "presence": true,
        "eyesOnScreen": false,
        "attention": false
      },
      "faceStatus": "NotExisting"
    }
  ],
  "unprocessedFaceCount": 1
}
```

**Example Request:**

```bash
curl -X POST "https://verifeye-face-api-eu.realeyes.ai/v1/face/identify" \
  -H "Authorization: ApiKey API-KEY-FROM-DEV-CONSOLE" \
  -H "Content-Type: application/json" \
  -d '{
    "image": {
      "url": "https://example.com/photo.jpg"
    },
    "collectionId": "my-collection",
    "maxFaces": 2,
    "include": {
      "emotions": true,
      "attention": true
    }
  }'
```

**Response Codes:**
- `200` - The faces were searched for; each one either matched or did not
- `400` - Validation failure
- `401` - Authentication failure
- `404` - Collection not found
- `504` - The request did not complete within the time budget

---

### Face status

Every face carries a `faceStatus` saying what the collection had to say about it.

| Value | Meaning | Returned by |
|-------|---------|-------------|
| `Existing` | The face matched one already in the collection, and that `faceId` was returned | Onboard, Identify |
| `New` | The face was not in the collection and has now been added to it | Onboard |
| `NotExisting` | The collection held no face matching this one | Identify |

Onboard only ever returns `Existing` or `New`: a face it does not find is added, so it always comes back with a `faceId`. `NotExisting` is Identify's alone.

`NotExisting` means one thing: **no match.** The collection held nobody who looks like this person. Every requested attribute is still returned — only the identity is missing. This is the ordinary end of an identify.


---

### Compare Embeddings

Compares two face embeddings and returns how similar they are. Both must be embeddings returned by [Onboard](#onboard) or [Identify](#identify) via `include.embedding`. No collection is involved and nothing is stored.

**Endpoint:** `POST /v1/face/compare-embeddings`

**Authentication:** API Key or Bearer Token

**Request Body:**

```json
{
  "embedding1": [0.0123, -0.0456, 0.0789],
  "embedding2": [0.0118, -0.0461, 0.0802]
}
```

**Request Parameters:**

| Parameter | Type | Required | Description |
|-----------|------|----------|-------------|
| `embedding1` | array | Yes | The embedding to compare. Must hold exactly **512** values |
| `embedding2` | array | Yes | The embedding to compare it with. Must hold exactly **512** values |

**Response Example:**

```json
{
  "similarity": 87
}
```

**Response Fields:**

| Name | Type | Description |
|------|------|-------------|
| `similarity` | integer | Similarity of the two embeddings with value range [1, 100] (higher is better). Reject anything below 70; **80** is the recommended operating point.<br><br>**Threshold reference:**<br>• **95** corresponds to FPR 1e-06<br>• **90** corresponds to FPR 1e-05<br>• **80** corresponds to FPR 1e-4<br>• **70** corresponds to FPR 1e-3 |

**Example Request:**

```bash
curl -X POST "https://verifeye-face-api-eu.realeyes.ai/v1/face/compare-embeddings" \
  -H "Authorization: ApiKey API-KEY-FROM-DEV-CONSOLE" \
  -H "Content-Type: application/json" \
  -d '{
    "embedding1": [0.0123, -0.0456, 0.0789],
    "embedding2": [0.0118, -0.0461, 0.0802]
  }'
```

**Response Codes:**
- `200` - The embeddings were compared
- `400` - Validation failure (a missing embedding, or one that does not hold exactly 512 values)
- `401` - Authentication failure

---

### Delete Face

Deletes a single face from the collection. Deleting a face also removes any external key association it had.

**Endpoint:** `DELETE /v1/face/delete`

**Authentication:** API Key or Bearer Token

**Query Parameters:**

| Parameter | Type | Required | Description |
|-----------|------|----------|-------------|
| `collectionId` | string | Yes | The id of the collection the face belongs to |
| `faceId` | string | Yes | The id of the face to be deleted. Max 64 characters |

**Response Example:**

```json
{}
```

**Example Request:**

```bash
curl -X DELETE "https://verifeye-face-api-eu.realeyes.ai/v1/face/delete?collectionId=my-collection&faceId=face_abc123xyz789" \
  -H "Authorization: ApiKey API-KEY-FROM-DEV-CONSOLE"
```

**Response Codes:**
- `200` - The face was deleted (idempotent)
- `400` - Validation failure
- `401` - Authentication failure
- `404` - Collection not found

---

## Collection Management

A collection is the store of faces that onboard and identify work against. For a conceptual overview of what a face collection is and how it relates to a Verification, see [Face collection](/redirect/concepts/#face-collection).

### Get Collection

Retrieves a single collection by its identifier.

**Endpoint:** `GET /v1/collection/get`

**Authentication:** API Key or Bearer Token

**Query Parameters:**

| Parameter | Type | Required | Description |
|-----------|------|----------|-------------|
| `collectionId` | string | Yes | The collection identifier |

**Response Example:**

```json
{
  "collection": {
    "collectionId": "my-collection",
    "description": "Collection for employee verification",
    "createdAt": "2026-01-15T10:30:00Z",
    "updatedAt": "2026-02-10T14:20:00Z"
  }
}
```

**Response Fields:**

| Field Path | Type | Description |
|------------|------|-------------|
| `collection` | object | The collection details |
| `collection.collectionId` | string | Identifier of the collection |
| `collection.description` | string | Optional textual description |
| `collection.createdAt` | string | UTC timestamp when the collection was created |
| `collection.updatedAt` | string | UTC timestamp when the collection was last updated |

**Example Request:**

```bash
curl -X GET "https://verifeye-face-api-eu.realeyes.ai/v1/collection/get?collectionId=my-collection" \
  -H "Authorization: ApiKey API-KEY-FROM-DEV-CONSOLE"
```

**Response Codes:**
- `200` - Collection found
- `400` - Validation failure
- `401` - Authentication failure
- `404` - Collection not found

---

### Get All Collections

Lists all collections belonging to the authenticated account.

**Endpoint:** `GET /v1/collection/get-all`

**Authentication:** API Key or Bearer Token

**Response Example:**

```json
{
  "collections": [
    {
      "collectionId": "my-collection",
      "description": "Collection for employee verification",
      "createdAt": "2026-01-15T10:30:00Z",
      "updatedAt": "2026-02-10T14:20:00Z"
    },
    {
      "collectionId": "employee-faces",
      "description": "",
      "createdAt": "2026-01-20T08:15:00Z",
      "updatedAt": "2026-01-20T08:15:00Z"
    }
  ]
}
```

**Response Fields:**

| Field Path | Type | Description |
|------------|------|-------------|
| `collections` | array | Collections returned for the account (may be empty) |
| `collections[].collectionId` | string | Identifier of the collection |
| `collections[].description` | string | Optional textual description of the collection |
| `collections[].createdAt` | string | UTC timestamp when the collection was created |
| `collections[].updatedAt` | string | UTC timestamp when the collection was last updated |

**Example Request:**

```bash
curl -X GET "https://verifeye-face-api-eu.realeyes.ai/v1/collection/get-all" \
  -H "Authorization: ApiKey API-KEY-FROM-DEV-CONSOLE"
```

**Response Codes:**
- `200` - Collections returned
- `400` - Validation failure
- `401` - Authentication failure

---

### Create Collection

Creates a new collection.

**Endpoint:** `POST /v1/collection/create`

**Authentication:** API Key or Bearer Token

**Request Body:**

```json
{
  "collectionId": "new-collection",
  "description": "Collection for customer verification"
}
```

**Request Parameters:**

| Parameter | Type | Required | Description |
|-----------|------|----------|-------------|
| `collectionId` | string | Yes | Unique identifier for the collection (max length 512). May only contain letters, numbers, dashes (`-`), underscores (`_`) and dots (`.`). Other characters are rejected with `400` |
| `description` | string | No | Optional description (max length 1024) |

**Response Example:**

```json
{
  "collectionId": "new-collection"
}
```

**Response Fields:**

| Name | Type | Description |
|------|------|-------------|
| `collectionId` | string | Identifier of the created collection |

**Example Request:**

```bash
curl -X POST "https://verifeye-face-api-eu.realeyes.ai/v1/collection/create" \
  -H "Authorization: ApiKey API-KEY-FROM-DEV-CONSOLE" \
  -H "Content-Type: application/json" \
  -d '{
    "collectionId": "new-collection",
    "description": "Collection for customer verification"
  }'
```

**Response Codes:**
- `200` - Collection created successfully
- `400` - Validation failure
- `401` - Authentication failure
- `409` - Collection already exists

---

### Update Collection

Updates an existing collection (only description is mutable).

**Endpoint:** `PUT /v1/collection/update`

**Authentication:** API Key or Bearer Token

**Request Body:**

```json
{
  "collectionId": "my-collection",
  "description": "Updated description for my collection"
}
```

**Request Parameters:**

| Parameter | Type | Required | Description |
|-----------|------|----------|-------------|
| `collectionId` | string | Yes | Identifier of the collection to update |
| `description` | string | No | New description (max length 1024) |

**Response Example:**

```json
{
  "collectionId": "my-collection"
}
```

**Response Fields:**

| Name | Type | Description |
|------|------|-------------|
| `collectionId` | string | Identifier of the updated collection |

**Example Request:**

```bash
curl -X PUT "https://verifeye-face-api-eu.realeyes.ai/v1/collection/update" \
  -H "Authorization: ApiKey API-KEY-FROM-DEV-CONSOLE" \
  -H "Content-Type: application/json" \
  -d '{
    "collectionId": "my-collection",
    "description": "Updated description for my collection"
  }'
```

**Response Codes:**
- `200` - Collection updated
- `400` - Validation failure
- `401` - Authentication failure
- `404` - Collection not found

---

### Reset Collection

Removes all faces from a collection without deleting the collection itself. The collection (and its description) is preserved and remains usable; only the faces it contains — and any external key associations they had — are removed. This operation is irreversible.

!!!warning
For large collections the reset can take some time to complete. Stop onboarding into the collection before calling reset: faces onboarded after the reset has started are **not** guaranteed to be removed and may survive the operation. Resume onboarding only once the call has returned.
!!!

**Endpoint:** `DELETE /v1/collection/reset`

**Authentication:** API Key or Bearer Token

**Query Parameters:**

| Parameter | Type | Required | Description |
|-----------|------|----------|-------------|
| `collectionId` | string | Yes | The collection identifier |

**Response Example:**

```json
{
  "collectionId": "my-collection"
}
```

**Response Fields:**

| Name | Type | Description |
|------|------|-------------|
| `collectionId` | string (nullable) | Identifier of the collection that was reset |

**Example Request:**

```bash
curl -X DELETE "https://verifeye-face-api-eu.realeyes.ai/v1/collection/reset?collectionId=my-collection" \
  -H "Authorization: ApiKey API-KEY-FROM-DEV-CONSOLE"
```

**Response Codes:**
- `200` - Collection reset (all faces removed)
- `400` - Validation failure
- `401` - Authentication failure
- `404` - Collection not found

---

### Delete Collection

Deletes a collection by its identifier.

**Endpoint:** `DELETE /v1/collection/delete`

**Authentication:** API Key or Bearer Token

**Query Parameters:**

| Parameter | Type | Required | Description |
|-----------|------|----------|-------------|
| `collectionId` | string | Yes | The collection identifier |

**Response Example:**

```json
{}
```

**Example Request:**

```bash
curl -X DELETE "https://verifeye-face-api-eu.realeyes.ai/v1/collection/delete?collectionId=my-collection" \
  -H "Authorization: ApiKey API-KEY-FROM-DEV-CONSOLE"
```

**Response Codes:**
- `200` - Collection deleted (idempotent)
- `400` - Validation failure
- `401` - Authentication failure
- `404` - Collection not found

---

## External Key Management

External keys let you attach your own identifier (for example a customer id, user id or reference number) to a face. A face has **at most one** external key, while an external key maps to a single face by default — or to several faces when duplicates are explicitly allowed.

External keys are managed independently of onboarding: [Onboard](#onboard) never sets one. After onboarding a face, call `set` to associate a key with it. Once associated, the key comes back on every onboard and identify result that matches the face.

### Set External Key

Associates an external key with a face. Creates a new association or updates the existing one (a face's key is replaced when set again with a different key).

**Endpoint:** `PUT /v1/external-key/set`

**Authentication:** API Key or Bearer Token

**Request Body:**

```json
{
  "collectionId": "my-collection",
  "faceId": "face_abc123xyz789",
  "externalKey": "user-123",
  "allowDuplicates": false
}
```

**Request Parameters:**

| Parameter | Type | Required | Description |
|-----------|------|----------|-------------|
| `collectionId` | string | Yes | The id of the collection the face belongs to |
| `faceId` | string | Yes | The id of the face to associate the external key with. Max 64 characters |
| `externalKey` | string | Yes | The external key to associate. See [External key format](#external-key-format) |
| `allowDuplicates` | boolean | No | When `false` (default), the request is rejected with `400` if the external key is already associated with a different face. When `true`, the key may map to multiple faces |

**Response Example:**

```json
{
  "faceId": "face_abc123xyz789",
  "externalKey": "user-123",
  "result": "Created"
}
```

**Response Fields:**

| Field Path | Type | Description |
|------------|------|-------------|
| `faceId` | string | The id of the face the external key was associated with |
| `externalKey` | string | The external key that was associated |
| `result` | string | Whether the association was newly created or updated. Possible values: `Created`, `Updated` |

**Example Request:**

```bash
curl -X PUT "https://verifeye-face-api-eu.realeyes.ai/v1/external-key/set" \
  -H "Authorization: ApiKey API-KEY-FROM-DEV-CONSOLE" \
  -H "Content-Type: application/json" \
  -d '{
    "collectionId": "my-collection",
    "faceId": "face_abc123xyz789",
    "externalKey": "user-123",
    "allowDuplicates": false
  }'
```

**Response Codes:**
- `200` - Association created or updated
- `400` - Validation failure, or the external key is already associated with a different face and `allowDuplicates` is `false`
- `401` - Authentication failure
- `404` - Collection not found

---

### Get Face IDs by External Key

Returns all face ids associated with the specified external key in a collection.

**Endpoint:** `GET /v1/external-key/get-face-ids`

**Authentication:** API Key or Bearer Token

**Query Parameters:**

| Parameter | Type | Required | Description |
|-----------|------|----------|-------------|
| `collectionId` | string | Yes | The collection identifier |
| `externalKey` | string | Yes | The external key to look up. See [External key format](#external-key-format) |

**Response Example:**

```json
{
  "faceIds": ["face_abc123xyz789"]
}
```

**Response Fields:**

| Field Path | Type | Description |
|------------|------|-------------|
| `faceIds` | array | The ids of the faces associated with the external key. Empty when none are associated. Contains more than one id only when the key was set with `allowDuplicates: true` |

**Example Request:**

```bash
curl -X GET "https://verifeye-face-api-eu.realeyes.ai/v1/external-key/get-face-ids?collectionId=my-collection&externalKey=user-123" \
  -H "Authorization: ApiKey API-KEY-FROM-DEV-CONSOLE"
```

**Response Codes:**
- `200` - Lookup succeeded
- `400` - Validation failure
- `401` - Authentication failure
- `404` - Collection not found

---

### Delete External Key Association

Removes external key associations. When `faceId` is omitted, every face associated with the external key is unlinked; otherwise only the single pair is removed. This only removes the key association — it does not delete the face itself (use [Delete Face](#delete-face) for that).

**Endpoint:** `DELETE /v1/external-key/delete`

**Authentication:** API Key or Bearer Token

**Query Parameters:**

| Parameter | Type | Required | Description |
|-----------|------|----------|-------------|
| `collectionId` | string | Yes | The collection identifier |
| `externalKey` | string | Yes | The external key whose associations are removed. See [External key format](#external-key-format) |
| `faceId` | string | No | When omitted, all pairs for the external key are removed. When provided, only that single pair is removed |

**Response Example:**

```json
{}
```

**Example Request:**

```bash
curl -X DELETE "https://verifeye-face-api-eu.realeyes.ai/v1/external-key/delete?collectionId=my-collection&externalKey=user-123" \
  -H "Authorization: ApiKey API-KEY-FROM-DEV-CONSOLE"
```

**Response Codes:**
- `200` - Associations removed (idempotent)
- `400` - Validation failure
- `401` - Authentication failure
- `404` - Collection not found

---

### External Key Format

- **Maximum length:** 512 characters.
- **Allowed characters:** printable ASCII only (`0x21`–`0x7E`). Spaces, control characters and Unicode characters are rejected with `400`.
- **Recommended encoding:** keep keys ASCII. If you need to embed richer identifiers (Unicode names, or values containing spaces or slashes), base64url-encode them on your side and store the encoded value (e.g. matching `^[A-Za-z0-9_-]{1,512}$`).
- **Why ASCII only:** external keys are used as lookup keys and may appear in URLs and logs. Restricting to printable ASCII avoids URL-encoding ambiguity and Unicode normalization collisions, where two visually identical keys are not byte-for-byte equal and therefore do not match.

---

## Use Cases

For end-to-end scenarios using this API — including registering a face and checking it later — see the [Use Cases](/redirect/use-cases/) page.

---

## Health Check

Check the API health status.

**Endpoint:** `GET /v1/healthz`

**Authentication:** None required

**Response Example:**

```
2026-08-04T10:30:45Z
```

**Response Fields:**

| Name | Type | Description |
|------|------|-------------|
| (response body) | string | The server UTC time in ISO 8601 format |

**Example Request:**

```bash
curl -X GET "https://verifeye-face-api-eu.realeyes.ai/v1/healthz"
```

**Response Codes:**
- `200` - API is healthy

---

## Common Response Codes

| Code | Description |
|------|-------------|
| `200` | Success |
| `400` | Bad Request - Invalid parameters |
| `401` | Unauthorized - Missing or invalid authentication |
| `403` | Forbidden - Valid authentication but account not found or insufficient permissions |
| `404` | Not Found - Resource not found |
| `500` | Internal Server Error |
| `504` | Gateway Timeout - The request did not complete within the time budget |

Model validation failures (`400`) return a JSON body (`ProblemDetails`) with an `errors` object listing the invalid fields. A `401` returns `{"message":"Unauthorized"}` — no `errors` object. Some request-level errors — a missing image, an unfetchable image URL — return a plain-text message instead. Do not assume a single error shape; branch on `Content-Type` and on the status code.

---

## Swagger Documentation

Interactive API documentation is available via Swagger UI:

- **EU**: [https://verifeye-face-api-eu.realeyes.ai/swagger](https://verifeye-face-api-eu.realeyes.ai/swagger)
- **US**: [https://verifeye-face-api-us.realeyes.ai/swagger](https://verifeye-face-api-us.realeyes.ai/swagger)

The OpenAPI specification is available at:

- **EU**: [https://verifeye-face-api-eu.realeyes.ai/swagger/v1/swagger.json](https://verifeye-face-api-eu.realeyes.ai/swagger/v1/swagger.json)
- **US**: [https://verifeye-face-api-us.realeyes.ai/swagger/v1/swagger.json](https://verifeye-face-api-us.realeyes.ai/swagger/v1/swagger.json)

---

*Last updated: 2026-08-04*
