11FaceID11FaceIDIdentity Verification

Developer reference

11FaceID API

A small REST surface for enrollment, 1:1 identity verification, secure QR token lifecycle, and audit history. Public fields use verificationImage, spoofCheck, matchConfidence, and verificationThreshold. Integrations must migrate to these names; legacy names are not available on this versioned API. Requests and responses are JSON. No facial images are stored — only encrypted feature templates.

/api/v1

Every endpoint below is versioned under /api/v1. Send your key as Authorization: Bearer <key>; the organization is derived from the key, so there is never a tenant id to pass.

Machine-readable spec: /api/v1/openapi.json

Tokens & the browser

There is no token exchange in this API — your key is the credential, sent directly on every call. But your key must never reach a browser, and for the cases where a browser has to talk to 11FaceID, the API hands you a second, deliberately weaker credential to use instead.

Never put an API key in client-side code.

A key in browser JavaScript, a mobile bundle, or a public repo is readable by anyone who looks, and it carries every scope you granted it for your whole organization. Keys belong on your server.

Your backend → 11FaceID

Your API key, sent as a Bearer header. It never leaves your infrastructure.

Authorization: Bearer sk_live_…

An end user's browser → 11FaceID

A signed token in the URL, minted by your backend with your key. It unlocks one person's verification and nothing else, so it is safe to put in a page.

/verify/<signed-token>

How you get one

Enrolling already returns the first token, as verifyUrl. Render it as a QR code, email it, or put it behind your own domain. When the person opens it, verification runs with the token and no key at all.

Token lifetime

24 hours from issue. Re-issuing increments the version and kills every earlier token for that person; revoking takes effect immediately, checked live on every verification. Session and project links follow the same rule — the live state in the database is the real gate, not the token.

Scopes

A key only reaches the endpoints its scopes cover. Create each key with the narrowest set that does the job — a check-in kiosk rarely needs more than identities:write and verify. A call outside a key's scopes returns 403 with code insufficient_scope.

  • identities:read

    List and read enrolled identities and their QR status

  • identities:write

    Enroll, update, delete identities; revoke/reissue QR tokens; send invites

  • verify

    Run 1:1 verification and 1:N search

  • sessions:read

    List Session Assurance sessions and read their state

  • sessions:write

    Start, tick, re-verify and end Session Assurance sessions

  • datasets:read

    List face datasets and their members

  • datasets:write

    Create, edit and delete face datasets and their members

  • settings:read

    Read use cases, face config, limits, policies and project links

  • settings:write

    Change use cases, face config, policies, project links and calibration

  • audit:read

    Read the audit trail, verification history and dashboard stats

Getting started

GET/api/v1/whoami

Confirm a key is live and discover what it can do — the organization it belongs to, the current plan, and the expanded list of scopes. Has no side effects, so it is the safe first call from a new integration.

Requires Authorization: Bearer <api key> · Scope: Any valid key

Response

{
  "tenantId": "tnt_event_northwind",
  "tenantName": "Northwind Events",
  "plan": "starter",
  "authKind": "api_key",
  "actor": "key:Kiosk – lobby",
  "role": "editor",
  "scopes": ["identities:read", "identities:write", "verify"]
}

Identities & enrollment

POST/api/v1/enroll

Enroll an identity for your organization. The captured face is processed in memory, a feature template is extracted and encrypted, and only the encrypted template is stored — the image is discarded. Issues the first secure QR token.

Requires Authorization: Bearer <api key> · Scope: identities:write

Request

{
  "useCase": "event",
  "fullName": "Amelia Stone",
  "phone": "+1 555 0100",
  "email": "amelia@example.com",
  "fields": { "ticketId": "TKT-4821", "eventName": "Northwind Summit" },
  "faceImage": "data:image/jpeg;base64,…"   // processed then discarded
}

Response

