Kairos and Betaface were two of the go-to face recognition APIs of the 2010s: enroll a face under a name, then recognize it in any photo. In 2026, Kairos sells identity verification and the public Betaface API is offline, which leaves attendance apps, visitor kiosks and photo tools built on them looking for a replacement.
Below: a small Python module that keeps the Kairos function names (enroll, recognize, verify, galleries) and the Kairos response shape on top of a hosted face recognition API. Every output comes from a live run on real photos of an office team.
Want to run it on your own photos? Get a key for the Face Analyzer API.
Where Kairos and Betaface stand in 2026
Checked on 28 October 2026:
- Kairos: kairos.com now presents identity verification (documents, selfies, liveness). The classic face recognition docs still live on face.kairos.com, but sign-up asks for a business email and how you plan to use Kairos, and pricing is a contact-sales form
- Betaface: the betaface.com products page reads "Public betafaceapi.com is temporarily down", the latest news post (17 March 2026) says the website is being restored, and the betafaceapi.com domain no longer resolves. On-premise licences are still offered
Kairos endpoints mapped
| Kairos | Face Analyzer API |
|---|---|
/enroll |
/save-face-in-repository (subject_id becomes external_id) |
/recognize |
/faceanalysis, then /search-face-in-repository per face |
/verify |
/search-face-in-repository filtered on the subject |
/detect |
/faceanalysis (box, landmarks, age range, gender, emotions) |
/gallery/list_all |
/list-facial-repositories |
/gallery/view |
/list-repository-faces |
/gallery/remove_subject |
/delete-face-from-repository |
/gallery/remove |
/delete-facial-repository |
| confidence 0 to 1, default 0.60 | Similarity 0 to 100, we use 90 |
For Betaface, a namespace becomes a face repository, a person such as john@mynamespace becomes the external_id, face tags (age, gender, glasses, smile) come from /faceanalysis, verification is /compare-faces and identification is /search-face-in-repository. The module below works as the base for both migrations.
Step 1: a Kairos-compatible module
Kairos /recognize matches every face in a photo, while the repository search looks at the main face of the image it receives, so recognize detects the faces first and searches a crop of each one. Save as kairos_compat.py:
import io
import requests
from PIL import Image, ImageOps
BASE = "https://faceanalyzer-ai.p.rapidapi.com"
HEADERS = {
"x-rapidapi-host": "faceanalyzer-ai.p.rapidapi.com",
"x-rapidapi-key": "YOUR_API_KEY",
}
MIN_SIMILARITY = 90 # 0 to 100 scale
def _post(endpoint, files=None, data=None):
r = requests.post(f"{BASE}{endpoint}", headers=HEADERS, files=files, data=data)
return r.json()["body"]
def _get(endpoint, params=None):
r = requests.get(f"{BASE}{endpoint}", headers=HEADERS, params=params)
return r.json()["body"]
def _search(face_jpg, gallery_name):
"""Return the matches of one face in a gallery, best first."""
body = _post(
"/search-face-in-repository",
files={"image": ("face.jpg", face_jpg, "image/jpeg")},
data={"repository_id": gallery_name},
)
matches = [m for m in body.get("FaceMatches", []) if m["Similarity"] >= MIN_SIMILARITY]
return sorted(matches, key=lambda m: m["Similarity"], reverse=True)
def enroll(image_path, subject_id, gallery_name):
"""Kairos /enroll: store the face under subject_id, creating the gallery if needed."""
if gallery_name not in _get("/list-facial-repositories")["FacialRepositoriesId"]:
_post("/create-facial-repository", data={"repository_id": gallery_name})
with open(image_path, "rb") as f:
body = _post(
"/save-face-in-repository",
files={"image": f},
data={"repository_id": gallery_name, "external_id": subject_id, "max_faces": "1"},
)
return {"face_id": body["FacesId"][0], "subject_id": subject_id}
def recognize(image_path, gallery_name):
"""Kairos /recognize: match every face in the photo against the gallery."""
with open(image_path, "rb") as f:
faces = _post("/faceanalysis", files={"image": f})["faces"]
im = ImageOps.exif_transpose(Image.open(image_path)).convert("RGB")
w, h = im.size
images = []
for face in faces:
b = face["boundingBox"]
x1, y1 = int(b["topLeft"]["x"] * w), int(b["topLeft"]["y"] * h)
x2, y2 = int(b["bottomRight"]["x"] * w), int(b["bottomRight"]["y"] * h)
m = int((x2 - x1) * 0.4)
buf = io.BytesIO()
im.crop((max(0, x1 - m), max(0, y1 - m), min(w, x2 + m), min(h, y2 + m))).save(buf, "JPEG")
matches = _search(buf.getvalue(), gallery_name)
transaction = {"topLeftX": x1, "topLeftY": y1, "width": x2 - x1, "height": y2 - y1}
if matches:
transaction.update(
status="success",
subject_id=matches[0]["ExternalImageId"],
confidence=round(matches[0]["Similarity"] / 100, 5),
)
else:
transaction.update(status="failure", message="no match found")
candidates = [
{"subject_id": m["ExternalImageId"], "confidence": round(m["Similarity"] / 100, 5)}
for m in matches
]
images.append({"transaction": transaction, "candidates": candidates})
return {"images": images}
def verify(image_path, subject_id, gallery_name):
"""Kairos /verify: is the main face in the photo this subject?"""
with open(image_path, "rb") as f:
matches = _search(f.read(), gallery_name)
scores = [m["Similarity"] for m in matches if m["ExternalImageId"] == subject_id]
confidence = round(max(scores, default=0) / 100, 5)
return {"status": "success" if scores else "failure", "subject_id": subject_id, "confidence": confidence}
def gallery_view(gallery_name):
"""Kairos /gallery/view: the subject_ids enrolled in a gallery."""
faces = _get("/list-repository-faces", {"repository_id": gallery_name})["Faces"]
return {"subject_ids": sorted({f["ExternalImageId"] for f in faces})}
def gallery_remove_subject(gallery_name, subject_id):
"""Kairos /gallery/remove_subject: delete every face enrolled for subject_id."""
faces = _get("/list-repository-faces", {"repository_id": gallery_name})["Faces"]
face_ids = [f["FaceId"] for f in faces if f["ExternalImageId"] == subject_id]
_post("/delete-face-from-repository", data={"repository_id": gallery_name, "face_ids": face_ids})
return {"status": "Complete", "removed": len(face_ids)}
def gallery_remove(gallery_name):
"""Kairos /gallery/remove: delete the gallery and all its faces."""
return _post("/delete-facial-repository", data={"repository_id": gallery_name})
Step 2: enroll your people
Four staff badges, one photo each, with the same call as Kairos. The gallery is created on first use:
from kairos_compat import enroll, recognize, verify, gallery_view, gallery_remove_subject
GALLERY = "office-staff"
for name in ["marcus", "andre", "nia", "daniel"]:
print(enroll(f"badges/{name}.jpg", name, GALLERY))
# {'face_id': 'b2ab7035-...', 'subject_id': 'marcus'}
# ... one line per person
Step 3: recognize every face in a group photo
result = recognize("team.jpg", GALLERY)
for image in result["images"]:
t = image["transaction"]
print(t["status"], t.get("subject_id", "-"), t.get("confidence", ""))
# success daniel 0.9998
# success nia 0.99934
# failure -
# success andre 0.99999
# success marcus 0.99999
Five faces, five correct answers in under 3 seconds: the four enrolled people are named above 0.999, and the fifth man, never enrolled, comes back as failure with no candidate and the same "no match found" message Kairos uses.
Step 4: verify one person
Tested on a photo from another day of the shoot, in a suit and tie instead of the white t-shirt of his badge:
print(verify("daniel_suit.jpg", "daniel", GALLERY))
print(verify("daniel_suit.jpg", "marcus", GALLERY))
# {'status': 'success', 'subject_id': 'daniel', 'confidence': 0.99975}
# {'status': 'failure', 'subject_id': 'marcus', 'confidence': 0.0}
Step 5: manage the gallery
print(gallery_view(GALLERY))
print(gallery_remove_subject(GALLERY, "andre"))
print(gallery_view(GALLERY))
# {'subject_ids': ['andre', 'daniel', 'marcus', 'nia']}
# {'status': 'Complete', 'removed': 1}
# {'subject_ids': ['daniel', 'marcus', 'nia']}
Migration tips
-
Re-enroll from the original photos: face templates are specific to each engine, so run your source images through
enrollonce with the same subject_ids - Set the threshold on the new scale: 90 on the 0 to 100 Similarity scale is a strict start for access control
- Enroll more than one photo per person: a profile shot or a photo with glasses helps in real conditions
- Switch behind a flag: the module returns Kairos-shaped JSON, so you can compare answers on a share of traffic first
- One gallery per customer: never mix the faces of two customers in a SaaS
Full guide with the Betaface mapping and sources: Kairos and Betaface API alternative on ai-engine.net.
Sources: Kairos API reference, Betaface products page, test photos by Kindel Media on Pexels.
👉 Grab a key from the Face Analyzer API page and point your app at kairos_compat.py.

Top comments (0)