REST API
Call FaceGate directly over HTTP if you would rather not use an SDK. Every endpoint the SDKs use is documented here, with the exact method, path, request body, and response shape pulled from the running server.
The whole face flow is three calls: create a liveness session, run the AWS Liveness widget on the client, then POST /v1/verify with the session id. The server fetches the verified reference image from AWS itself — your client never uploads a face image to the verify endpoint.
Base URL
https://api.facegate.aiThere is no version prefix on the host. The version lives in the path (/v1/...).
Authentication
Every endpoint requires an API key except /v1/health, the demo routes, and the Stripe webhook. Send the key one of two ways:
# Preferred — Authorization header
curl https://api.facegate.ai/v1/users \
-H "Authorization: Bearer fg_live_your_key_here"
# Alternative — x-api-key header
curl https://api.facegate.ai/v1/users \
-H "x-api-key: fg_live_your_key_here"Keys come in two flavors:
| Prefix | Use |
|---|---|
fg_test_… | Development. Safe to put in non-production builds. |
fg_live_… | Production traffic and billing. |
Each key is scoped to one tenant. The server resolves the tenant from the key, so you never pass a tenant id yourself. A missing or malformed key returns 401; a suspended tenant returns 403.
Optional request headers
Some endpoints read these to bind sessions to a device and improve risk scoring. All are optional.
| Header | Purpose |
|---|---|
x-device-fingerprint | Hex SHA-256 (64 chars). Binds the issued JWT to the device. |
x-device-id | Stable device identifier. If omitted, the server derives one. |
user-agent | Recorded on the audit trail and used for device-type analytics. |
Endpoints
All 18 route groups, grouped by what they do.
Core authentication flow
| Method | Path | Purpose |
|---|---|---|
GET | /v1/health | Liveness/readiness probe. No auth. |
POST | /v1/liveness/session | Create an AWS Face Liveness session. |
GET | /v1/liveness/result/:sessionId | Read a liveness result directly. |
POST | /v1/verify | Verify a face from a liveness session. Returns a JWT. |
POST | /v1/match | Match-only (no liveness). Returns the matched user. |
POST | /v1/enroll | Enroll a new user's face. |
POST | /v1/detect-attributes | Detect face attributes from an image. |
Sessions
| Method | Path | Purpose |
|---|---|---|
POST | /v1/session | Exchange a verification token for a provider session. |
POST | /v1/session/:id/revoke | Revoke a session immediately. |
GET | /v1/sessions/:id/risk-score | Read the current risk score for a session. |
Users
| Method | Path | Purpose |
|---|---|---|
GET | /v1/users | List enrolled users for the tenant. |
GET | /v1/users/:id | Get a user profile and enrollment status. |
DELETE | /v1/users/:id | Delete a user and all face data. |
PATCH | /v1/users/:id/status | Set a user's status (active / suspended / locked). |
POST | /v1/users/:id/re-enroll | Replace a user's face enrollments. |
Consent & compliance
| Method | Path | Purpose |
|---|---|---|
POST | /v1/consent | Record a biometric consent. Required before enroll. |
DELETE | /v1/consent/:id | Revoke consent (and delete the linked user). |
Policy
| Method | Path | Purpose |
|---|---|---|
POST | /v1/policy/rotation | Set the face-rotation policy. |
POST | /v1/policy/risk | Set risk thresholds and lockout rules. |
POST | /v1/policy/continuous | Set continuous re-verification settings. |
Cross-device handoff
| Method | Path | Purpose |
|---|---|---|
POST | /v1/handoff | Start a handoff session (QR / phone). |
GET | /v1/handoff/:token/status | Poll a handoff session's status. |
POST | /v1/handoff/:token/complete | Mark a handoff complete (called by the phone). |
Risk, signals & threat intelligence
| Method | Path | Purpose |
|---|---|---|
POST | /v1/signals | Beacon a batch of client telemetry signals. |
GET | /v1/threat-intel | Read learned attack patterns for the tenant. |
POST | /v1/sync | Sync offline verifications for server re-check. |
Analytics & billing
| Method | Path | Purpose |
|---|---|---|
GET | /v1/analytics/overview | Aggregate auth stats over a period. |
GET | /v1/analytics/devices | Success rate broken down by device type. |
GET | /v1/analytics/failures | Top error codes and retry stats. |
GET | /v1/billing/status | Current plan, MAU count, and overage. |
GET | /v1/usage/summary | Verify/enroll counts for a billing period. |
GET | /v1/usage/daily | Daily usage counts across a date range. |
POST | /v1/billing/checkout | Create a Stripe checkout session. |
POST | /v1/billing/portal | Create a Stripe billing portal session. |
POST | /v1/webhooks/stripe | Stripe webhook receiver. No auth (Stripe-signed). |
Demo (unauthenticated)
The /demo routes power the public live demo on facegate.ai. They run against a shared demo tenant and require no API key.
| Method | Path | Purpose |
|---|---|---|
GET | /demo | Serve the demo page. |
POST | /demo/liveness-session | Create a liveness session for the demo tenant. |
POST | /demo/enroll | Enroll into the demo collection. |
POST | /demo/verify | Verify against the demo collection. |
GET | /demo/preflight | Demo health pre-check. |
Health
The only endpoint that needs no API key. Use it for uptime checks and load-balancer probes.
curl https://api.facegate.ai/v1/healthReturns 200 when healthy, 503 while starting up or when the database is unreachable.
{
"status": "healthy",
"checks": {
"db": { "status": "ok", "latencyMs": 4 },
"rekognition": { "status": "ok", "latencyMs": 120 }
},
"version": "0.1.0",
"uptime_seconds": 8421
}Liveness session
Step one of every verify and enroll. Create a session, then drive the AWS Face Liveness widget on the client with the returned credentials. The session id is what you later hand to /v1/verify or /v1/enroll.
curl -X POST https://api.facegate.ai/v1/liveness/session \
-H "Authorization: Bearer fg_live_your_key_here"{
"session_id": "a1b2c3d4-…",
"credentials": { "accessKeyId": "…", "secretAccessKey": "…", "sessionToken": "…" },
"region": "us-east-1"
}You usually do not call GET /v1/liveness/result/:sessionId directly — /v1/verify reads the result for you. It exists for debugging:
curl https://api.facegate.ai/v1/liveness/result/a1b2c3d4-… \
-H "Authorization: Bearer fg_live_your_key_here"{ "is_live": true, "confidence": 98.7 }Verify
The main authentication call. Pass the liveness_session_id from the step above. The server pulls the AWS-verified reference image, confirms liveness (confidence ≥ 90), searches the tenant's face collection, runs risk scoring, and — on a match — mints a signed JWT bound to the device.
curl -X POST https://api.facegate.ai/v1/verify \
-H "Authorization: Bearer fg_live_your_key_here" \
-H "Content-Type: application/json" \
-H "x-device-fingerprint: 9f86d081884c7d659a2feaa0c55ad015a3bf4f1b2b0b822cd15d6c15b0f00a08" \
-d '{ "liveness_session_id": "a1b2c3d4-…" }'Request body:
| Field | Type | Required | Notes |
|---|---|---|---|
liveness_session_id | string | yes | The session id from /v1/liveness/session. |
On a successful match:
{
"matched": true,
"confidence": 99.4,
"verification_token": "eyJhbGciOi…",
"refresh_token": "eyJhbGciOi…",
"session_id": "sess_…",
"device_id": "dev_…",
"user": {
"id": "user_…",
"name": "Ada Guard",
"role": "guard",
"metadata": {}
},
"risk": {
"score": 0.08,
"level": "low",
"factors": [],
"step_up_required": false,
"step_up_type": null
},
"re_enrollment_required": false
}When no enrolled face matches, the call still returns 200 with matched: false and a null token. A liveness failure returns 403; an already-consumed session returns 400. The verification_token is the JWT — verify it yourself (Tier 1) or exchange it at /v1/session for a provider session (Tier 2).
Match
Match-only, no liveness. Send a base64 image and get back the matched user. Use this for low-stakes lookups where you do not need a fresh liveness proof or a session token. It does not issue a JWT.
curl -X POST https://api.facegate.ai/v1/match \
-H "Authorization: Bearer fg_live_your_key_here" \
-H "Content-Type: application/json" \
-d '{ "image": "/9j/4AAQSkZJRg…" }'| Field | Type | Required | Notes |
|---|---|---|---|
image | string | yes | Base64-encoded image (JPEG/PNG). |
{
"matched": true,
"confidence": 98.9,
"user": { "id": "user_…", "name": "Ada Guard", "role": "guard", "metadata": {} }
}A 422 NO_FACE_DETECTED means no face was found in the image — reset the camera and try again. An invalid base64 payload returns 400 INVALID_IMAGE_FORMAT.
Enroll
Register a user's face. A consent record is required first — call POST /v1/consent and pass the returned consent_id. You may supply either a liveness_session_id (preferred — the image comes from the AWS-verified session) or a client-captured face_image.
curl -X POST https://api.facegate.ai/v1/enroll \
-H "Authorization: Bearer fg_live_your_key_here" \
-H "Content-Type: application/json" \
-d '{
"consent_id": "c0ffee00-…",
"liveness_session_id": "a1b2c3d4-…",
"name": "Ada Guard",
"role": "guard",
"email": "ada@example.com"
}'| Field | Type | Required | Notes |
|---|---|---|---|
name | string | yes | Display name. |
role | string | yes | Role string, surfaced in tokens and /v1/users. |
consent_id | uuid | yes | From POST /v1/consent. Without it you get 400 CONSENT_REQUIRED. |
liveness_session_id | string | one of | Use a verified liveness session as the enrollment image. |
face_image | string | one of | Base64 image instead of a liveness session. |
external_id | string | no | Your own user id, for cross-referencing. |
email | string | no | Stored on the user record. |
phone | string | no | Stored on the user record. |
enrolled_with_glasses | boolean | no | Recorded for appearance tracking. |
metadata | object | no | Arbitrary key/value data. |
At least one of liveness_session_id or face_image must be present. Returns 201:
{
"user_id": "user_…",
"enrollments": [
{ "faceId": "face_…", "angle": "front", "confidence": 99.8 }
]
}Re-enrolling an already-enrolled identity returns 409 USER_ALREADY_ENROLLED. A revoked or unknown consent returns 400.
Detect attributes
Run face-attribute detection on an image without enrolling or matching — useful for client-side quality gating before you start a liveness session.
curl -X POST https://api.facegate.ai/v1/detect-attributes \
-H "Authorization: Bearer fg_live_your_key_here" \
-H "Content-Type: application/json" \
-d '{ "image": "data:image/jpeg;base64,/9j/4AAQSkZJRg…" }'Returns the provider's attribute object (eyes-open, occlusion, sharpness, pose, and so on). 400 No face detected in image if the frame has no face.
Create session
Exchange a verification_token (the JWT from /v1/verify) for a session in your auth provider. Today the jwt provider is implemented; other providers return 501 PROVIDER_NOT_IMPLEMENTED.
curl -X POST https://api.facegate.ai/v1/session \
-H "Authorization: Bearer fg_live_your_key_here" \
-H "Content-Type: application/json" \
-d '{
"verification_token": "eyJhbGciOi…",
"provider": "jwt"
}'| Field | Type | Required | Notes |
|---|---|---|---|
verification_token | string | yes | The JWT returned by /v1/verify. |
provider | enum | no | supabase | auth0 | firebase | jwt. Defaults to jwt. |
{
"token": "eyJhbGciOi…",
"refresh_token": "",
"expires_in": 900,
"user": { "id": "user_…", "name": "Ada Guard", "role": "guard", "metadata": {} },
"provider_session": null
}Revoke a session at any time for instant logout across devices:
curl -X POST https://api.facegate.ai/v1/session/sess_abc/revoke \
-H "Authorization: Bearer fg_live_your_key_here"{ "revoked": true, "session_id": "sess_abc" }Users
List, inspect, delete, and manage enrolled users. All scoped to the tenant behind the API key.
# List
curl https://api.facegate.ai/v1/users \
-H "Authorization: Bearer fg_live_your_key_here"{
"users": [
{
"id": "user_…",
"name": "Ada Guard",
"role": "guard",
"email": "ada@example.com",
"status": "active",
"created_at": "2026-05-01T12:00:00.000Z",
"face_count": 1
}
]
}Get a single user, including enrollment records:
curl https://api.facegate.ai/v1/users/user_abc \
-H "Authorization: Bearer fg_live_your_key_here"Delete a user and all face data. The optional reason query param accepts user_request (default) or tenant_request:
curl -X DELETE "https://api.facegate.ai/v1/users/user_abc?reason=tenant_request" \
-H "Authorization: Bearer fg_live_your_key_here"Set status (active, suspended, or locked):
curl -X PATCH https://api.facegate.ai/v1/users/user_abc/status \
-H "Authorization: Bearer fg_live_your_key_here" \
-H "Content-Type: application/json" \
-d '{ "status": "suspended" }'A missing user returns 404 on any of these.
Consent
Record a biometric consent before enrolling. The server validates the consent_version and consent_text_hash against its registry, so you cannot record consent for text it does not recognize.
curl -X POST https://api.facegate.ai/v1/consent \
-H "Authorization: Bearer fg_live_your_key_here" \
-H "Content-Type: application/json" \
-d '{
"consent_version": "v1",
"consent_text_hash": "sha256-of-the-canonical-text"
}'| Field | Type | Required | Notes |
|---|---|---|---|
consent_version | string | yes | Must match a known version in the registry. |
consent_text_hash | string | yes | Must match the canonical hash for that version. |
user_id | string | no | Link the consent to an existing user. |
{ "consent_id": "c0ffee00-…", "consented_at": "2026-05-01T12:00:00.000Z" }Revoking consent revokes the record and, if a user is linked, deletes that user and their face data:
curl -X DELETE https://api.facegate.ai/v1/consent/c0ffee00-… \
-H "Authorization: Bearer fg_live_your_key_here"{ "revoked": true, "revoked_at": "2026-05-02T09:00:00.000Z", "user_deleted": true }Policy
Three policy endpoints, all POST, all upsert (create or update the tenant's single policy of that type).
POST /v1/policy/rotation — face-rotation cadence:
curl -X POST https://api.facegate.ai/v1/policy/rotation \
-H "Authorization: Bearer fg_live_your_key_here" \
-H "Content-Type: application/json" \
-d '{ "rotation_days": 90, "grace_period_days": 7, "notify_before_days": 14 }'POST /v1/policy/risk — risk thresholds and lockout:
curl -X POST https://api.facegate.ai/v1/policy/risk \
-H "Authorization: Bearer fg_live_your_key_here" \
-H "Content-Type: application/json" \
-d '{ "block_threshold": 0.9, "step_up_threshold": 0.6, "max_failed_attempts": 5, "lockout_duration_minutes": 30 }'POST /v1/policy/continuous — continuous re-verification:
curl -X POST https://api.facegate.ai/v1/policy/continuous \
-H "Authorization: Bearer fg_live_your_key_here" \
-H "Content-Type: application/json" \
-d '{ "re_verify_interval_minutes": 30, "confidence_decay_rate": 0.1, "min_confidence": 85 }'Each returns the saved config and whether it was created or updated:
{ "policy_type": "rotation", "action": "updated", "config": { "rotation_days": 90, "grace_period_days": 7, "notify_before_days": 14 } }Cross-device handoff
For devices without a camera. Create a handoff session, show the returned URL as a QR code, and let the user finish on their phone. Poll the status endpoint (or connect to the WebSocket URL) to know when they are done.
curl -X POST https://api.facegate.ai/v1/handoff \
-H "Authorization: Bearer fg_live_your_key_here" \
-H "Content-Type: application/json" \
-d '{ "mode": "verify" }'| Field | Type | Required | Notes |
|---|---|---|---|
mode | enum | no | verify (default) or enroll. |
{
"handoff_token": "V1StGXR8…",
"verify_url": "https://api.facegate.ai/v1/handoff/V1StGXR8…/verify",
"expires_at": "2026-05-01T12:10:00.000Z",
"ws_url": "wss://api.facegate.ai/v1/handoff/V1StGXR8…/ws"
}Poll the status (returns 410 once the session expires, ten minutes after creation):
curl https://api.facegate.ai/v1/handoff/V1StGXR8…/status \
-H "Authorization: Bearer fg_live_your_key_here"{ "status": "completed", "user_id": "user_…", "session_data": { } }Risk score
Read the live risk score for an active session.
curl https://api.facegate.ai/v1/sessions/sess_abc/risk-score \
-H "Authorization: Bearer fg_live_your_key_here"{
"session_id": "sess_abc",
"risk_score": 0.08,
"confidence": 99.4,
"last_verified_at": "2026-05-01T12:00:00.000Z"
}Signals
Beacon a batch of client-side telemetry signals (verify attempts, retries, errors) for analytics and risk learning. Returns 202 once accepted.
curl -X POST https://api.facegate.ai/v1/signals \
-H "Authorization: Bearer fg_live_your_key_here" \
-H "Content-Type: application/json" \
-d '{
"device_fingerprint": "9f86d081…",
"signals": [
{
"signal_id": "550e8400-e29b-41d4-a716-446655440000",
"source": "sdk-react",
"type": "retry",
"timestamp": 1748000000000,
"data": { "attempt_number": 2 }
}
]
}'Up to 100 signals per request. Each signal_id must be a UUID, and timestamps must be within the last 24 hours (and not more than 60 seconds in the future).
Threat intelligence
Read the attack patterns the system has learned for your tenant — part of FaceGate's collective-immunity model.
curl https://api.facegate.ai/v1/threat-intel \
-H "Authorization: Bearer fg_live_your_key_here"{ "patterns": [] }Offline sync
Upload offline verifications captured by a client running in offline mode. The server logs each one and re-checks the primary frame against the face collection, flagging any that do not match.
curl -X POST https://api.facegate.ai/v1/sync \
-H "Authorization: Bearer fg_live_your_key_here" \
-H "Content-Type: application/json" \
-d '{
"records": [
{
"type": "offline_verify",
"user_id": "user_…",
"confidence": 97.2,
"timestamp": "2026-05-01T11:55:00.000Z",
"frames": ["/9j/4AAQSkZJRg…"]
}
]
}'Up to 50 records per request, each with 1–5 frames.
{
"processed": 1,
"re_verified": [
{ "user_id": "user_…", "timestamp": "2026-05-01T11:55:00.000Z", "server_matched": true, "server_confidence": 98.1, "flagged": false }
]
}Analytics
Three read endpoints, all gated by your plan's analytics feature. The period query param accepts 7d, 30d (default), or 90d.
curl "https://api.facegate.ai/v1/analytics/overview?period=30d" \
-H "Authorization: Bearer fg_live_your_key_here"{
"total_attempts": 1284,
"success_rate": 0.982,
"avg_confidence": 98.7,
"avg_latency_ms": 1340,
"active_users": 96,
"period": "30d"
}GET /v1/analytics/devices breaks success rate down by device type; GET /v1/analytics/failures returns the top error codes and retry stats.
Billing & usage
Read your plan, usage, and overage, and create Stripe checkout or portal sessions.
curl https://api.facegate.ai/v1/billing/status \
-H "Authorization: Bearer fg_live_your_key_here"{
"plan": "pro",
"plan_display_name": "Pro",
"price_monthly": 99,
"mau_count": 412,
"mau_limit": 1000,
"verify_count": 1284,
"enroll_count": 37,
"overage_mau": 0,
"overage_cost": 0
}GET /v1/usage/summary?period=2026-05 returns counts for a billing month; GET /v1/usage/daily?from=…&to=… returns daily counts. POST /v1/billing/checkout ({ "plan": "pro" | "business" }) and POST /v1/billing/portal return Stripe URLs. All billing endpoints return 503 billing_not_configured if Stripe is not set up on the server.
Errors
Errors come back as JSON with an error field, and validation failures add a details array from the schema check.
{ "error": "Validation failed", "details": [ /* zod issues */ ] }| Status | Meaning |
|---|---|
400 | Bad request — validation failed, missing consent, or session already consumed. |
401 | Missing or invalid API key, or invalid verification token. |
403 | Liveness failed, or the tenant account is suspended. |
404 | User, session, or consent record not found. |
409 | Already enrolled. |
410 | Handoff session expired. |
422 | No face detected in the image. |
429 | Usage or rate limit reached. |
500 | Upstream service error (database or face provider). |
501 | Session provider not implemented. |
503 | Service starting, billing not configured, or signal ingestion unavailable. |
Next steps
- Quickstart — the five-minute React + Supabase path.
- React SDK — the
<FaceGate />component. - Supabase adapter — turn a verify token into a Supabase session.
- Security — liveness, anti-spoofing, and how face data is stored.