{
  "id": "FID-A1B2C3",
  "status": "pending",
  "verifyUrl": "/verify/<signed-token>",
  "sessionUrl": "/session-join/<signed-token>",   // Session use cases only
  "qr": { "version": 1, "status": "active", "expiresAt": "…" },
  "registration": { "id": "FID-A1B2C3", "faceTemplate": { "dim": 512, "alg": "AES-256-GCM" } }
}
GET/api/v1/registrations

List enrolled identities, optionally narrowed to one use case, status, or verified state. Returns profiles only — never template material.

Requires Authorization: Bearer <api key> · Scope: identities:read

Request

?useCase=event&status=pending&verified=false   // all optional

Response

{
  "count": 42,
  "registrations": [ { "id": "FID-A1B2C3", "fullName": "Amelia Stone", "status": "checked_in" } ],
  "emailStatus": { "FID-A1B2C3": "email_sent" }
}
GET/api/v1/registrations/{id}

Fetch one enrolled identity together with its QR token history and audit trail. Returns template metadata only (dimensionality and algorithm), never the raw template or any image.

Requires Authorization: Bearer <api key> · Scope: identities:read

Response

{
  "registration": { "id": "FID-A1B2C3", "fullName": "Amelia Stone",
                    "faceTemplate": { "dim": 512, "alg": "AES-256-GCM" } },
  "qrTokens": [ { "version": 2, "status": "active", "expiresAt": "…" } ],
  "logs": [ { "type": "verify", "decision": "approved", "timestamp": "…" } ]
}
PATCH/api/v1/registrations/{id}

Edit contact details and/or re-enroll the face. Organization and use case are immutable. Re-enrolling supersedes the outstanding QR token, because it points at a template that no longer exists, and emails the new one.

Requires Authorization: Bearer <api key> · Scope: identities:write · Write access

Request

{
  "fullName": "Amelia Stone-Reid",
  "fields": { "ticketId": "TKT-5000" },
  "faceImage": "data:image/jpeg;base64,…"   // optional — re-enrolls the face
}

Response

{
  "registration": { "id": "FID-A1B2C3", "fullName": "Amelia Stone-Reid" },
  "qr": { "version": 3, "status": "active" },   // present only if re-enrolled
  "reenrolled": true
}
DELETE/api/v1/registrations/{id}

Destroy the registration, its encrypted template, and its QR tokens. Irreversible; the audit trail is retained.

Requires Authorization: Bearer <api key> · Scope: identities:write · Write access

Response

{ "id": "FID-A1B2C3", "deleted": true }
POST/api/v1/registrations/{id}/qr-link

Get a shareable verification link for an identity, issuing a fresh token only if none is currently usable.

Requires Authorization: Bearer <api key> · Scope: identities:read

Response

{ "ok": true, "verifyUrl": "/verify/<signed-token>", "expiresAt": "…", "issued": false }
POST/api/v1/registrations/{id}/send-email

Re-send the person's email with their QR code, use case and its open period — for the common case where the first one never arrived. For a Session use case the email carries the use case's Session link instead of the verification link (`link` in the response says which was sent).

Requires Authorization: Bearer <api key> · Scope: identities:write · Write access

Response

{ "ok": true, "sent": true, "link": "verify" }   // "session" for a Session use case

Verification

POST/api/v1/verify

Run a 1:1 verification. Supply the secure QR token (preferred, and no credential needed) or a registration id with a key holding the verify scope. The live face is processed in memory, checked for spoofing, and the live template is compared against the stored encrypted template. Expired, revoked and cross-organization attempts are blocked and audit-logged.

Requires Authorization: Bearer <api key> · Scope: verify

Request

{
  "token": "<signed-qr-token>",                   // no credential needed with a token
  "verificationImage": "data:image/jpeg;base64,…" // processed then discarded
}
// or, with an API key holding `verify`:
{ "id": "FID-A1B2C3", "verificationImage": "data:image/jpeg;base64,…" }

Response

