Skip to main content

Workflows & Presets

Two reusable objects sit behind an ephemeral token:

  • a workflow — an ordered list of steps the hosted verification UI renders as a wizard;
  • a preset — a saved template of token settings, so you do not resend the same capability set on every mint.

Both live on the same overloaded endpoint as the token mint, and both are scoped to your tenant: an id that belongs to another tenant answers 404, never another tenant's data.

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 per-step fields are not yet acted on.

The overloaded endpoint

POST /api/v1/ephemeral-tokens dispatches on the body. The first matching field wins, in this order:

Body fieldActionRole required
list_presets: trueList this tenant's presetsadmin, superuser
delete_preset_id (uuid)Delete one presetadmin, superuser
update_preset_id (uuid)Update one presetadmin, superuser
list_workflows: trueList this tenant's workflows (with_steps: true to include steps)admin, superuser
delete_workflow_id (uuid)Delete one workflowadmin, superuser
update_workflow_id (uuid)Update one workflow, and replace its steps if steps is presentadmin, superuser
create_workflow: true and/or create_preset: trueCreate either or bothadmin, superuser
subject_id (uuid)Mint an ephemeral token — see Create Tokenadmin, superuser

Every operation on this endpoint requires admin or superuser; the read-only view role is refused 403 at the router, including the list operations.

Creation is combinable: one request may carry create_workflow, create_preset and subject_id together, and each is created in that order from the same body. They are not auto-linked — a preset or a token is bound to a workflow only by an explicit workflow_id, so create the workflow first and reference the id it returns. Every id on this endpoint is a canonical UUID string.


1. Workflows

A workflow is a name, an optional description and an ordered list of steps. The step order and step type are what drive the hosted UI — it walks the subject through the steps in step_order, showing one capture or instruction screen per step.

Step types the hosted UI understands

step_nameWhat the subject sees
verify_licenseA prompt to capture or upload an ID document.
check_selfieA prompt to capture a face photo.
age_gender_estimationAn analysis step run against a captured face photo.

Any other step_name renders a generic instruction page rather than failing, so an unknown or future step type never breaks a live session.

Per-step configuration is reserved

Each step can carry a config_json string, and it is stored and returned verbatim. The keys inside it are not enforced by the API today — the step's step_name and step_order are the only parts of a workflow that change behaviour. Per-step configuration fields are reserved for a later release; do not treat a value inside config_json as a control that is in force.

Create a workflow

curl -X POST "https://acme.identiwise.com/api/v1/ephemeral-tokens" \
-H "Authorization: Bearer YOUR_SYSTEM_TOKEN" \
-H "Content-Type: application/json" \
-d '{
"create_workflow": true,
"workflow_name": "Standard onboarding",
"description": "Document, then selfie, then age estimation.",
"steps": [
{ "step_name": "verify_license", "step_order": 1 },
{ "step_name": "check_selfie", "step_order": 2 },
{ "step_name": "age_gender_estimation", "step_order": 3 }
]
}'

The workflow row and its steps are written in one transaction: if any step fails to insert, nothing is created.

List workflows

curl -X POST "https://acme.identiwise.com/api/v1/ephemeral-tokens" \
-H "Authorization: Bearer YOUR_SYSTEM_TOKEN" \
-H "Content-Type: application/json" \
-d '{ "list_workflows": true, "with_steps": true }'
{
"status": "workflow_list",
"count": 1,
"workflows": [
{
"id": "0191f3d0-15b8-7a44-8e77-2c9d0b6f1a33",
"workflow_name": "Standard onboarding",
"description": "Document, then selfie, then age estimation.",
"created_at": "2026-09-01 09:12:44",
"updated_at": "2026-09-01 09:12:44",
"steps": [
{
"id": "0191f3d0-15b9-7c02-9a44-77e1b3d5c410",
"workflow_id": "0191f3d0-15b8-7a44-8e77-2c9d0b6f1a33",
"step_name": "verify_license",
"step_order": 1,
"config_json": null,
"created_at": "2026-09-01 09:12:44"
}
]
}
]
}

Update and delete

update_workflow_id updates the name and description. If the body contains steps, the existing steps are replaced wholesale with the list you send, in one transaction — send the complete step list, not a delta. Omit steps to leave the existing ones untouched.

Unlike a preset update, a workflow update is not partial for description: omitting it stores an empty description. Send description on every update where you want the existing one kept. (workflow_name does fall back to the stored value when omitted.)

# Replace the steps of an existing workflow
curl -X POST "https://acme.identiwise.com/api/v1/ephemeral-tokens" \
-H "Authorization: Bearer YOUR_SYSTEM_TOKEN" \
-H "Content-Type: application/json" \
-d '{
"update_workflow_id": "0191f3d0-15b8-7a44-8e77-2c9d0b6f1a33",
"workflow_name": "Standard onboarding (v2)",
"description": "Document, then selfie.",
"steps": [
{ "step_name": "verify_license", "step_order": 1 },
{ "step_name": "check_selfie", "step_order": 2 }
]
}'

# Delete
curl -X POST "https://acme.identiwise.com/api/v1/ephemeral-tokens" \
-H "Authorization: Bearer YOUR_SYSTEM_TOKEN" \
-H "Content-Type: application/json" \
-d '{ "delete_workflow_id": "0191f3d0-15b8-7a44-8e77-2c9d0b6f1a33" }'

Both answer 200 with workflow_updated / workflow_deleted and the id, or 404 when the workflow is not in your tenant.


