Start a new flow
Resolves a flow definition based on purpose + audience context and returns the first capability step. Creates a new session implicitly unless `session_id` is provided (for step-up / reauth on an existing session). The response contains an `id` field — the flow handle. Use it as the path parameter for all subsequent `/flow/{id}/submit` calls. The response also sets an encrypted `HttpOnly` cookie (`_zflow`) containing the flow's orchestration state (current step, collected data, history). The server is stateless between requests — all flow state lives in this cookie. The browser sends it automatically on subsequent requests.
Resolves a flow definition based on purpose + audience context and returns
the first capability step. Creates a new session implicitly unless
session_id is provided (for step-up / reauth on an existing session).
The response contains an id field — the flow handle. Use it as the path
parameter for all subsequent /flow/{id}/submit calls.
The response also sets an encrypted HttpOnly cookie (_zflow) containing
the flow's orchestration state (current step, collected data, history).
The server is stateless between requests — all flow state lives in this
cookie. The browser sends it automatically on subsequent requests.
application/json- body
project_id*project-id^[a-zA-Z0-9_-]+$purpose*string"login""register""recovery""profiling""reauth""link_account"flow_definition_name?stringName of a specific flow definition to use. When omitted, the engine selects the best-matching definition based on purpose + audience context.
^[a-z][a-z0-9-]*$dry_run?booleanSet to true to simulate the flow without actually creating any
user or session. Useful for testing and validation of flow definitions.
This requires setting the flow_definition_name property as well.
falseschema_version?stringSemver version of the flow definition JSON Schema to use. When omitted, the latest version is used.
^\d+\.\d+\.\d+$challenge_nonce?challenge-nonceOrigin-bound, single-use nonce obtained from POST /bootstrap/challenge.
Binds the request to the browser that initiated the flow, preventing
cross-origin replay.
session_id?stringExisting session to attach to (for step-up / reauth). Omit to let the flow create a new session implicitly.
auth_request_id?stringOIDC/SAML auth request ID (links flow to protocol request).
redirect_uri?stringurihint?Flow started, first step returned.
application/json- response
id*stringFlow handle. Use this as the path parameter for subsequent submit/event calls. May change between responses when a flow pivot or pop occurs.
session_id*stringUnderlying session ID. Stable across all stacked flows.
session_token?stringReserved for future rotation. The sealed _zflow cookie carries the flow state today.
step*A step contains ordered capability arrays: what to collect (fields),
what the user can do (actions), and what security gates must be satisfied (gates).
The LiquidJS template in branding.liquid_template iterates these arrays in order
and builds name-keyed indexes locally for lookup.
branding?Resolved branding inherited from the app → team → project hierarchy. Determined at flow creation based on audience context. Does not change between steps.
redirect_uri?stringPresent when step.complete is redirect.
urihandoff_token?stringShort-lived, audience-bound token produced on flow completion.
Exchange it for a session via POST /sessions/exchange.
Only present when step.complete is set.
handoff_token_expires_at?stringExpiry timestamp for handoff_token. Only present when
handoff_token is set.
date-timecurl -X POST 'https://example.com/flow' \ -H 'Content-Type: application/json' \ -d '{ "project_id": "string", "purpose": "login"}'{ "id": "string", "session_id": "string", "session_token": "string", "step": { "name": "string", "texts": { "title_key": "string", "description_key": "string" }, "error": "string", "complete": "redirect", "redirect_url": "http://example.com", "fields": [], "identifier": { "value": "string", "autocomplete": "string" }, "actions": [], "gates": {}, "sso_providers": [], "challenge": { "method": "passkey", "challenge_id": "string", "options": {} } }, "branding": { "$schema": "string", "layout": "centered", "liquid_template": "string", "logo_url": "http://example.com", "hero_url": "http://example.com", "theme": { "mode": "light", "light": { "logo_url": "http://example.com", "palette": { "primary": "#4F46E5", "on_primary": "#4F46E5", "background": "#4F46E5", "surface": "#4F46E5", "muted": "#4F46E5", "border": "#4F46E5", "text": "#4F46E5", "text_muted": "#4F46E5", "link": "#4F46E5", "success": "#4F46E5", "warning": "#4F46E5", "error": "#4F46E5" } }, "dark": { "logo_url": "http://example.com", "palette": { "primary": "#4F46E5", "on_primary": "#4F46E5", "background": "#4F46E5", "surface": "#4F46E5", "muted": "#4F46E5", "border": "#4F46E5", "text": "#4F46E5", "text_muted": "#4F46E5", "link": "#4F46E5", "success": "#4F46E5", "warning": "#4F46E5", "error": "#4F46E5" } } }, "typography": { "font_family": "string", "font_url": "http://example.com", "scale": 0.75 }, "shape": { "radius": "none", "density": "compact", "logo_scale": 0.5 } }, "redirect_uri": "http://example.com", "handoff_token": "string", "handoff_token_expires_at": "2019-08-24T14:15:22Z"}