{
  "spoofCheck": "pass",
  "face_match": "pass",
  "matchConfidence": 0.91,
  "verificationThreshold": 0.75,
  "decision": "approved",
  "reason": "Live face matched the enrolled template.",
  "timestamp": "…",
  "tenant_id": "tnt_event_northwind",
  "registration_id": "FID-A1B2C3"
}

Secure QR tokens

GET/api/v1/qr/resolve

Resolve a secure QR token to its current state (active, expired, revoked, reissued, invalid) and the profile behind it. Powers the hosted verification page; no credential is needed because the token's signature is the proof.

No credential required — the signed QR token is the proof

Request

?token=<signed-qr-token>

Response

{
  "status": "active",            // active | expired | revoked | reissued | invalid
  "usable": true,
  "qr": { "version": 2, "status": "active", "expiresAt": "…" },
  "registration": { "id": "FID-A1B2C3", "fullName": "Amelia Stone" },
  "tenant": { "id": "tnt_event_northwind", "name": "Northwind Events", "type": "event" }
}
POST/api/v1/qr/revoke

Revoke the active secure QR token for a registration. Subsequent verification attempts with that token are blocked and logged.

Requires Authorization: Bearer <api key> · Scope: identities:write · Write access

Request

{ "registrationId": "FID-A1B2C3" }

Response

{ "ok": true, "registrationId": "FID-A1B2C3", "status": "revoked" }
POST/api/v1/qr/reissue

Issue a fresh secure QR token, superseding any prior active token (its version increments). Returns the new verify URL.

Requires Authorization: Bearer <api key> · Scope: identities:write · Write access

Request

{ "registrationId": "FID-A1B2C3" }

Response

{
  "ok": true,
  "registrationId": "FID-A1B2C3",
  "verifyUrl": "/verify/<new-signed-token>",
  "qr": { "version": 2, "status": "active" }
}

Face datasets

GET/api/v1/face-datasets

List your face datasets and how many identities each one holds.

Requires Authorization: Bearer <api key> · Scope: datasets:read

Response

{ "datasets": [ { "id": "fds_…", "name": "Staff", "memberCount": 24 } ] }
POST/api/v1/face-datasets

Create a face dataset — a named group of enrolled identities.

Requires Authorization: Bearer <api key> · Scope: datasets:write · Write access

Request

{ "name": "Staff" }

Response

{ "ok": true, "dataset": { "id": "<uuid>", "tenantId": "tnt_event_northwind",
  "name": "Staff", "createdAt": "…", "createdBy": null, "memberCount": 0 } }
GET/api/v1/face-datasets/{id}

Fetch one dataset together with its members.

Requires Authorization: Bearer <api key> · Scope: datasets:read

Response

{ "dataset": { "id": "fds_…", "name": "Staff" },
  "members": [ { "registrationId": "FID-A1B2C3", "fullName": "Amelia Stone" } ] }
PATCH/api/v1/face-datasets/{id}

Rename a dataset.

Requires Authorization: Bearer <api key> · Scope: datasets:write · Write access

Request

{ "name": "Staff — 2026" }

Response

{ "ok": true }
DELETE/api/v1/face-datasets/{id}

Delete a dataset. The identities in it are untouched; only the grouping goes away.

Requires Authorization: Bearer <api key> · Scope: datasets:write · Write access

Response

{ "ok": true, "id": "fds_…" }
POST/api/v1/face-datasets/{id}/members

Add people to a dataset — link identities that are already enrolled (e.g. a subset of a bigger dataset), or enroll a new face.

Requires Authorization: Bearer <api key> · Scope: datasets:write · Write access

Request

// Link people who are already enrolled (no new photo, no extra quota):
{ "registrationIds": ["FID-A1B2C3", "FID-D4E5F6"] }  // or { "registrationId": "FID-A1B2C3" }

// …or enroll a new face straight into the dataset:
{ "fullName": "Amelia Stone", "phone": "+66812345678", "email": "amelia@example.com",
  "faceImage": "data:image/jpeg;base64,…" }

