Skip to main content

Create Ephemeral Token

An ephemeral token is a short-lived, capability-scoped credential minted for one subject. Your backend creates it with its own Bearer token and hands it to the subject's browser or app, which then sends it as X-Api-Key on the client routes (/subject/*, /ephemeral/capability). The subject never sees a system credential, and the token can only act on the one subject it was minted for.

note

Identiwise is in final development and the API is not open to the public yet. This page describes the behaviour of the current build, including which token fields are enforced today and which are accepted but not yet acted on.

Endpoint

POST /api/v1/ephemeral-tokens

Authorization: system Bearer token, role admin or superuser. Base URL: your tenant hostname — https://{tenant}.identiwise.com/api/v1.

This one path is overloaded: sending subject_id mints a token, while other body fields manage workflows and presets. See Workflows & Presets.

Request fields

All ids on this endpoint are canonical UUID strings.

FieldTypeDefaultDescription
subject_idstring (uuid)Required to mint a token. The subject this credential acts for. Must exist in your tenant, otherwise 404.
expires_in_minutesinteger60Lifetime from now. The response returns the resolved expires_at. A value below 1 falls back to the default.
max_usesinteger | nullnullHow many upload / re-upload submissions this token allows. null = unlimited until it expires. 1 = single use. Reads never consume a use.
one_timebooleanfalseDeprecated legacy alias: true maps to max_uses: 1. Ignored when max_uses is present.
max_license_attemptsinteger | null3Per-type cap on document submissions. null = unlimited until expiry.
max_selfie_attemptsinteger | null3Per-type cap on selfie submissions. ?type=analyze and ?type=verify_age count here too — they write selfie rows and drive the same pipeline.
preset_idstring (uuid)Load the token's settings from a saved preset. Body fields override the preset.
workflow_idstring (uuid)Bind the token to a workflow, whose ordered steps the hosted verify UI renders as a wizard. Must belong to your tenant.
upload_typestringanyany, upload, reupload, none — which upload routes the credential may drive (see below).
can_edit_detailsbooleantrueAllows the subject to correct their own stored details via POST /subject/details. Unset answers 403.
view_subject_detailsbooleantrueAllows GET /subject/details. Unset answers 403.
can_list_reuploadbooleantrueAllows the subject to list submissions awaiting a re-upload. Unset answers 403.
can_list_need_replybooleantrueAllows the subject to list submissions awaiting their reply. Unset answers 403.
can_return_capabilitiesbooleantrueAllows GET /ephemeral/capability to return the full capability set. Unset returns only the flag.
require_face_matchbooleanfalseSelects the token's assurance flow — see below.
min_ageintegerThe age gate applied by POST /subject/upload?type=verify_age. Negative values are ignored.
verification_idstringYour own submission key, recorded on the token row at mint. Requires verification_type in the same request, otherwise 400.
external_uidstringYour own identifier for the case, recorded on the token row at mint.

verification_id and external_uid are recorded so a minted credential can be traced back to the case it was issued for. The submission itself takes its verification_id from the upload request body, not from the token row.

upload_type today

any permits both POST /subject/upload and POST /subject/reupload. upload permits the first and answers 403 on the second; reupload is the mirror image. none is a known gap: it blocks /subject/reupload but does not currently block /subject/upload. Do not rely on it to make a token read-only — mint with max_uses or the per-type caps instead.

Accepted and stored, but not yet enforced

These fields are accepted by the mint, stored on the token row and echoed back in the response and in GET /ephemeral/capability. No code path acts on them today. They are listed here so that nothing in the API's own output is mistaken for a live control.

FieldStatus
verification_type (license, selfie, age_gender_emotion)Accepted and stored; required when you send verification_id. No route checks the submission it receives against it — a token minted for license will accept a selfie.
license_threshold, selfie_thresholdAccepted and stored, not yet enforced. The face-match verdict comes from the comparison engine's own decision; these numbers are not compared against anything.
can_read_own_capabilitiesAccepted and stored, not yet enforced. GET /ephemeral/capability is gated by can_return_capabilities alone.
photo_stream_capabilityAccepted and stored, not yet enforced.
workflow_flagsAccepted and stored, not yet enforced. Workflow behaviour comes from the workflow's steps.

GET /ephemeral/capability additionally returns a restrict_detail_edits key. It is not a field of the mint and not stored on the token row — sending it has no effect, and the capability response reports it as a constant null. It restricts nothing; can_edit_details is the enforced control over subject detail edits.

Assurance flows: require_face_match

