mcpField
D
didit-face-match

didit-face-match

github.com/didit-protocolSecurity29249 installsUpdated 2w ago
Repository

About didit-face-match

A skill for using Didit's Face Match API to compare two facial images. It returns a similarity score and an approval status, with a configurable threshold and optional image rotation.

Key features

  1. 01Compare two facial images
  2. 02Return similarity score and status
  3. 03Set a decline threshold
  4. 04Optionally rotate images for detection
  5. 05Report face boxes and estimates

Use cases

  1. 01Compare a selfie with an ID portrait
  2. 02Check whether two submitted images show the same person
  3. 03Assess face similarity with a chosen threshold

Install

Copy the command and run it in a terminal.

$npx skills add https://github.com/didit-protocol/skills --skill didit-face-match

After installing, type/didit-face-matchin Claude Code to confirm it loaded.

Bundled filesscripts/match_faces.py

Details

From the project README

Didit Face Match API

Overview

Compares two facial images to determine whether they show the same person. Returns a similarity score (0-100) and an Approved / Declined status.

Key constraints:

  • Allowed file extensions: tiff, jpg, jpeg, png, webp (no PDF)
  • Maximum file size: 5 MB per image (images are compressed to about 0.5 MB before processing)
  • Both user_image and ref_image are required
  • Submit one clear, front-facing, well-lit face per image for best results

Pricing: $0.05 (per successful call; standalone calls have no free tier). These are the list prices until 1 November 2026, when Didit pricing changes and the 500 free checks a month become $10 of free credit a month. Check didit.me/pricing for current prices.

Decision: Approved only when the score is strictly above face_match_score_decline_threshold (default 30). A score at or below it returns Declined with LOW_FACE_MATCH_SIMILARITY. If no face pair can be scored, the call still returns HTTP 200 with Declined and NO_REFERENCE_IMAGE. There is no In Review on this endpoint; only the threshold is configurable.

Face search index: this endpoint never enrolls faces and never runs blocklist or duplicate screening. Use POST /v3/passive-liveness/ (didit-liveness-detection) or POST /v3/face-search/ (didit-face-search) for those.

Saving (save_api_request, default true): the call is saved as an API session (Business Console, GET /v3/session/{request_id}/decision/, status.updated webhook) and both images are stored as the session's face-match source and target images. With false nothing is stored and request_id cannot be looked up.

Sandbox: sandbox API keys return a static Approved payload after request validation; nothing is saved or billed.

Capabilities: similarity scoring, configurable decline threshold, face bounding boxes with model-estimated age and gender per face (informational), optional rotation for non-upright faces.

API Reference: https://docs.didit.me/standalone-apis/face-match Feature Guide: https://docs.didit.me/core-technology/face-match/overview


Authentication

Every request needs the x-api-key header. Use your application's API key from the Didit Business Console under Developers > API keys, or create an account and key programmatically (see Getting Started). Named keys with custom access need Sessions write to run standalone checks; a key limited to some workflows or to approved sessions cannot run them.

Getting Started (No Account Yet?)

If you don't have a Didit API key, create one in 2 API calls:

  1. Register: POST https://apx.didit.me/auth/v2/programmatic/register/ with {"email": "[email protected]", "password": "MyStr0ng!Pass"}
  2. Check email for a 6-character verification code
  3. Verify: POST https://apx.didit.me/auth/v2/programmatic/verify-email/ with {"email": "[email protected]", "code": "A3K9F2"}. The key is in the response at application.api_key.

To add credits: GET /v3/billing/balance/ to check, POST /v3/billing/top-up/ with {"amount_in_dollars": 50} for a Stripe checkout link.

See the didit-verification-management skill for full platform management (workflows, sessions, users, billing).


Endpoint

POST https://verification.didit.me/v3/face-match/
Headers
HeaderValueRequired
x-api-keyYour API keyYes
Content-Typemultipart/form-data (set by your HTTP client)Yes
Request Parameters (multipart/form-data)
ParameterTypeRequiredDefaultConstraintsDescription
user_imagefileYes-tiff/jpg/jpeg/png/webp, max 5 MBFace to verify (e.g. a selfie)
ref_imagefileYes-Same as aboveReference face (e.g. an ID document portrait or an enrolled photo)
face_match_score_decline_thresholdfloatNo300-100A score at or below this value declines with LOW_FACE_MATCH_SIMILARITY; strictly above approves
rotate_imagebooleanNofalse-Try both images at 0/90/180/270 degrees and use the orientation with the best face detection. Adds latency
save_api_requestbooleanNotrue-Save as an API session and store both images (see Overview)
vendor_datastringNo--Your user identifier, stored and echoed back
metadataJSON objectNo-JSON-encoded string in multipartStored with the session (when saved) and echoed back
Example
import requests

with open("selfie.jpg", "rb") as selfie, open("id_photo.jpg", "rb") as portrait:
    response = requests.post(
        "https://verification.didit.me/v3/face-match/",
        headers={"x-api-key": "YOUR_API_KEY"},
        files={
            "user_image": ("selfie.jpg", selfie, "image/jpeg"),
            "ref_image": ("id_photo.jpg", portrait, "image/jpeg"),
        },
        data={"face_match_score_decline_threshold": "50", "vendor_data": "user-123"},
        timeout=60,
    )