Response

// link:   { "ok": true, "added": 2, "alreadyMembers": 0 }
// enroll: { "ok": true, "registrationId": "FID-A1B2C3" }
DELETE/api/v1/face-datasets/{id}/members/{registrationId}

Remove an identity from a dataset without deleting the registration.

Requires Authorization: Bearer <api key> · Scope: datasets:write · Write access

Response

{ "ok": true }
GET/api/v1/face-datasets/{id}/use-cases

Requires Authorization: Bearer <api key> · Scope: datasets:read

Response

{ "useCases": [ { "id": "<uuid>", "key": "math_10", "label": "Math — Grade 10",
  "closedAt": null, "linked": true } ] }
PUT/api/v1/face-datasets/{id}/use-cases

Requires Authorization: Bearer <api key> · Scope: settings:write · Write access

Request

// The full set of use cases that should verify against this dataset.
// Other datasets' links on those use cases are untouched. [] unlinks all.
{ "useCaseIds": ["<uuid>", "<uuid>"] }

Response

{ "ok": true, "linked": 2, "added": 1, "removed": 0 }

Session Assurance

Continuous, policy-driven assurance throughout a session. These endpoints are available only when the module is enabled; otherwise they return 403. Organizations can define multiple Verification Policies and assign different policies to different workflows without changing the verification engine.

GET/api/v1/session-policies

List Verification Policies and the available presets (Online Auction, Online Examination, Online Training, Remote Attendance). A preset is a recommended starting configuration only.

Requires Authorization: Bearer <api key> · Scope: settings:read

Response

{
  "policies": [
    { "id": "pol_online_auction", "name": "Online Auction / Bidder Monitoring", "custom": false },
    { "id": "pol_…", "name": "Exam — Room 1", "custom": true, "libraryVisible": false, "linkedUseCases": [ { "key": "exam", "label": "Exam" } ] }
  ],
  "presets": [ { "key": "online_auction", "name": "Online Auction / Bidder Monitoring" } ]
}
POST/api/v1/session-policies

Create a Verification Policy (Save as Custom Policy / Duplicate).

Requires Authorization: Bearer <api key> · Scope: settings:write · Write access

Request

{
  "name": "Exam — Final",
  "initialVerification": { "qrRequired": true, "spoofCheckRequired": true, "retryLimit": 3, "retryIntervalSecs": 5, "faceQualityRequired": true },
  "verification": { "intervalSecs": 45, "randomized": true, "maxConsecutiveMismatches": 3, "maxMissedVerifications": 2 },
  "camera": { "disconnectedTimeoutSecs": 10, "blockedDetection": true, "unavailableAction": "require_reverify" },
  "response": { "onVerificationFailure": "warn", "onMaxConsecutiveMismatches": "terminate", "onMaxMissedVerifications": "require_reverify" },
  "auditLevel": "full_history"
}

Response

{ "ok": true, "policy": { "id": "pol_…", "custom": true } }
PUT/api/v1/session-policies/{id}

Edit an existing Verification Policy.

Requires Authorization: Bearer <api key> · Scope: settings:write · Write access

Request

{ "verification": { "intervalSecs": 60 } }

Response

{ "ok": true, "policy": { "id": "pol_…", "updatedAt": "…" } }
DELETE/api/v1/session-policies/{id}

Delete a Verification Policy. Blocked (409) while referenced by a live session.

Requires Authorization: Bearer <api key> · Scope: settings:write · Write access

Response

{ "ok": true, "id": "pol_…", "status": "deleted" }
GET/api/v1/sessions

List assurance sessions for your organization and their current state.

Requires Authorization: Bearer <api key> · Scope: sessions:read

Response

{ "count": 1, "sessions": [ { "id": "ses_…", "policyName": "Online Auction / Bidder Monitoring", "status": "active" } ] }
POST/api/v1/sessions/start

