Skip to main content
POST
cURL
Use this endpoint to create workflows through the API without building workflow graph nodes yourself. Send a features array in the order users should complete the checks. Didit converts it into a node-based workflow internally and adds the final status step automatically. This POST /v3/workflows/ endpoint creates simple, linear workflows only — a flat features list. For branching, conditional routing, Document AI, and decline/decision nodes, use the node graph format documented below (it powers the visual builder in the console and the Didit MCP workflow tools). See Workflow Feature Configs for every config field accepted by each feature.

Basic KYC workflow

Questionnaire workflow

First create the questionnaire with POST /v3/questionnaires/. Then use the returned questionnaire_id as questionnaire_uuid.
Feature order matters. For example, put OCR before FACE_MATCH, NFC, DATABASE_VALIDATION, or user AML checks that depend on document data.

Feature config reference

Each features[] item has this shape:
The exact config object depends on the feature. Review the full field list, defaults, required settings, value ranges, and examples in Workflow Feature Configs. For OCR, omit config.documents_allowed, send null, or send {} to allow every supported document type. Include it only when you want to restrict the workflow to specific countries or document types. For QUESTIONNAIRE, config.questionnaire_uuid is required.

Optional workflow settings

You can include any of these top-level fields when creating a workflow to control session behavior. They are all optional and fall back to the workflow defaults when omitted.

Branching & node-based workflows (graph)

A linear features list can’t express “if X then Y.” For conditional branching (decline on a status, route on an extracted field, gate a step on a match), a Document AI request step, or terminal decline/decision nodes, Didit workflows use a node graph — the same structure the console’s visual builder produces.
The simplest way to build a graph workflow is the Didit MCP — ask an agent in plain language (“after ID verification, if the profession is ≈ ‘Software Engineer’, add a proof-of-funds step”) and it constructs, validates, and saves the graph for you. The structure below is what those tools (and the console) produce.

Graph shape

A graph is { start_node, nodes }. nodes is a map of node id → node. Edges are implicit: each node points forward via next (unconditional) and/or branches[].goto (conditional). start_node must be a feature node. Branches are evaluated in order, first match wins; an empty rules: [] is the catch-all “else” (keep it last). session_status: "Determine" auto-decides from the combined feature outcomes.
Every branch node must have an explicit else branch — a branch with rules: [] that routes sessions matching none of the conditions. Don’t rely on the branch node’s next as the fallback: a bare next renders as an ambiguous edge in the builder and is easy to omit. When you send a branch node with a next and no else branch, the API converts that next into an explicit else branch for you (so { …, "branches": [<conditions>], "next": "liveness" } is saved as branches: [<conditions>, { "id": "else", "rules": [], "goto": "liveness" }]). Build the else yourself to be explicit; the conversion is a safety net, not a substitute.

Branch rules & operators

Each rule compares a field to a value with an operator:
  • Fields name a feature outcome (kyc.status, aml.status, document_ai.status, face_match.score, …) or an extracted value (kyc.full_name, kyc.age, kyc.nationality, kyc.extra_fields.profession, a Document-AI field document_ai.<key>, expected_details.*). A branch may only read a field whose feature has run on every path reaching it.
  • Operators: equals, not_equals, greater_than(_or_equals), less_than(_or_equals), contains, not_contains, in, not_in, is_empty, is_not_empty, regex, and fuzzy_match (string fields only; requires a score 0–100 — a similarity threshold, e.g. profession ≥ 80% similar to “Software Engineer”).

Document AI (e.g. proof of funds)

A DOCUMENT_AI feature node requests one or more documents and extracts named fields:
Each configured field becomes a branchable value document_ai.<key>, and the step’s outcome is document_ai.status. Set "is_full_name": true on the field that holds the document holder’s full name. Its extracted value is compared against the verified identity’s full name (the OCR/ID-verification name, or the session’s expected details), and document_ai_name_mismatch_action decides what happens on a mismatch below document_ai_name_match_score_threshold. The flag is optional — a document may mark no field — and at most one field per document may set it. See Workflow feature configs → Document AI for every Document AI config field.

Endpoints & draft lifecycle

Graph editing is per-application and uses the console/management endpoints (user-Bearer + read:workflows/write:workflows). A graph can only be edited on a draft version, so the lifecycle is create-draft → set graph → publish:

Worked example: route Software Engineers to proof of funds

After ID verification: if it was declined, decline the session; if the holder’s profession ≈ “Software Engineer” (≥80% similar), request a proof-of-funds document; then make a final decision.
The final node uses session_status: "Determine", which auto-decides (approve / decline / in-review) from whatever features actually ran on the session — so both the proof-of-funds path and the else path resolve correctly without any branch reading document_ai.status (which doesn’t exist on the path that skips Document AI). Remember: a branch may only read a field whose feature ran on every path reaching it. Send it as { "graph": <above> } to the …/workflow-graph/ endpoint (validate first). To build a new workflow from scratch use the MCP’s didit_workflow_set_graph; to modify an existing workflow use didit_workflow_edit_graph (small ops, preserves the existing nodes and allow-lists). See the Didit MCP.

Authorizations

x-api-key
string
header
required

Body

application/json

Create a simple linear workflow. The API converts this feature list into a node-based workflow graph internally. STRICT field whitelist: any key not listed here (including workflow_type) is rejected with a 400.

features
object[]
required

Verification features in execution order. The API links each item to the next one and adds one final status node automatically.

Minimum array length: 1
workflow_label
string
default:ID Verification

Display name for the workflow (max 50 characters). Defaults to "ID Verification" when omitted — always set it so workflows stay distinguishable.

Maximum string length: 50
Example:

"Standard KYC"

is_default
boolean

Set this workflow as the default for new sessions.

status
enum<string>

Omit this field to create a published workflow ready for sessions. Use draft only when you want to save without publishing.

Available options:
draft,
published
is_white_label_enabled
boolean
default:false

Enable white-label customization for sessions created with this workflow.

is_desktop_allowed
boolean
default:false

Allow the verification flow to run on desktop browsers.

max_retry_attempts
integer
default:3

Maximum retry attempts allowed after a declined verification. 0 blocks the user after the first decline.

Required range: 0 <= x <= 10
retry_window_days
integer | null
default:7

Rolling window in days used to count retries. null enforces an all-time limit.

Required range: 1 <= x <= 365
face_liveness_max_attempts
integer
default:3

Maximum liveness submissions per session. Default 3: one initial attempt plus two retries. This value is also copied into generated liveness and age-estimation workflow nodes unless the feature-level config overrides it.

Required range: 1 <= x <= 3
face_match_max_attempts
integer
default:3

Maximum face-match submissions per session. Default 3: one initial attempt plus two retries. This value is also copied into generated face-match workflow nodes unless the feature-level config overrides it.

Required range: 1 <= x <= 3
session_expiration_time
integer
default:604800

Seconds before an unfinished session expires. Minimum 1 hour (3600), maximum 4 weeks (2419200).

Required range: 3600 <= x <= 2419200

Response

Workflow created. The response body is the FULL workflow configuration — the same raw VerificationSettings serialization returned by GET /v3/workflows/{settings_uuid}/ (~190 fields: every feature toggle, threshold, action and the generated workflow_graph), not the compact list item. Store the uuid (also exposed as workflow_id) so you can reference it from later session-creation calls. Note: features is a " + "-joined string and prices are JSON numbers.

Full workflow configuration (abbreviated here — see GET /v3/workflows/{settings_uuid}/).