2. Presets

A preset is a saved template of the token settings described in Create Token. A token minted with preset_id inherits the values the preset stores, including require_face_match, max_uses, the per-type attempt caps, the capability flags, min_age and a linked workflow_id — with the two exceptions noted below.

Precedence at mint: request body → preset → built-in default. Anything you send in the mint body overrides the preset for that one token.

Name the flags you want

A preset stores a value for every flag, including the ones you leave out — and can_return_capabilities, can_list_reupload, can_list_need_reply and can_read_own_capabilities default to off in a preset even though minting without a preset defaults them on. (can_edit_details and view_subject_details default on either way.) A token minted from the preset inherits the stored value, so list every flag the preset should grant. The one exception is require_face_match: an explicit null in the mint body means "no opinion", so it never downgrades a strict preset.

The per-type attempt caps have the opposite gap: a preset that stores null (unlimited) for max_license_attempts or max_selfie_attempts is not inherited. The mint reads a stored null as "not set" and falls through to the built-in default of 3, so tokens from that preset accept three document and three selfie submissions. To mint an uncapped token, send "max_license_attempts": null (or "max_selfie_attempts": null) in the mint body itself.

Create a preset

curl -X POST "https://acme.identiwise.com/api/v1/ephemeral-tokens" \
-H "Authorization: Bearer YOUR_SYSTEM_TOKEN" \
-H "Content-Type: application/json" \
-d '{
"create_preset": true,
"preset_name": "Standard onboarding",
"description": "Strict assurance, three document attempts.",
"workflow_id": "0191f3d0-15b8-7a44-8e77-2c9d0b6f1a33",
"upload_type": "any",
"can_edit_details": true,
"view_subject_details": true,
"can_return_capabilities": true,
"can_list_reupload": true,
"can_list_need_reply": true,
"require_face_match": true,
"max_uses": 5,
"max_license_attempts": 3,
"max_selfie_attempts": 3,
"min_age": 18
}'

preset_name is required. Creating a workflow and a preset in the same request answers workflow_and_preset_created with both ids.

List, update and delete

curl -X POST "https://acme.identiwise.com/api/v1/ephemeral-tokens" \
-H "Authorization: Bearer YOUR_SYSTEM_TOKEN" \
-H "Content-Type: application/json" \
-d '{ "list_presets": true }'

The listing returns status: "preset_list", a count and a presets array carrying each preset's id, preset_name, description, upload_type, the capability flags, max_uses, the per-type attempt caps, require_face_match, min_age, workflow_id and created_at. It also returns license_threshold, selfie_threshold, photo_stream_capability, can_read_own_capabilities and workflow_flags, which are stored but not yet enforced — see the table in Create Token.

update_preset_id is a partial update: fields you omit keep their stored values. Sending max_license_attempts: null or max_selfie_attempts: null means "unlimited until the token expires"; 0 and negative values are refused with 400.

curl -X POST "https://acme.identiwise.com/api/v1/ephemeral-tokens" \
-H "Authorization: Bearer YOUR_SYSTEM_TOKEN" \
-H "Content-Type: application/json" \
-d '{
"update_preset_id": "0191f3d4-9c02-7f18-b3a1-6e5d2c7b4900",
"max_license_attempts": 2
}'

curl -X POST "https://acme.identiwise.com/api/v1/ephemeral-tokens" \
-H "Authorization: Bearer YOUR_SYSTEM_TOKEN" \
-H "Content-Type: application/json" \
-d '{ "delete_preset_id": "0191f3d4-9c02-7f18-b3a1-6e5d2c7b4900" }'

Both answer 200 with preset_updated / preset_deleted and the id, or 404 when the preset is not in your tenant.


3. Using them at mint

# From a preset — the smallest possible mint
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",
"preset_id": "0191f3d4-9c02-7f18-b3a1-6e5d2c7b4900"
}'

# From a workflow alone, with settings sent inline
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",
"workflow_id": "0191f3d0-15b8-7a44-8e77-2c9d0b6f1a33",
"expires_in_minutes": 30,
"max_uses": 2
}'

A preset_id or workflow_id that does not resolve inside your tenant answers 404 and mints nothing.


4. What the subject's client sees

GET /api/v1/ephemeral/capability, authenticated with X-Api-Key: {ephemeral token}, returns the capability set and the resolved workflow steps. It is how the hosted verification UI decides which screens to render, and it is the same contract if you build your own client.

{
"can_edit_details": true,
"view_subject_details": true,
"upload_type": "any",
"can_list_reupload": true,
"can_list_need_reply": true,
"can_return_capabilities": true,
"require_face_match": true,
"min_age": 18,
"max_uses": 5,
"use_count": 1,
"max_license_attempts": 3,
"license_use_count": 1,
"max_selfie_attempts": 3,
"selfie_use_count": 0,
"workflow_steps": [
{
"step_name": "verify_license",
"step_order": 1,
"config": {}
}
]
}

Each step in this response carries step_name, step_order and a decoded config object — the six-column form with ids and timestamps shown in section 1 is what list_workflows returns, not this route.

The consumption counters are returned alongside the caps, so a client can tell how many submissions the credential has left. The response also echoes license_threshold, selfie_threshold, photo_stream_capability, can_read_own_capabilities, workflow_flags, verification_type and a permanently null restrict_detail_edits — all stored, none of them enforced today. Access to this route is gated by can_return_capabilities; when that flag is unset the response carries only the flag itself.