Start a Session Assurance session for an enrolled identity under a selected policy. Records an initial 1:1 verification and returns a hosted session URL you can send the person to; pass returnUrl to bring them back to your own app afterwards.

Requires Authorization: Bearer <api key> · Scope: sessions:write

Request

{ "registrationId": "FID-D4E5F6", "policyId": "pol_online_auction", "returnUrl": "https://your.app/done" }

Response

{ "ok": true, "session": { "id": "ses_…", "status": "active", "totalChecks": 1 },
  "sessionUrl": "https://…/session/<signed-token>" }
POST/api/v1/sessions/check

Run one periodic identity verification. Frames are processed in memory only. Optional flags simulate a missed check or a camera problem; the policy decides the response.

Requires Authorization: Bearer <api key> · Scope: sessions:read

Request

{ "sessionId": "ses_…", "kind": "scheduled" }   // or { "missed": true } / { "camera": "lost" }

Response

{ "ok": true, "session": { "status": "warning", "failedChecks": 1, "consecutiveMismatches": 1 } }
POST/api/v1/sessions/reverify

Perform an immediate re-verification. On success the session returns to active; on failure it escalates per the policy.

Requires Authorization: Bearer <api key> · Scope: sessions:write

Request

{ "sessionId": "ses_…", "verificationImage": "data:image/jpeg;base64,…" }

Response

{ "ok": true, "session": { "status": "active" } }
POST/api/v1/sessions/end

End a session and write the closing audit event. A session ended before its policy's time limit is recorded as expired; one that ran its full length as completed.

Requires Authorization: Bearer <api key> · Scope: sessions:write

Request

{ "sessionId": "ses_…" }

Response

{ "ok": true, "session": { "status": "expired", "endReason": "ended_early_by_admin", "endedAt": "…" } }
POST/api/v1/sessions/tick

Advance every live session by one scheduler step. Use this when you drive the clock yourself — from your own cron, say — instead of relying on the browser session page.

Requires Authorization: Bearer <api key> · Scope: sessions:write

Response

{ "ok": true, "advanced": 3, "sessions": [ { "id": "ses_…", "status": "active" } ] }
POST/api/v1/sessions/{id}/link

Re-issue the hosted session URL for a session that is still live, for when the original link was lost.

Requires Authorization: Bearer <api key> · Scope: sessions:write

Response

{ "ok": true, "sessionUrl": "https://…/session/<signed-token>" }

Configuration

GET/api/v1/use-cases

List your use cases and the custom fields each one collects at enrollment.

Requires Authorization: Bearer <api key> · Scope: settings:read

Response

{ "useCases": [ { "key": "event", "label": "Event check-in", "fields": [ { "key": "ticketId", "label": "Ticket ID" } ] } ] }
POST/api/v1/use-cases

Create a use case with its own set of enrollment fields.

Requires Authorization: Bearer <api key> · Scope: settings:write · Write access

Request

{ "key": "exam", "label": "Exam check-in", "fields": [ { "key": "seatNo", "label": "Seat number" } ] }

Response

{ "ok": true, "useCase": { "key": "exam" }, "policyId": null }
PATCH/api/v1/use-cases/{id}

Edit a use case — its label, fields, schedule, capacity, or the return URL its hosted links redirect to.

Requires Authorization: Bearer <api key> · Scope: settings:write · Write access

Request

{ "label": "Exam check-in (2026)", "returnUrl": "https://your.app/done" }

Response

{ "ok": true, "useCase": { "key": "exam" } }
DELETE/api/v1/use-cases/{id}

Delete a use case. Blocked while identities are still enrolled under it.

Requires Authorization: Bearer <api key> · Scope: settings:write · Write access

Response

{ "ok": true }
GET/api/v1/settings/face-config

Read the organization's detector-confidence floor for enrollment. Null means the engine default applies.

Requires Authorization: Bearer <api key> · Scope: settings:read

Response

{ "enrollMinDetScore": 0.8, "isCustom": false,
  "platformDefault": 0.8, "range": { "min": 0.5, "max": 0.9 } }
