Skip to content

AI Integration Skill

Biometry Integration Architect — API v2

This document is an instruction set for AI coding agents (Claude, GPT, Copilot, etc.) building biometric verification against Biometry API v2. Feed it to your agent as context before it writes integration code.

Machine-readable contract, if your agent can fetch a URL: https://api.biometrysolutions.com/api-gateway/v2/openapi.json.

Working on an existing v1 integration? See the v1 skill and the migration guide.


System architecture

Biometry follows an API proxy pattern. The customer’s backend proxies all Biometry calls; the frontend never calls Biometry directly, because the API token is a secret that must not reach a browser or mobile binary.

┌─────────────────────┐ ┌──────────────────────┐ ┌─────────────────────┐
│ Customer Frontend │ │ Customer Backend │ │ Biometry API │
│ │ │ │ │ │
│ Web SDK / Flutter │────▶│ /api/verify │────▶│ /api-gateway/v2/ │
│ (captures media) │◀────│ (holds API token) │◀────│ │
└─────────────────────┘ └──────────────────────┘ └─────────────────────┘

Base URL: https://api.biometrysolutions.com/api-gateway/v2

Other prefixes you will need (unversioned, shared with v1):

  • /api-consent — consent records
  • /api-transactions — transaction history
  • /api-projects — API key management
  • /api-auth — Console user auth; not part of a backend integration

Authentication

One header, on every call:

Authorization: Bearer <API_TOKEN>

The token is a JWT minted in the Console. It encodes project_id, the enabled services, and optionally a scoring system ID. It does not expire — it is valid until revoked, so treat it as a long-lived secret: environment variable or secret manager, one token per application, never in version control.

