Skip to main content

Authentication

Identiwise uses two credentials with deliberately different reach. Your servers hold the long-lived one; the person being verified only ever holds a short-lived, single-subject one.

MethodHeaderHeld byScope
System / bearer tokenAuthorization: Bearer <token>Your backend, or a staff user in the admin consoleThe whole tenant, gated by role (superuser / admin / view)
Ephemeral tokenX-Api-Key: <token>The subject's browser or appOne subject, only the capabilities enabled at mint, for minutes
Pre-launch

Tokens are not being issued yet. The requests below describe the API as it will ship — they are not live endpoints you can call today. Email hello@identiwise.com to be told when access opens.

Every request is addressed to your own tenant hostname — see Multi-tenant context at the end of this page. The examples use acme.identiwise.com; substitute your own subdomain.


1. Staff login (two-phase)

POST /api/v1/admin/login authenticates a staff user with email and password. It does not always return a token.

  • If the user has no two-factor authenticator enrolled, the response carries the bearer token directly.
  • If the user has 2FA enrolled, the response is a TOTP challenge instead — a single-use challenge_token valid for 300 seconds, with at most 5 code attempts. You must complete step 2 to get a token.
  • If the tenant enforces 2FA and the user has not enrolled, the response is 403. The user enrols in the account portal; there is no way around it from the API.

Your client must handle both shapes.

Step 1 — email and password

curl -X POST "https://acme.identiwise.com/api/v1/admin/login" \
-H "Content-Type: application/json" \
-d '{
"email": "admin@yourcompany.com",
"password": "your_secure_password"
}'

Bearer issued (no 2FA enrolled):

{ "token": "…", "username": "admin", "role": "superuser" }

TOTP challenge (2FA enrolled):

{ "challenge": "totp", "challenge_token": "…", "expires_in": 300 }

Step 2 — the TOTP code

Exchange the challenge and the 6-digit code from the user's authenticator app for the bearer token.

curl -X POST "https://acme.identiwise.com/api/v1/admin/login/verify" \
-H "Content-Type: application/json" \
-d '{
"challenge_token": "CHALLENGE_TOKEN_FROM_STEP_1",
"code": "123456"
}'

Response:

{ "token": "…", "username": "admin", "role": "superuser" }

Every failure mode — unknown, expired or already-consumed challenge, wrong code, attempts exhausted, wrong tenant — returns the same uniform 401, so the response tells an attacker nothing about which part was wrong.


Do not script the login flow above from a server. It is an interactive credential: it needs a human's password, it is subject to per-IP login rate limiting, and under a tenant that enforces 2FA it needs a code from a phone.

For machine-to-machine use, a superuser mints a long-lived API token instead:

curl -X POST "https://acme.identiwise.com/api/v1/admin/api-tokens" \
-H "Authorization: Bearer YOUR_SESSION_TOKEN" \
-H "Content-Type: application/json" \
-d '{
"description": "billing-service (production)",
"expires_at": "2027-01-31"
}'
FieldNotes
descriptionOptional. String, up to 255 characters. Label it after the system that will hold it — this is what you will read when deciding what to revoke.
expires_atOptional. YYYY-MM-DD HH:MM:SS, RFC 3339 YYYY-MM-DDTHH:MM:SSZ, or YYYY-MM-DD (normalised to midnight). Absent or empty means no expiry.
{
"status": "API token created",
"token": "…",
"expires_at": "2027-01-31 00:00:00",
"description": "billing-service (production)"
}
Shown once

The plaintext token appears in this response and nowhere else — we store only a fingerprint and a hash of it. If you lose it, revoke the token and mint another.

Manage tokens with GET /api/v1/admin/api-tokens and revoke one with DELETE /api/v1/admin/api-tokens/{id}. Mint one token per consuming system so that revoking a compromised one does not take the rest down with it.

Using a bearer token

GET /api/v1/admin/subjects HTTP/1.1
Host: acme.identiwise.com
Authorization: Bearer YOUR_SYSTEM_TOKEN

3. Ephemeral tokens (for the subject's device)

An ephemeral token delegates a narrow, time-boxed permission to one person, so their browser or app can talk to Identiwise directly — you never proxy image bytes through your own servers.

The flow

  1. Your backend calls POST /api/v1/ephemeral-tokens with your bearer token, naming the subject and the capabilities that subject may exercise.
  2. Identiwise returns a token that expires in expires_in_minutes (default 60).
  3. Your backend hands the token to your frontend — or to the hosted verification UI as https://acme.identiwise.com/verify/?token=….
  4. The client sends it as X-Api-Key on the subject routes.

Minting a token

curl -X POST "https://acme.identiwise.com/api/v1/ephemeral-tokens" \
-H "Authorization: Bearer YOUR_SYSTEM_TOKEN" \
-H "Content-Type: application/json" \
-d '{
"subject_id": "018f3a2b-7c41-7d3e-9a10-6b2c5d8e4f00",
"expires_in_minutes": 30,
"max_uses": 2,
"upload_type": "any",
"can_edit_details": true,
"require_face_match": true
}'

subject_id is the UUID string returned when you created the subject — shard-side ids (subjects, submissions, tokens, presets, workflows) are UUIDs, while staff users and API tokens use integer ids.

The response echoes the capabilities the token was actually minted with, so you can assert on them rather than assume:

{
"status": "ephemeral_token_created",
"token": "…",
"expires_at": "2026-09-05 14:32:00",
"max_uses": 2,
"upload_type": "any",
"can_edit_details": true,
"require_face_match": true
}

max_uses — the consumption contract

max_uses counts upload and re-upload submissions only. Reads — fetching details, listing messages, reading capabilities — never consume a use.

ValueMeaning
1Single use. One submission and the token is dead.
nAllows n submissions, e.g. one retry after a blurry capture.
omitted / nullUnlimited submissions until expires_at.

An exhausted token is fully dead: every endpoint denies it, exactly as an expired one does. A use is reserved before any storage or AI cost, so a submission that parks for review still consumes one.

Legacy field

one_time: true is a deprecated alias for max_uses: 1, kept for older integrations. It is ignored when max_uses is present. Use max_uses in new code.

Using an ephemeral token

The client sends it as X-Api-Key. No bearer token is involved.

POST /api/v1/subject/details HTTP/1.1
Host: acme.identiwise.com
X-Api-Key: YOUR_EPHEMERAL_TOKEN
Content-Type: application/json

{
"first_name": "Alice",
"last_name": "Doe"
}

The same header carries the subject's uploads (POST /api/v1/subject/upload?type=license), messages, re-uploads and deletion requests. A client can read what its own token is allowed to do with GET /api/v1/ephemeral/capability.

Security warning

Never put a bearer token in client-side code — JavaScript, a mobile app, or anything a user can read. It grants tenant-wide access for as long as it lives. Only ephemeral tokens belong on a device: they are scoped to one subject, expire in minutes, and can be limited to a fixed number of submissions.


Multi-tenant context

Identiwise resolves your tenant from the Host header of the request, before authentication runs. Your API base URL is your own tenant hostname:

https://{tenant}.identiwise.com/api/v1

So for tenant acme, https://acme.identiwise.com/api/v1/admin/subjects. The same hostname serves your admin console at /admin/ and the hosted verification UI at /verify/.

There is no shared or default API hostname — a token issued for one tenant is refused on another tenant's hostname, so the host and the credential must agree. If you route requests through a proxy, make sure it forwards the Host header unchanged; rewriting it will make the request resolve to no tenant, or to the wrong one.