If your app has to confirm that the person holding the phone is the person on the ID card, or that today's check-in selfie matches the photo enrolled last month, you need a face comparison API: send two images, get back whether they show the same person. Here is the one-call version with the Face Analyzer API, how to read the response, and a verify function that handles the cases naive integrations miss.
Want to try it on your own photos? Get a key on the Face Analyzer API page and run the snippets below.
Compare Two Faces in One Call
/compare-faces takes a source_image (the reference face, e.g. the ID photo) and a target_image (the photo to check, e.g. the selfie) as multipart uploads.
curl -X POST "https://faceanalyzer-ai.p.rapidapi.com/compare-faces" \
-H "x-rapidapi-key: YOUR_API_KEY" \
-H "x-rapidapi-host: faceanalyzer-ai.p.rapidapi.com" \
-F "source_image=@id_photo.jpg" \
-F "target_image=@selfie.jpg"
import requests
URL = "https://faceanalyzer-ai.p.rapidapi.com/compare-faces"
HEADERS = {
"x-rapidapi-host": "faceanalyzer-ai.p.rapidapi.com",
"x-rapidapi-key": "YOUR_API_KEY",
}
with open("id_photo.jpg", "rb") as src, open("selfie.jpg", "rb") as tgt:
response = requests.post(
URL,
headers=HEADERS,
files={"source_image": src, "target_image": tgt},
)
body = response.json()["body"]
print(len(body["matchedFaces"]), "matched,", len(body["unmatchedFaces"]), "unmatched")
# 1 matched, 0 unmatched
Photos already online? Send source_image_url and target_image_url as form fields (form-urlencoded or multipart; a JSON body is rejected).
Reading the Response
Each face found in the target lands in matchedFaces (same person as the source) or unmatchedFaces (anyone else), with a normalized boundingBox and eye, nose and mouth landmarks. A real result on a target photo with two people:
Two things to know:
- The endpoint makes the match decision for you: no threshold to tune.
- No usable face in either image returns two empty lists: check both and ask for a new photo.
So handle four outcomes:
- 1 matched, 0 unmatched: same person, accept
- 0 matched, 1 unmatched: different person, reject or review
- 0 and 0: no usable face, ask for a new photo
- more than one face in total: ask for a solo photo
A Verify Function You Can Ship
def verify(source_path, target_path):
with open(source_path, "rb") as src, open(target_path, "rb") as tgt:
response = requests.post(
URL,
headers=HEADERS,
files={"source_image": src, "target_image": tgt},
)
body = response.json()["body"]
matched = len(body["matchedFaces"])
unmatched = len(body["unmatchedFaces"])
if matched == 0 and unmatched == 0:
return "NO_FACE" # no usable face found: ask for a new photo
if matched + unmatched > 1:
return "MULTIPLE_FACES" # a selfie should show exactly one person
return "MATCH" if matched == 1 else "NO_MATCH"
print(verify("id_photo.jpg", "selfie.jpg")) # MATCH
print(verify("id_photo.jpg", "stranger.jpg")) # NO_MATCH
print(verify("id_photo.jpg", "landscape.jpg")) # NO_FACE
print(verify("id_photo.jpg", "two_people.jpg")) # MULTIPLE_FACES
Those are the real outputs on our test photos. Keep the source to one face too: with two people in the source, the API compared using the larger face.
The full guide has the JavaScript version and the annotated results.
Need a Similarity Score? Use 1:N Search
For access control, check-in or attendance you have one new photo and many enrolled people. Enroll each person in a facial repository, then search: the response includes a Similarity from 0 to 100.
BASE = "https://faceanalyzer-ai.p.rapidapi.com"
requests.post(f"{BASE}/create-facial-repository", headers=HEADERS,
data={"repository_id": "office-access"})
with open("id_photo.jpg", "rb") as f:
requests.post(
f"{BASE}/save-face-in-repository",
headers=HEADERS,
files={"image": f},
data={"repository_id": "office-access", "external_id": "employee-001", "max_faces": "1"},
)
with open("selfie.jpg", "rb") as f:
response = requests.post(
f"{BASE}/search-face-in-repository",
headers=HEADERS,
files={"image": f},
data={"repository_id": "office-access"},
)
matches = response.json()["body"]["FaceMatches"]
if matches and matches[0]["Similarity"] >= 90:
print("ACCESS GRANTED:", matches[0]["ExternalImageId"], round(matches[0]["Similarity"], 2))
else:
print("ACCESS DENIED")
# ACCESS GRANTED: employee-001 99.96
Someone never enrolled returns an empty FaceMatches list.
Use Cases
- KYC: selfie vs the portrait cropped from an ID document
- Access control and event check-in: search camera captures against enrolled people
- Attendance: a daily 1:N search stops buddy punching
- Duplicate accounts: search new signup selfies against existing users
- Exam proctoring: re-check the student at random intervals
Production Tips
- Several faces in the frame make the result ambiguous: reject for verification
- ID portraits are small: crop tightly and use the best scan available
- Front-facing, evenly lit selfies without heavy sunglasses give the cleanest match
- Pair identity with liveness: for onboarding, payments or door access, add a liveness step
- Faces are biometric data: consent, documented retention, deletion on request
👉 Get your key on the Face Analyzer API page and run verify() on your own ID photo and selfie.

Top comments (0)