PATCH/api/v1/settings/face-config

Raise or lower the detector-confidence floor required to accept an enrollment photo. Send null to go back to the engine default.

Requires Authorization: Bearer <api key> · Scope: settings:write · Write access

Request

{ "minDetScore": 0.7 }   // null resets to the platform default

Response

{ "enrollMinDetScore": 0.7, "isCustom": true,
  "platformDefault": 0.8, "range": { "min": 0.5, "max": 0.9 } }
POST/api/v1/calibration

Compare two images through the live engine and get the raw similarity back, without enrolling anything. Use it to pick a match threshold against your own photos rather than guessing.

Requires Authorization: Bearer <api key> · Scope: settings:write · Write access

Request

{ "image": "data:image/jpeg;base64,…" }

Response

{
  "detect": { "accepted": true, "reason": "OK", "numFaces": 1, "score": 0.9, "bbox": [278, 188, 811, 927] },
  "spoofCheck": { "probability": 0.94, "isLive": true },
  "enrollMinDetScore": 0.8,
  "wouldEnroll": true
}
GET/api/v1/limits

Current plan limits and usage — enrolled faces, team seats, verifications this month.

Requires Authorization: Bearer <api key> · Scope: settings:read

Response

{
  "limits": { "maxVerificationsPerMonth": 1000, "maxEnrolledFaces": null, "maxUsers": 5 },
  "usage": { "verificationsThisMonth": 2, "enrolledFaces": 9, "users": 1 },
  "plan": "starter",
  "trialEndsAt": null
}
GET/api/v1/tenants

Your organization's profile as the API sees it.

Requires Authorization: Bearer <api key> · Scope: settings:read

Response

{ "count": 1, "tenants": [
  { "id": "tnt_event_northwind", "name": "Northwind Events", "type": "event", "defaultUseCase": "event" }
] }
GET/api/v1/project-links

Get the shareable self-enrollment and self-verification links for a use case, plus whether they are currently open, closed, or full.

Requires Authorization: Bearer <api key> · Scope: settings:read

Request

?useCase=event

Response

{
  "enrollPath": "/enroll-link/<signed-token>",
  "verifyPath": "/verify-link/<signed-token>",
  "status": { "open": true, "full": false }
}
POST/api/v1/project-links

Email someone the self-enrollment link for a use case.

Requires Authorization: Bearer <api key> · Scope: settings:write · Write access

Request

{ "useCase": "event", "email": "amelia@example.com" }

Response

{ "ok": true }

Audit & reporting

GET/api/v1/logs

The append-only audit trail for your organization, newest first and capped per request.

Requires Authorization: Bearer <api key> · Scope: audit:read

Request

?registrationId=FID-A1B2C3&limit=200   // both optional

Response

{ "count": 200, "logs": [ { "type": "enroll", "reason": "…", "timestamp": "…" } ] }
GET/api/v1/verifications

Verification history — successful, failed, and blocked attempts, with the reason each one was blocked.

Requires Authorization: Bearer <api key> · Scope: audit:read

Request

?registrationId=FID-A1B2C3&limit=200   // both optional

Response

{
  "count": 42,
  "verifications": [
    { "type": "verify", "decision": "approved", "matchConfidence": 0.91, "timestamp": "…" },
    { "type": "verify_blocked", "block": "qr_expired", "timestamp": "…" }
  ]
}
GET/api/v1/dashboard

The numbers behind the portal dashboard: enrollment and verification totals, approval rate, session stats, and recent activity.

Requires Authorization: Bearer <api key> · Scope: audit:read

Response

{
  "stats": { "registrations": 812, "verifications": 4310, "approvalRate": 0.97 },
  "sessionStats": { "active": 3, "flagged": 1 },
  "recent": [ { "type": "verify", "decision": "approved", "timestamp": "…" } ],
  "activity": [ … ], "activityTotal": 512
}