FaceGateBack to home

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.ai

There 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:

PrefixUse
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.

HeaderPurpose
x-device-fingerprintHex SHA-256 (64 chars). Binds the issued JWT to the device.
x-device-idStable device identifier. If omitted, the server derives one.
user-agentRecorded on the audit trail and used for device-type analytics.

Endpoints

All 18 route groups, grouped by what they do.

Core authentication flow

MethodPathPurpose
GET/v1/healthLiveness/readiness probe. No auth.
POST/v1/liveness/sessionCreate an AWS Face Liveness session.
GET/v1/liveness/result/:sessionIdRead a liveness result directly.
POST/v1/verifyVerify a face from a liveness session. Returns a JWT.
POST/v1/matchMatch-only (no liveness). Returns the matched user.
POST/v1/enrollEnroll a new user's face.
POST/v1/detect-attributesDetect face attributes from an image.

Sessions

MethodPathPurpose
POST/v1/sessionExchange a verification token for a provider session.
POST/v1/session/:id/revokeRevoke a session immediately.
GET/v1/sessions/:id/risk-scoreRead the current risk score for a session.

Users

MethodPathPurpose
GET/v1/usersList enrolled users for the tenant.
GET/v1/users/:idGet a user profile and enrollment status.
DELETE/v1/users/:idDelete a user and all face data.
PATCH/v1/users/:id/statusSet a user's status (active / suspended / locked).
POST/v1/users/:id/re-enrollReplace a user's face enrollments.

Consent & compliance

MethodPathPurpose
POST/v1/consentRecord a biometric consent. Required before enroll.
DELETE/v1/consent/:idRevoke consent (and delete the linked user).

Policy

MethodPathPurpose
POST/v1/policy/rotationSet the face-rotation policy.
POST/v1/policy/riskSet risk thresholds and lockout rules.
POST/v1/policy/continuousSet continuous re-verification settings.

Cross-device handoff

MethodPathPurpose
POST/v1/handoffStart a handoff session (QR / phone).
GET/v1/handoff/:token/statusPoll a handoff session's status.
POST/v1/handoff/:token/completeMark a handoff complete (called by the phone).

Risk, signals & threat intelligence

MethodPathPurpose
POST/v1/signalsBeacon a batch of client telemetry signals.
GET/v1/threat-intelRead learned attack patterns for the tenant.
POST/v1/syncSync offline verifications for server re-check.

Analytics & billing

MethodPathPurpose
GET/v1/analytics/overviewAggregate auth stats over a period.
GET/v1/analytics/devicesSuccess rate broken down by device type.
GET/v1/analytics/failuresTop error codes and retry stats.
GET/v1/billing/statusCurrent plan, MAU count, and overage.
GET/v1/usage/summaryVerify/enroll counts for a billing period.
GET/v1/usage/dailyDaily usage counts across a date range.
POST/v1/billing/checkoutCreate a Stripe checkout session.
POST/v1/billing/portalCreate a Stripe billing portal session.
POST/v1/webhooks/stripeStripe 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.

MethodPathPurpose
GET/demoServe the demo page.
POST/demo/liveness-sessionCreate a liveness session for the demo tenant.
POST/demo/enrollEnroll into the demo collection.
POST/demo/verifyVerify against the demo collection.
GET/demo/preflightDemo 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/health

Returns 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:

FieldTypeRequiredNotes
liveness_session_idstringyesThe 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…" }'
FieldTypeRequiredNotes
imagestringyesBase64-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"
  }'
FieldTypeRequiredNotes
namestringyesDisplay name.
rolestringyesRole string, surfaced in tokens and /v1/users.
consent_iduuidyesFrom POST /v1/consent. Without it you get 400 CONSENT_REQUIRED.
liveness_session_idstringone ofUse a verified liveness session as the enrollment image.
face_imagestringone ofBase64 image instead of a liveness session.
external_idstringnoYour own user id, for cross-referencing.
emailstringnoStored on the user record.
phonestringnoStored on the user record.
enrolled_with_glassesbooleannoRecorded for appearance tracking.
metadataobjectnoArbitrary 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"
  }'
FieldTypeRequiredNotes
verification_tokenstringyesThe JWT returned by /v1/verify.
providerenumnosupabase | 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"
  }'
FieldTypeRequiredNotes
consent_versionstringyesMust match a known version in the registry.
consent_text_hashstringyesMust match the canonical hash for that version.
user_idstringnoLink 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" }'
FieldTypeRequiredNotes
modeenumnoverify (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 */ ] }
StatusMeaning
400Bad request — validation failed, missing consent, or session already consumed.
401Missing or invalid API key, or invalid verification token.
403Liveness failed, or the tenant account is suspended.
404User, session, or consent record not found.
409Already enrolled.
410Handoff session expired.
422No face detected in the image.
429Usage or rate limit reached.
500Upstream service error (database or face provider).
501Session provider not implemented.
503Service 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.