FaceLearn API Documentation
A single, versioned REST API for face enrollment, verification, attendance tracking, and analytics. All endpoints accept and return JSON.
Base URL: https://api.facelearn.io/v1
All requests require an API key. Get yours from your dashboard.
Introduction
FaceLearn.io is a face recognition server designed for training programs, schools, and institutions. It handles three things exceptionally well:
- Enrollment — register a face and store it as an encrypted template
- Verification — match a live face against a stored template with liveness detection
- Attendance — record check-ins, track history, and generate reports
Raw images are never stored. Every face is converted to an irreversible mathematical template the moment it enters the system, and the original image is discarded in the same request.
Biological data requires consent. You are responsible for obtaining explicit, informed consent from every person whose face you enroll. See our Privacy Policy and Terms of Service.
Quickstart
Get from zero to your first verification in under 5 minutes.
-
Create an account
Sign up at facelearn.io/signup.php. You'll receive your API key by email — save it, we only show it once.
-
Store your key safely
Never commit your API key to a repository. Use an environment variable:
.envFACELEARN_API_KEY=fl_live_xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx -
Enroll a face
Send a base64-encoded image to
POST /enroll. You'll get back a template ID you can use for future verifications. -
Verify a face
Send a live capture to
POST /match-face. The server runs liveness detection and returns a match result with a confidence score. -
Record attendance
On a successful match, call
POST /attendancewith the person's ID to write the check-in to their record.
Authentication
Every request must include your API key in the Authorization header using the Bearer scheme:
Authorization: Bearer fl_live_xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx
Alternatively, the older X-API-Key header is still supported for backward compatibility:
X-API-Key: fl_live_xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx
Key format
Keys follow the pattern fl_<env>_<40 random chars>:
fl_live_…— production key, hits real datafl_test_…— sandbox key, no real verification, free (no quota impact)
Keep your key secret. Treat it like a password. If it leaks, revoke it immediately from your dashboard and issue a new one. Never expose it in client-side JavaScript or mobile apps — always proxy through your own server.
Errors
FaceLearn uses conventional HTTP status codes. Responses with errors return a JSON body with a machine-readable error code and a human-readable message.
{
"error": "invalid_api_key",
"message": "The API key provided is invalid or has been revoked."
}
| Status | Meaning | Common causes |
|---|---|---|
| 200 | Success | Request processed |
| 400 | Bad request | Missing fields, malformed JSON |
| 401 | Unauthorized | Missing or invalid API key |
| 403 | Forbidden | Key lacks the required scope |
| 404 | Not found | Resource doesn't exist or belongs to another org |
| 422 | Unprocessable | Face not detected, liveness failed |
| 429 | Rate limited | Too many requests — see Retry-After header |
| 500 | Server error | Unexpected failure — contact support |
Rate limits
Rate limits are applied per API key:
| Plan | Requests / min | Requests / day |
|---|---|---|
| Starter | 30 | 1,000 |
| Growth | 300 | 100,000 |
| Enterprise | Custom | Custom |
When you exceed a limit, the API returns 429 Too Many Requests with a Retry-After header indicating the number of seconds to wait:
HTTP/2 429
Retry-After: 42
X-RateLimit-Limit: 300
X-RateLimit-Remaining: 0
X-RateLimit-Reset: 1735689600
Enroll a face
Register a face and store it as an encrypted template. The raw image is discarded immediately after the template is created.
Request body
| Field | Type | Required | Description |
|---|---|---|---|
image |
string | required | Base64-encoded JPEG or PNG. Max 5 MB, min 200×200 px. |
person_id |
string | required | Your internal ID for this person (e.g. a student number). Must be unique per org. |
label |
string | optional | Human-readable label, e.g. "Juan Dela Cruz". |
metadata |
object | optional | Any JSON object — cohort, department, enrollment date, etc. |
curl -X POST https://api.facelearn.io/v1/enroll \
-H "Authorization: Bearer $FACELEARN_API_KEY" \
-H "Content-Type: application/json" \
-d '{
"image": "'$(base64 -w0 face.jpg)'",
"person_id": "STU-2024-0842",
"label": "Juan Dela Cruz",
"metadata": { "cohort": "A", "department": "Engineering" }
}'
Response
{
"ok": true,
"template_id": "tpl_9a8b7c6d5e4f3a2b",
"person_id": "STU-2024-0842",
"quality_score": 0.94,
"created_at": "2026-10-08T13:42:11+08:00"
}
Quality matters. quality_score is a 0–1 confidence that the captured image is suitable for matching. Anything below 0.7 should be re-enrolled with a better photo (better lighting, straight-on angle, no sunglasses/masks).
Verify a face
Compare a live captured face against one or more stored templates. Includes server-side liveness detection — spoofing attempts (photos, videos, masks) are rejected.
Request body
| Field | Type | Required | Description |
|---|---|---|---|
image |
string | required | Base64-encoded live capture. Same size limits as enroll. |
person_id |
string | optional | If set, matches only against this person's templates (1:1). If omitted, searches all templates in your org (1:N). |
threshold |
float | optional | Match confidence threshold, 0.0–1.0. Default 0.75. Higher = stricter. |
liveness |
bool | optional | Enable liveness detection. Default true. Do not disable in production. |
curl -X POST https://api.facelearn.io/v1/match-face \
-H "Authorization: Bearer $FACELEARN_API_KEY" \
-H "Content-Type: application/json" \
-d '{
"image": "'$(base64 -w0 capture.jpg)'",
"person_id": "STU-2024-0842"
}'
Response — match found
{
"ok": true,
"matched": true,
"person_id": "STU-2024-0842",
"confidence": 0.992,
"liveness_passed": true,
"processing_ms": 187
}
Response — no match
{
"ok": true,
"matched": false,
"confidence": 0.41,
"liveness_passed": true
}
Response — liveness failed (spoofing attempt)
{
"ok": false,
"error": "liveness_failed",
"message": "The submitted image does not appear to be a live capture.",
"reason": "no_depth_cues"
}
Always check both fields. A response with "matched": true but "liveness_passed": false means the face looked right but the server suspects a spoof. Never treat that as a valid identity.
Record attendance
Write a check-in event to a person's academic record. Typically called right after a successful /match-face response.
Request body
| Field | Type | Required | Description |
|---|---|---|---|
person_id |
string | required | Who is checking in. |
session_id |
string | required | Your internal session identifier. |
status |
string | optional | present (default) · late · excused · absent. |
occurred_at |
string | optional | ISO 8601 timestamp. Defaults to server time. Useful for offline-queued check-ins. |
metadata |
object | optional | Any extra JSON to attach to the record. |
curl -X POST https://api.facelearn.io/v1/attendance \
-H "Authorization: Bearer $FACELEARN_API_KEY" \
-H "Content-Type: application/json" \
-d '{
"person_id": "STU-2024-0842",
"session_id": "COHORT-A-2026-10-08-MORNING",
"status": "present"
}'
Response
{
"ok": true,
"attendance_id": "att_3f8e2a1b4c5d6e7f",
"person_id": "STU-2024-0842",
"session_id": "COHORT-A-2026-10-08-MORNING",
"status": "present",
"occurred_at": "2026-10-08T08:02:14+08:00"
}
Idempotent by design. Submitting the same person_id + session_id twice returns the original record — not a duplicate. Safe to retry on network errors.
Today's stats
Returns aggregate check-in statistics for the current day (in your org's timezone).
Query parameters
| Field | Type | Required | Description |
|---|---|---|---|
session_id |
string | optional | Scope to a single session. Omit for org-wide totals. |
curl https://api.facelearn.io/v1/stats_today?session_id=COHORT-A-2026-10-08-MORNING \
-H "Authorization: Bearer $FACELEARN_API_KEY"
Response
{
"ok": true,
"date": "2026-10-08",
"session_id": "COHORT-A-2026-10-08-MORNING",
"enrolled": 42,
"present": 38,
"late": 2,
"absent": 2,
"attendance_rate": 0.952
}
Webhooks
Register a webhook URL in your dashboard and we'll POST a signed JSON payload whenever key events occur.
| Event | When it fires |
|---|---|
face.enrolled | A new template is successfully created |
face.matched | A verification succeeds above the threshold |
face.spoof_detected | Liveness detection rejects a capture |
attendance.recorded | A check-in is written to a record |
Every webhook includes an X-FaceLearn-Signature header — an HMAC-SHA256 of the raw body using your webhook secret:
// Verify webhook authenticity
$payload = file_get_contents('php://input');
$signature = $_SERVER['HTTP_X_FACELEARN_SIGNATURE'] ?? '';
$expected = hash_hmac('sha256', $payload, 'your_webhook_secret');
if (!hash_equals($expected, $signature)) {
http_response_code(401);
exit;
}
$event = json_decode($payload, true);
// handle $event['type'] ...
Always verify the signature. Never trust a webhook body without checking the HMAC first — otherwise anyone can POST fake events to your endpoint.
SDKs & libraries
Official SDKs are coming soon. In the meantime, the API is plain JSON over HTTPS — any HTTP client will work.
// Enroll a face
$ch = curl_init('https://api.facelearn.io/v1/enroll');
curl_setopt_array($ch, [
CURLOPT_POST => true,
CURLOPT_RETURNTRANSFER => true,
CURLOPT_HTTPHEADER => [
'Authorization: Bearer ' . getenv('FACELEARN_API_KEY'),
'Content-Type: application/json',
],
CURLOPT_POSTFIELDS => json_encode([
'image' => base64_encode(file_get_contents('face.jpg')),
'person_id' => 'STU-2024-0842',
]),
]);
$response = json_decode(curl_exec($ch), true);
curl_close($ch);
// Enroll a face
const res = await fetch('https://api.facelearn.io/v1/enroll', {
method: 'POST',
headers: {
'Authorization': `Bearer ${process.env.FACELEARN_API_KEY}`,
'Content-Type': 'application/json',
},
body: JSON.stringify({
image: imageBase64,
person_id: 'STU-2024-0842',
}),
});
const data = await res.json();
import os, base64, requests
with open("face.jpg", "rb") as f:
image_b64 = base64.b64encode(f.read()).decode()
res = requests.post(
"https://api.facelearn.io/v1/enroll",
headers={"Authorization": f"Bearer {os.environ['FACELEARN_API_KEY']}"},
json={"image": image_b64, "person_id": "STU-2024-0842"},
)
data = res.json()
Changelog
| Version | Date | Changes |
|---|---|---|
v1.0 |
Oct 2026 | Initial public release — enroll, match-face, attendance, stats_today |
Versioning. We follow URL-based versioning. Breaking changes ship under a new major version (/v2/). Non-breaking additions appear in the current version without notice.