require_face_match chooses what a document approval is allowed to mean for this token. With the default false (document-check flow), a licence belonging to a subject with no approved reference selfie on file may be approved on document detection plus OCR alone; the approval then reports face_comparison: "none", which is the machine-readable admission that it asserts "the document reads correctly" and not "the presenter's identity was verified". With require_face_match: true (strict flow), a licence approval requires a face comparison against an approved reference — if none exists the submission parks for administrator review with a stated reason, OCR still runs so the reviewer sees the score, and one manual approval anchors the subject so automation resumes from then on. Strictness is per token: minting document-check tokens for the same subject re-opens the document-only route, so mint strict tokens only for subjects that need identity assurance. A strict park still consumes a token use, because a use is reserved before any processing cost.

Consumption

A "use" is an upload or re-upload submission; reads never consume one. The use is reserved atomically before any storage or processing cost, against max_uses and against the matching per-type cap in the same statement — so a per-type refusal can never spend a global use, and two concurrent submissions cannot both win the last attempt. An exhausted token is fully dead: every client route denies it.

Request examples

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": "0191f3c2-7a41-7c3e-9b02-4f8a1d6e5b90",
"expires_in_minutes": 120,
"max_uses": 3,
"max_license_attempts": 2,
"max_selfie_attempts": 2,
"upload_type": "any",
"can_edit_details": true,
"require_face_match": true,
"min_age": 18,
"workflow_id": "0191f3d0-15b8-7a44-8e77-2c9d0b6f1a33",
"external_uid": "case-84213"
}'

Response

201 Created

{
"status": "ephemeral_token_created",
"token": "9f2a71c4e8b30d5a6c17f4b982e0d3a1",
"expires_at": "2026-09-05 14:30:00",
"external_uid": "case-84213",
"verification_id": null,
"verification_type": null,
"can_edit_details": true,
"upload_type": "any",
"can_return_capabilities": true,
"photo_stream_capability": true,
"can_list_reupload": true,
"can_list_need_reply": true,
"can_read_own_capabilities": true,
"view_subject_details": true,
"license_threshold": 0.65,
"selfie_threshold": 0.65,
"max_license_attempts": 2,
"max_selfie_attempts": 2,
"workflow_flags": null,
"require_face_match": true,
"max_uses": 3,
"workflow_id": "0191f3d0-15b8-7a44-8e77-2c9d0b6f1a33",
"min_age": 18
}

expires_at is returned as YYYY-MM-DD HH:MM:SS. The token value is returned once, at mint; no endpoint reads it back, so keep it only for as long as you need to hand it to the subject.

Handing the token to the subject

Send the subject to the hosted verification UI with the token in the query string:

https://acme.identiwise.com/verify/?token=9f2a71c4e8b30d5a6c17f4b982e0d3a1

The page reads GET /ephemeral/capability and renders itself from what the token allows: a step-by-step wizard when a workflow_id is bound, otherwise a tile dashboard (details, capture, re-upload, messages). It captures from the device camera and offers a QR hand-off so a desktop user can finish on their phone. If you build your own client instead, use the same token as X-Api-Key against the routes in Client Actions.

Status codes

Minting

CodeMeaning
201Token created.
400Malformed JSON; a non-scalar top-level value; an invalid subject_id; max_uses or either attempt cap that is not a positive integer or null; verification_id sent without verification_type.
403The Bearer token's role is neither admin nor superuser.
404The subject, preset_id or workflow_id does not exist in this tenant.
503The issuance could not be recorded in the audit trail, or the database was contended — no token was issued. Honour Retry-After and retry.

Using the token

CodeMeaning
401The token is missing, unknown, expired, or exhausted (use_count has reached max_uses). An exhausted token is dead on every client route, not just uploads.
403A capability the route needs is unset on this token, the upload_type forbids that route, or a per-type attempt cap is spent — for example Ephemeral token has used all 2 license attempts (max_license_attempts).
409The verification_id / external_uid names a submission of this subject that is already approved. An approved submission is final; no token use is consumed. Re-opening is an administrator action (reject with re-upload required, then POST /subject/reupload). The same 409 is returned if the submission was approved while an upload's storage write was still in flight — in that case nothing is recorded and the just-written file is removed, but the reserved use is not refunded.

Security notes

  • Mint the token from your backend. The system Bearer token must never reach a browser.
  • Keep the ephemeral token in application memory rather than persistent browser storage, and mint it with the shortest lifetime and the smallest capability set the step actually needs.
  • The token is scoped to one subject in one tenant. It cannot read or write another subject's records, and it grants no access to the admin API.
  • Every mint is written to the tenant's append-only audit trail before the credential exists; if that record cannot be written, no token is issued.