# Face Compare — Face Verification & Embeddings API (No GPU) (`adorable_partial/face-compare`) Actor

Check if photos show the same person: compare faces with a reference photo or embedding, get similarity scores, match/no-match and 128-d face embeddings. 99.0% on LFW. Your images only, no web search. Pay per image.

- **URL**: https://apify.com/adorable_partial/face-compare.md
- **Developed by:** [Leandro Zanatta](https://apify.com/adorable_partial) (community)
- **Stats:** 3 total users, 2 monthly users, 100.0% runs succeeded, 0 bookmarks
- **User rating**: No ratings yet

## Pricing

from $1.05 / 1,000 image analyzeds

This Actor is paid per event. You are not charged for the Apify platform usage, but only a fixed price for specific events.
Since this Actor supports Apify Store discounts, the price gets lower the higher subscription plan you have.

Learn more: https://docs.apify.com/actors/running/actors-in-store.md#pay-per-event

## What's an Apify Actor?

An Actor is a serverless cloud program that runs on the Apify platform. It has two run modes.
In Batch mode, an Actor accepts a well-defined JSON input, performs an action which can take anything from a few seconds to a few hours,
and optionally produces a well-defined JSON output, datasets with results, or files in key-value store.
In Standby mode, an Actor provides a web server which can be used as a website, API, or an MCP server.

Apify vocabulary and the platform model are defined once, in the agent quickstart at https://apify.com/agents.md.

## How to integrate an Actor?

If asked about integration, you help developers integrate Actors into their projects.
You adapt to their stack and deliver integrations that are safe, well-documented, and production-ready.

Do not guess an integration path. Every one of them is in the agent quickstart at https://apify.com/agents.md: the Apify MCP server, Agent Skills with the Apify CLI, the JavaScript and Python clients, the REST API, and the account-free path for an agent with no human to sign in. It also carries the rule on stating cost before the first paid run.

For examples already wired to this Actor's own input schema, see the [API](#api) section below.

Each client library has reference documentation the quickstart does not restate: [JavaScript/TypeScript](https://docs.apify.com/api/client/js/docs.md) (`npm install apify-client`) and [Python](https://docs.apify.com/api/client/python/docs.md) (`pip install apify-client`).

# README

## Face Compare — Face Verification & Face Embeddings API

Check whether **two or more photos show the same person**. Give the Actor a **reference photo** (or a stored **face embedding**) and a list of images: every face found is compared with the reference and you get a **similarity score**, a **match / no match** decision, the face **position**, and optionally the **128-number face embedding** to store and reuse. No GPU, no external AI service: detection and recognition run inside the Actor with open models.

![A reference portrait compared with three photos: two matches, including a small face in an 1862 group photo, and one different person](https://api.apify.com/v2/key-value-stores/npVfw50We9G7f32Ta/records/demo.png?signature=NeSYuqpGN42FZEkuGN71)

*Real output of this Actor. Public-domain photos from the Library of Congress via Wikimedia Commons.*

### What it does

- 🧑‍🤝‍🧑 **1:1 and 1:N verification**: compare a reference face with every face in your images
- 🎯 **Measured accuracy**: **99.0%** on the standard LFW benchmark (1,000 test pairs), with **0 false matches** at the default threshold
- 🔢 **Face embeddings**: 128-number vectors you can store and compare later, without keeping the photos
- 🖼️ **Several faces per image**: boxes, detection score and similarity for each face; group photos work
- 🧬 **Robust references**: give 2–5 photos of the same person and they are averaged into one reference
- ⚡ **Fast and cheap**: about 15 ms of compute per image on CPU; you pay per image analysed
- 🔒 **Your images only**: compares the images you send. It does **not** search the internet or any database of people

### Who uses it

| Sector | Typical use |
|---|---|
| **Onboarding & KYC flows** | Check that the selfie matches the ID document photo (with the user's consent) |
| **Events & access control** | Match attendee selfies with registration photos at check-in |
| **HR & workforce apps** | Confirm the employee clocking in is the registered employee |
| **Marketplaces & communities** | Detect duplicate or fake profiles that reuse the same face across accounts |
| **Photo services & archives** | Find all photos of a person in a client's own event, school or family album |
| **Developers & data teams** | Generate face embeddings for de-duplication, clustering and search in your own vector database |

### How it works

1. **Detection**: faces are found with **YuNet** (MIT license), including 5 facial landmarks.
2. **Alignment**: each face is aligned using the eyes, nose and mouth corners.
3. **Embedding**: **SFace** (Apache 2.0 license, OpenCV Model Zoo) turns each face into a 128-number vector.
4. **Comparison**: the cosine similarity between the reference and each face is computed. At or above the **threshold**, it is a match.

### Accuracy

Measured with this Actor's code on the 1,000 test pairs of the **LFW** benchmark (500 same-person and 500 different-person pairs):

| Threshold | Accuracy | False matches | Missed matches |
|---|---|---|---|
| 0.30 | 98.3% | 2.0% | 1.4% |
| 0.33 | 99.0% | 0.2% | 1.8% |
| **0.363 (default)** | **99.0%** | **0.0%** | **2.0%** |
| 0.40 | 98.8% | 0.0% | 2.4% |
| 0.45 | 98.3% | 0.0% | 3.4% |

Real-world accuracy depends on the photos: frontal, sharp, well-lit faces of at least ~80 px work best. Strong profiles, sunglasses, masks, very small or blurry faces reduce similarity. For decisions that matter, keep a human review step for borderline scores.

### Input

| Field | Type | Default | Description |
|---|---|---|---|
| **Reference photo(s)** (`referenceImageUrls`) | array | — | 1–5 photos of the person. The largest face of each is used and they are averaged |
| **Reference embedding** (`referenceEmbedding`) | array | — | A 128-number embedding returned earlier by this Actor |
| **Images to check** (`imageUrls`) | array | — | Images to analyse (JPEG, PNG, WebP, BMP, TIFF; up to 50 MB). Google Drive and Dropbox links work |
| **Match threshold** (`threshold`) | string | `0.363` | Similarity needed to call it a match |
| **Return embeddings** (`returnEmbeddings`) | boolean | `false` | Include each face's embedding (always on without a reference) |
| **Return facial landmarks** (`includeLandmarks`) | boolean | `false` | Eyes, nose tip and mouth corners per face |
| **Minimum face size** (`minFaceSize`) | integer | `40` | Ignore faces smaller than this (px) |
| **Max faces per image** (`maxFacesPerImage`) | integer | `20` | Keep only the largest N faces |

**Three ways to use it:**

```json
{ "referenceImageUrls": ["https://example.com/id-photo.jpg"], "imageUrls": ["https://example.com/selfie.jpg"] }
```

```json
{ "referenceEmbedding": [0.0123, -0.0456, "… 128 numbers …"], "imageUrls": ["https://example.com/new-photo.jpg"] }
```

```json
{ "imageUrls": ["https://example.com/a.jpg", "https://example.com/b.jpg"], "returnEmbeddings": true }
```

The last one (no reference) only extracts embeddings, for your own storage, clustering or de-duplication.

### Output

One dataset row per image:

```json
{
  "sourceUrl": "https://example.com/selfie.jpg",
  "width": 1280,
  "height": 1681,
  "facesFound": 1,
  "isMatch": true,
  "bestSimilarity": 0.8942,
  "bestFaceIndex": 0,
  "faces": [
    {
      "faceIndex": 0,
      "box": { "x1": 340, "y1": 280, "x2": 790, "y2": 871 },
      "detectionScore": 0.926,
      "similarity": 0.8942,
      "isMatch": true,
      "embedding": [0.0412, -0.0187, "… 128 numbers, only with returnEmbeddings …"]
    }
  ]
}
```

| Field | Meaning |
|---|---|
| `isMatch` | `true` if any face in the image matches the reference |
| `bestSimilarity`, `bestFaceIndex` | Highest similarity in the image and which face it was |
| `faces[].similarity` | Cosine similarity to the reference, from -1 to 1 (on LFW, 90% of same-person pairs scored 0.48–0.83 and 95% of different-person pairs scored below 0.25) |
| `faces[].box` | Face position in pixels |
| `faces[].embedding` | 128 numbers, L2-normalised |
| `error` | Present only when an image failed (failed images are free) |

The reference actually used (averaged embedding and which face was taken from each reference photo) is saved in the run's key-value store as **`REFERENCE`**, so you can reuse it as `referenceEmbedding` in later runs.

### Pricing

Pay per **image analysed** (reference photos included), plus a tiny per-run start fee. No subscription. Failed images are free. Apify plan discounts apply automatically (see the **Pricing** tab).

| Example | Images billed |
|---|---|
| Selfie vs ID photo | 2 |
| 1 reference vs 500 event photos | 501 |
| Embeddings for 10,000 profile photos | 10,000 |

Use the run's *maximum cost* setting to cap spending; the Actor stops cleanly when the limit is reached.

### Use it from code or an AI agent

**Python: verify a selfie against an ID photo**

```python
from apify_client import ApifyClient

client = ApifyClient("<YOUR_APIFY_TOKEN>")
run = client.actor("adorable_partial/face-compare").call(run_input={
    "referenceImageUrls": ["https://example.com/id-photo.jpg"],
    "imageUrls": ["https://example.com/selfie.jpg"],
})
row = next(client.dataset(run["defaultDatasetId"]).iterate_items())
print(row["isMatch"], row["bestSimilarity"])
```

**JavaScript**

```javascript
import { ApifyClient } from 'apify-client';

const client = new ApifyClient({ token: '<YOUR_APIFY_TOKEN>' });
const run = await client.actor('adorable_partial/face-compare').call({
    imageUrls: ['https://example.com/a.jpg', 'https://example.com/b.jpg'],
    returnEmbeddings: true,
});
const { items } = await client.dataset(run.defaultDatasetId).listItems();
items.forEach((i) => console.log(i.sourceUrl, i.faces.map((f) => f.embedding.length)));
```

**HTTP (one request, waits for the result)**

```bash
curl -X POST "https://api.apify.com/v2/acts/adorable_partial~face-compare/run-sync-get-dataset-items?token=<YOUR_APIFY_TOKEN>" \
  -H "Content-Type: application/json" \
  -d '{"referenceImageUrls": ["https://example.com/id.jpg"], "imageUrls": ["https://example.com/selfie.jpg"]}'
```

**No-code and agents:** use the Apify modules in **Make**, **Zapier** or **n8n**, trigger runs from **webhooks**, or call it from an AI agent through the **Apify MCP server**.

### Responsible use

Face images and face embeddings are **biometric data**, a special category of personal data under the **GDPR (art. 9)**, Brazil's **LGPD (art. 11)** and similar laws. When you use this Actor:

- Process only images you are allowed to process, with a valid legal basis, usually the **explicit consent** of the people involved.
- Use it to **verify** a person who takes part in your process (onboarding, check-in, de-duplication), **not to identify strangers** or monitor people.
- Keep embeddings only as long as needed; delete runs and datasets you no longer need (Apify's data-retention settings apply).
- Combine scores with human review where a wrong decision could harm someone.

This Actor has no web search, no face database and no identity lookup: it only compares the faces you send to each other.

### FAQ

**What similarity counts as the same person?** The default threshold 0.363 is the one recommended for this model and gave 0 false matches on LFW. Raise it (0.40–0.45) when false matches are costly, lower it (0.30–0.33) when missing a match is costlier.

**Can I compare embeddings myself?** Yes. Embeddings are L2-normalised, so the dot product equals the cosine similarity. Use the same threshold.

**Are embeddings compatible with other face models?** No. Only compare embeddings produced by this Actor.

**Is it a liveness / anti-spoofing check?** No. It compares faces in images; it cannot tell a live person from a photo of a photo.

**Does it detect age, gender or emotion?** No, and it is not intended to.

**Which models and licenses?** YuNet face detector (MIT) and SFace face recognizer (Apache 2.0), both from the OpenCV Model Zoo. No third-party API is called.

### Related Actors

- [Image & Video Anonymizer](https://apify.com/adorable_partial/image-anonymizer) — blur faces and license plates (GDPR / LGPD)
- [Video Frame Extractor for AI](https://apify.com/adorable_partial/video-frame-extractor-ai) — turn videos into clean image datasets
- [Video Optimizer](https://apify.com/adorable_partial/video-optimizer) — reduce FPS, resize and compress videos
- [Audio & Video to Text (Whisper)](https://apify.com/adorable_partial/whisper-audio-video-transcriber) — transcripts and subtitles

### Support

Questions or a use case that needs another option? Open an issue in the **Issues** tab.

### Need a different tool? Request it

Missing a feature, an input or output format, or a whole different tool for your workflow? **Open an issue in the Issues tab** and describe what you need:

- what goes in (files, URLs, data) and what should come out,
- roughly how much you process (per day or month),
- one example, if you can share it.

Requests with a real use case get built, either as a new option in this Actor or as a new Actor in [this collection](https://apify.com/adorable_partial), and you get notified in the issue when it's live. Every request is read.

# Actor input Schema

## `referenceImageUrls` (type: `array`):

One or more clear photos of the person to look for (the largest face in each photo is used; several photos are averaged into a more robust reference). Leave empty to only extract face embeddings.

## `referenceEmbedding` (type: `array`):

A 128-number face embedding returned earlier by this Actor (field "embedding" or the REFERENCE record). Lets you compare against a stored person without sending the photo again. Combined with reference photos if both are given.

## `imageUrls` (type: `array`):

Images to analyse (JPEG, PNG, WebP, BMP, TIFF; up to 50 MB each). Every face found is compared with the reference. Public Google Drive and Dropbox links work.

## `threshold` (type: `string`):

Cosine similarity at or above which two faces count as the same person. 0.363 (default) gave 99.0% accuracy and 0 false matches on the LFW benchmark. Raise it (e.g. 0.45) for fewer false matches, lower it (e.g. 0.30) to miss fewer true matches.

## `returnEmbeddings` (type: `boolean`):

Include the 128-number embedding of every face in the output (always on when no reference is given).

## `includeLandmarks` (type: `boolean`):

Include the 5 landmark points (eyes, nose tip, mouth corners) of every face.

## `minFaceSize` (type: `integer`):

Ignore faces smaller than this. Tiny faces give unreliable comparisons.

## `maxFacesPerImage` (type: `integer`):

Keep only the N largest faces of each image.

## Actor input object example

```json
{
  "referenceImageUrls": [
    "https://thumb.wikimedia.org/wikipedia/commons/thumb/a/ab/Abraham_Lincoln_O-77_matte_collodion_print.jpg/1280px-Abraham_Lincoln_O-77_matte_collodion_print.jpg"
  ],
  "imageUrls": [
    "https://thumb.wikimedia.org/wikipedia/commons/thumb/4/44/Abraham_Lincoln_head_on_shoulders_photo_portrait.jpg/1280px-Abraham_Lincoln_head_on_shoulders_photo_portrait.jpg",
    "https://thumb.wikimedia.org/wikipedia/commons/thumb/1/12/Abraham_Lincoln_and_George_B_McClellan_in_the_general%27s_tent%2C_Oct_1862.jpg/1280px-Abraham_Lincoln_and_George_B_McClellan_in_the_general%27s_tent%2C_Oct_1862.jpg",
    "https://thumb.wikimedia.org/wikipedia/commons/thumb/5/5c/Ulysses_S._Grant_1870-1880.jpg/1280px-Ulysses_S._Grant_1870-1880.jpg"
  ],
  "threshold": "0.363",
  "returnEmbeddings": false,
  "includeLandmarks": false,
  "minFaceSize": 40,
  "maxFacesPerImage": 20
}
```

# Actor output Schema

## `results` (type: `string`):

One row per image: faces found, similarity to the reference, match / no match, boxes and optional embeddings.

## `reference` (type: `string`):

The averaged reference embedding (reuse it later as referenceEmbedding).

# API

You can run this Actor programmatically using our API. Below are code examples in JavaScript, Python, and CLI, as well as the OpenAPI specification and MCP server setup.

## JavaScript example

```javascript
import { ApifyClient } from 'apify-client';

// Initialize the ApifyClient with your Apify API token
// Replace the '<YOUR_API_TOKEN>' with your token
const client = new ApifyClient({
    token: '<YOUR_API_TOKEN>',
});

// Prepare Actor input
const input = {
    "referenceImageUrls": [
        "https://thumb.wikimedia.org/wikipedia/commons/thumb/a/ab/Abraham_Lincoln_O-77_matte_collodion_print.jpg/1280px-Abraham_Lincoln_O-77_matte_collodion_print.jpg"
    ],
    "imageUrls": [
        "https://thumb.wikimedia.org/wikipedia/commons/thumb/4/44/Abraham_Lincoln_head_on_shoulders_photo_portrait.jpg/1280px-Abraham_Lincoln_head_on_shoulders_photo_portrait.jpg",
        "https://thumb.wikimedia.org/wikipedia/commons/thumb/1/12/Abraham_Lincoln_and_George_B_McClellan_in_the_general%27s_tent%2C_Oct_1862.jpg/1280px-Abraham_Lincoln_and_George_B_McClellan_in_the_general%27s_tent%2C_Oct_1862.jpg",
        "https://thumb.wikimedia.org/wikipedia/commons/thumb/5/5c/Ulysses_S._Grant_1870-1880.jpg/1280px-Ulysses_S._Grant_1870-1880.jpg"
    ]
};

// Run the Actor and wait for it to finish
const run = await client.actor("adorable_partial/face-compare").call(input);

// Fetch and print Actor results from the run's dataset (if any)
console.log('Results from dataset');
console.log(`💾 Check your data here: https://console.apify.com/storage/datasets/${run.defaultDatasetId}`);
const { items } = await client.dataset(run.defaultDatasetId).listItems();
items.forEach((item) => {
    console.dir(item);
});

// 📚 Want to learn more 📖? Go to → https://docs.apify.com/api/client/js/docs

```

## Python example

```python
from apify_client import ApifyClient

# Initialize the ApifyClient with your Apify API token
# Replace '<YOUR_API_TOKEN>' with your token.
client = ApifyClient("<YOUR_API_TOKEN>")

# Prepare the Actor input
run_input = {
    "referenceImageUrls": ["https://thumb.wikimedia.org/wikipedia/commons/thumb/a/ab/Abraham_Lincoln_O-77_matte_collodion_print.jpg/1280px-Abraham_Lincoln_O-77_matte_collodion_print.jpg"],
    "imageUrls": [
        "https://thumb.wikimedia.org/wikipedia/commons/thumb/4/44/Abraham_Lincoln_head_on_shoulders_photo_portrait.jpg/1280px-Abraham_Lincoln_head_on_shoulders_photo_portrait.jpg",
        "https://thumb.wikimedia.org/wikipedia/commons/thumb/1/12/Abraham_Lincoln_and_George_B_McClellan_in_the_general%27s_tent%2C_Oct_1862.jpg/1280px-Abraham_Lincoln_and_George_B_McClellan_in_the_general%27s_tent%2C_Oct_1862.jpg",
        "https://thumb.wikimedia.org/wikipedia/commons/thumb/5/5c/Ulysses_S._Grant_1870-1880.jpg/1280px-Ulysses_S._Grant_1870-1880.jpg",
    ],
}

# Run the Actor and wait for it to finish
run = client.actor("adorable_partial/face-compare").call(run_input=run_input)

# Fetch and print Actor results from the run's dataset (if there are any)
print(f"💾 Check your data here: https://console.apify.com/storage/datasets/{run.default_dataset_id}")
for item in client.dataset(run.default_dataset_id).iterate_items():
    print(item)

# 📚 Want to learn more 📖? Go to → https://docs.apify.com/api/client/python/docs/quick-start

```

## CLI example

```bash
echo '{
  "referenceImageUrls": [
    "https://thumb.wikimedia.org/wikipedia/commons/thumb/a/ab/Abraham_Lincoln_O-77_matte_collodion_print.jpg/1280px-Abraham_Lincoln_O-77_matte_collodion_print.jpg"
  ],
  "imageUrls": [
    "https://thumb.wikimedia.org/wikipedia/commons/thumb/4/44/Abraham_Lincoln_head_on_shoulders_photo_portrait.jpg/1280px-Abraham_Lincoln_head_on_shoulders_photo_portrait.jpg",
    "https://thumb.wikimedia.org/wikipedia/commons/thumb/1/12/Abraham_Lincoln_and_George_B_McClellan_in_the_general%27s_tent%2C_Oct_1862.jpg/1280px-Abraham_Lincoln_and_George_B_McClellan_in_the_general%27s_tent%2C_Oct_1862.jpg",
    "https://thumb.wikimedia.org/wikipedia/commons/thumb/5/5c/Ulysses_S._Grant_1870-1880.jpg/1280px-Ulysses_S._Grant_1870-1880.jpg"
  ]
}' |
apify call adorable_partial/face-compare --silent --output-dataset

```

## MCP server setup

```json
{
    "mcpServers": {
        "apify": {
            "type": "http",
            "url": "https://mcp.apify.com/?tools=fetch-actor-details,adorable_partial/face-compare"
        }
    }
}
```

The hosted server signs you in with OAuth on first connect, so no API token belongs in this config. Clients without OAuth support can send an `Authorization: Bearer <APIFY_API_TOKEN>` header instead, using a token from API & Integrations in Apify Console (https://console.apify.com/settings/integrations).

## OpenAPI specification

Download the OpenAPI definition: https://api.apify.com/v2/actors/3cZtjTepdCfkWzckn/builds/PMFDhokzswdM4sfGX/openapi.json