response.raise_for_status()
face_match = response.json()["face_match"]
print(face_match["status"], face_match["score"])
const formData = new FormData();
formData.append("user_image", selfieFile);
formData.append("ref_image", referenceFile);
formData.append("face_match_score_decline_threshold", "50");
formData.append("vendor_data", "user-123");

const response = await fetch("https://verification.didit.me/v3/face-match/", {
  method: "POST",
  headers: { "x-api-key": "YOUR_API_KEY" },
  body: formData,
});
const { face_match } = await response.json();
Response (200 OK)
{
  "request_id": "f9f5a777-8002-44c6-baaf-128bcfd6e226",
  "face_match": {
    "status": "Approved",
    "score": 99.41,
    "user_image": {
      "entities": [
        {"bbox": [661, 728, 1688, 2188], "confidence": 0.732973, "age": 26.91, "gender": "male"}
      ],
      "best_angle": 0
    },
    "ref_image": {
      "entities": [
        {"bbox": [653, 745, 1691, 2185], "confidence": 0.722082, "age": 27.0, "gender": "male"}
      ],
      "best_angle": 0
    },
    "warnings": []
  },
  "vendor_data": "user-123",
  "metadata": null,
  "created_at": "2026-06-12T01:04:42.763237+00:00"
}
Status Values & Handling
StatusMeaningAction
"Approved"Score strictly above the thresholdFaces match, proceed
"Declined"Score at or below the threshold (LOW_FACE_MATCH_SIMILARITY), or no face pair could be scored (NO_REFERENCE_IMAGE)Check warnings. For NO_REFERENCE_IMAGE ask for clearer images (try rotate_image=true for sideways captures)
Error Responses
CodeMeaningAction
400Invalid request (missing image, file over 5 MB, unsupported extension, threshold outside 0-100)Read the field errors in the response body
401The API key has expiredRotate it or create a new key in Developers > API keys
403Missing, wrong or revoked API key; a scoped key without access to this endpoint; a request from outside the key's IP allowlist; or not enough creditsRead the message in detail or error: fix the x-api-key header or the key's access, or top up
429Rate limit: 300 POST/PATCH/DELETE and 600 GET requests a minute per keyWait the Retry-After seconds, then retry

Response Field Reference

FieldTypeDescription
request_iduuidSession id when save_api_request=true (use with GET /v3/session/{request_id}/decision/); otherwise a one-off correlation id
face_match.statusstring"Approved" or "Declined"
face_match.scorefloat or null0-100 similarity (higher = more similar). Rounded to 2 decimals when saved; raw float with save_api_request=false. When no face pair could be scored it is null (saved) or 0 (not saved), always with NO_REFERENCE_IMAGE
face_match.user_image.entities[]arrayOne entry per detected face: bbox [x_min, y_min, x_max, y_max], confidence (0-1), age, gender. Age and gender are informational and do not affect the decision
face_match.user_image.best_angleintegerRotation (0, 90, 180, 270) that gave the best detection; non-zero only when rotate_image=true corrected it
face_match.ref_imageobjectSame shape as user_image, for the reference image
face_match.warnings[]array{risk, feature, additional_data, log_type, short_description, long_description}. feature is always FACEMATCH, additional_data is always null
vendor_datastring or nullEcho of what you sent
metadataobject or nullEcho of what you sent
created_atstringISO 8601 UTC time of the response

Warning Tags

TagSeverityDeclinesWhen it fires
NO_REFERENCE_IMAGEerrorYesNo face pair could be scored (for example, no usable face in one of the images)
LOW_FACE_MATCH_SIMILARITYerrorYesScore at or below face_match_score_decline_threshold

NO_FACE_DETECTED is never raised by this endpoint: a missing face surfaces as NO_REFERENCE_IMAGE.

Security best practice: store only status, score and request_id. With save_api_request=true the stored images are available from the session decision as time-limited URLs (4 hours by default), so you do not need to keep biometric images on your servers.


Choosing a Threshold

  • The default 30 is permissive. Raise it (for example 50 to 60) for stricter verification; values outside 0-100 return 400.
  • The comparison is "strictly above approves": with threshold 50, a score of 50.0 declines.
  • The standalone endpoint has only a decline threshold. Hosted workflows (FACE_MATCH feature) add a separate face_match_score_review_threshold that sends scores between the two thresholds to In Review.
  • Log score alongside your own outcomes and tune the threshold to your false-match tolerance.

Common Workflows

Selfie vs ID Document
1. POST /v3/passive-liveness/ with the selfie -> confirm it is live (didit-liveness-detection)
2. POST /v3/id-verification/ with the document -> extract and check it (didit-id-document-verification)
3. POST /v3/face-match/ with user_image=selfie, ref_image=document portrait
4. All Approved -> the live person matches the document

For a hosted flow that does all three in one session, use a workflow with OCR, LIVENESS and FACE_MATCH (see the didit-kyc-onboarding skill).


Utility Scripts

export DIDIT_API_KEY="your_api_key"

python scripts/match_faces.py selfie.jpg id_photo.jpg
python scripts/match_faces.py selfie.jpg id_photo.jpg --threshold 50 --rotate --vendor-data user-123
python scripts/match_faces.py selfie.jpg id_photo.jpg --no-save --metadata '{"flow": "login"}'

Comments

Markdown supported