A second credential exists — the Console user session — and it is not interchangeable. These endpoints require it and will reject an API token with 401: scoring systems (/v2/scoring-systems/*), webhooks (/v2/webhooks/*), consent document authoring, GET /v2/sessions, DELETE /v2/users/{user_id}. If your task says “create a webhook”, the answer is the Console, not the API token.


The two rules that differ most from v1

1. Identity is user_id, in the body. No X-User-Fullname. user_id is an opaque, caller-chosen string, 1–128 chars matching ^[A-Za-z0-9._:@-]+$, unique inside the project. Use the customer’s own primary key, a UUID, or a hash — never a person’s name, and never anything with spaces (it will be rejected with 400).

2. Parameters live in the body. File-carrying endpoints are multipart/form-data with a single JSON part named request alongside the file parts. The v1 business headers (X-User-Fullname, X-Session-ID, X-Action-Trigger, X-Use-Prefilled-Video, X-Inhouse-Docauth, X-Inhouse-MRZ) are stripped on v2 routes — sending them does nothing.

Headers that remain headers: Authorization, X-Request-Id, X-Client-App, X-Client-Version, X-App-Version, X-Device-Info, X-Geo-Location.


Response envelope

Every response, success or failure:

{
"data": { },
"decision": { "status": "pass", "score": 87.5, "reasons": [] },
"meta": { "request_id": "b6f0…", "message": "video processed successfully" }
}
{
"error": { "code": "invalid_request", "message": "user_id is required" },
"meta": { "request_id": "b6f0…" }
}
  • data — endpoint payload. Omitted when there is nothing to return.
  • decision — present only when a scoring system was evaluated. status ∈ pass | refer | fail.
  • meta.request_id — always present. Log it on every call; it is what Biometry support traces.

Service results are keyed by public service name: face_liveness_detection, active_speaker_detection, visual_speech_recognition, face_recognition, voice_recognition. Each value is { code, description, result, score } with score on a 0–100 scale. Only services enabled on the token appear.


Failure modes an agent must handle

These are the three things naive generated code gets wrong.

1. 200 does not mean “passed”. Services run in parallel and report independently. A failed one appears as {"error": "…"} under its own key while the HTTP status stays 200:

{ "data": { "face_recognition": { "error": "there is no enrolled face for this person" } } }

Always inspect each service entry and the decision, never the status alone.

2. Rejected enrollment is also 200. POST /v2/enrollments/face returns meta.message: "failed to enroll face" with a non-zero data.enrollment_result.code (1 no face / too small / bad quality, 2 more than one face, 3 face not centered). Voice returns data.status of error or qafailed. Branch on those, not on the status code.

3. Missing consent silently drops services. Face Recognition and Voice Recognition require authorization consent. Without it they are removed from the call and the response carries the header X-Removed-Services: true — you get a 200 with the identity check simply absent. Check consent up front or check that header.

Error codes to branch on: invalid_request (400), invalid_api_key / invalid_token (401), service_not_enabled (403), forbidden (403, includes missing consent), not_found (404), conflict (409), unprocessable_entity (422), internal_error (500, retry with backoff).


Consent still lives on the unversioned consent service, and this is the single most common integration mistake: pass your user_id as user_fullname. That field is the identity key the platform matches on.

POST /api-consent/consent → authorization consent (recognition, enrollment)
POST /api-consent/strg-consent → storage consent (enrollment only)
Authorization: Bearer <API_TOKEN>
Content-Type: application/json
{ "is_consent_given": true, "user_fullname": "<same value as user_id>" }

POST grants (rejects false with 400); PUT on the same path changes an existing record — that is how a withdrawal is recorded. GET /api-consent/history?user_fullname=… returns the audit trail.


Integration flow: verify a returning user

1. Pre-flight — is this user enrolled?

GET /api-gateway/v2/users/{user_id}
{ "data": { "user_id": "…", "enrollments": { "face": true, "voice": false }, "voice_phrases": [] } }

An unknown user is not a 404 — it returns enrollments all false. Use this to route between onboarding and login, and to skip a check the user cannot pass. Batch form: GET /v2/users?ids=a,b,c (max 100).

2. Start a session

POST /api-gateway/v2/sessions → { "data": { "session_id": "…" } }
POST /api-gateway/v2/sessions?warmup=true (pre-warms ML services)

Sessions group the calls of one attempt and store the first uploaded video, so /v2/face-match can reuse it. Pass session_id in the request part of every subsequent call.

3. Liveness

POST /api-gateway/v2/liveness
Content-Type: multipart/form-data
request = {"user_id":"…","session_id":"…","phrase":"one two three four five six"}
video = <file>

Returns face_liveness_detection, active_speaker_detection, visual_speech_recognition. phrase is required — it is what Visual Speech Recognition compares its transcript against. Optional fields in request: vocabulary, trigger, services.exclude (array of service keys).

4. Identity

POST /api-gateway/v2/face-verify request = {"user_id","phrase","session_id"}, video = <file>
POST /api-gateway/v2/voice-verify request = {"user_id","phrase","session_id"}, voice = <file>

Each compares against the user’s enrolled template and returns its own decision. Run them only for modalities the pre-flight said are enrolled.

5. End the session

POST /api-gateway/v2/sessions/{id}/end
{ "phone_number": "+996700000000" } ← optional SIM-swap check

Ending a session does not return a combined verdict. Compose the per-call decision objects yourself. Do not generate code that waits for a session-level score.


Integration flow: document onboarding

POST /api-gateway/v2/sessions → session_id
POST /api-gateway/v2/documents/check
request = {"session_id":"…","provider":"inhouse"} (provider/mrz_provider optional)
document = <file> → extracted fields + portrait_photo
POST /api-gateway/v2/liveness request+video → anti-spoof results
POST /api-gateway/v2/face-match
request = {"user_id":"…","session_id":"…","use_session_video":true}
reference_image = <document portrait> → match against the stored video
POST /api-gateway/v2/enrollments/face (optional, for future logins)
request = {"user_id":"…","use_session_video":true} → enrolls from the session's video
POST /api-gateway/v2/sessions/{id}/end

use_session_video is honoured by /v2/face-match, /v2/face-verify, /v2/voice-verify and /v2/enrollments/face — every one of them reuses the video the liveness step stored on the session, so the capture is uploaded once. /v2/enrollments/voice does not take it: a voiceprint needs several distinct recordings. Enroll before /v2/sessions/{id}/end; once the session is closed its video can no longer be enrolled from.


Asynchronous: deepfake detection

POST /api-gateway/v2/deepfake/checks request = {"user_id","session_id"}, video = <file> → 201
GET /api-gateway/v2/deepfake/checks/{request_id}

The ID to poll with is data.metadata.request_id from the 201 response — not meta.request_id, which identifies the HTTP call. Poll until data.status is completed or failed; the verdict is data.result.prediction (real / fake) with confidence and score. Or register a webhook and skip polling.


Checklist before shipping generated code

  • API token read from the environment on the backend; never sent to a client
  • user_id is opaque and stable, matches ^[A-Za-z0-9._:@-]{1,128}$, and is the same value used as user_fullname for consent
  • Consent collected before enrollment (authorization and storage) and before recognition (authorization)
  • Parameters in the request JSON part, not in X-* headers
  • Every response checked per service, not by HTTP status
  • meta.request_id logged on every call
  • session_id passed to every call in one attempt, and the session ended on both success and failure paths
  • Retries only on 5xx, with backoff; never retry a successful enrollment or deepfake submission
  • Media captured client-side via the Web or Flutter SDK and forwarded through your backend

Reference

TopicPage
Envelope, identity model, service keysAPI v2 Overview
End-to-end curl walkthroughv2 Quick Start
Every error codeError catalog
Verification endpoints in fullVerification
How the verdict is computedScoring Systems
Reading results backTransactions