> ## Documentation Index
> Fetch the complete documentation index at: https://docs.didit.me/llms.txt
> Use this file to discover all available pages before exploring further.

# Crea workflow API: costruisci il flusso KYC

> Crea via API un workflow Didit con funzionalità ordinate: KYC, KYB, AML e biometria in un unico flusso. Pagamento a chiamata, senza contratti.



Prompt API per creare un workflow
        Copia il prompt negli appunti



      
      

\<pre>

\`Create a simple Didit workflow through the Management API.

Endpoint:
POST https://verification.didit.me/v3/workflows/

Authentication:
Use the x-api-key header with my Didit API key.

Requirements:
\- Use the simple v3 API shape with features.
\- Send features in the exact order users should complete them.
\- Do not send workflow_graph, branches, actions, webhooks, or conditional logic.
\- Didit will convert the features array into a linear node-based workflow and add the final status step automatically.
\- Use uppercase feature values such as OCR, LIVENESS, FACE_MATCH, AML, QUESTIONNAIRE, EMAIL_VERIFICATION, PHONE_VERIFICATION, or PROOF_OF_ADDRESS.
\- Configure each feature with the fields from /management-api/workflows/feature-configs.
\- For an OCR feature, omit config.documents_allowed or send an empty object to allow every supported document type. Send config.documents_allowed only to restrict the allowed documents.
\- Put dependency features first. For example, put OCR before FACE_MATCH, NFC, DATABASE_VALIDATION, or user AML checks that depend on document data.
\- For a QUESTIONNAIRE feature, first create the questionnaire with POST /v3/questionnaires/ and use the returned questionnaire_id as config.questionnaire_uuid.
\- Store the workflow uuid returned by the response. Use it as workflow_id when creating sessions and as settings_uuid when updating this workflow.

Example KYC request body:
\{
"workflow_label": "Standard KYC",
"features": [
\{
  "feature": "OCR",
  "config": {
    "documents_allowed": {
      "USA": {
        "DL": {
          "enabled": 1,
          "sides": 2
        }
      }
    },
    "duplicated_user_action": "REVIEW"
  }
},
\{
  "feature": "LIVENESS",
  "config": {
    "face_liveness_method": "PASSIVE"
  }
},
\{
  "feature": "FACE_MATCH"
}
]
}

Example questionnaire workflow body:
\{
"workflow_label": "Source of Funds",
"features": [
\{
  "feature": "QUESTIONNAIRE",
  "config": {
    "questionnaire_uuid": "11111111-2222-3333-4444-555555555555",
    "review_questionnaire_manually": true
  }
}
]
}

Build the API call, handle validation errors, and return the workflow uuid from the response.`

\</pre>



      
      

Incolla in

Usa questo endpoint per creare workflow tramite API senza costruire tu stesso i nodi del grafo del workflow. Invia un array `features` nell'ordine in cui gli utenti devono completare i controlli. Didit lo converte internamente in un workflow basato su nodi e aggiunge automaticamente il passaggio finale di stato.Questo endpoint `POST /v3/workflows/` crea solo workflow **semplici e lineari**, cioè un elenco piatto di `features`. Per **ramificazioni, instradamento condizionale, Document AI e nodi di rifiuto o decisione**, usa il [formato a grafo](#grafo-dei-flussi-con-ramificazioni-e-nodi) descritto di seguito. Questo formato alimenta il builder visuale della console e gli strumenti workflow del [Didit MCP](https://docs.didit.me/it/integration/mcp/overview.md).Consulta [Configurazioni delle funzionalità del workflow](https://docs.didit.me/it/management-api/workflows/feature-configs.md) per ogni campo `config` accettato da ciascuna funzionalità.## Workflow KYC di base```json theme={null}
{
  "workflow_label": "Standard KYC",
  "features": [
    {
      "feature": "OCR",
      "config": {
        "documents_allowed": {
          "USA": {
            "DL": {
              "enabled": 1,
              "sides": 2
            }
          }
        },
        "duplicated_user_action": "REVIEW"
      }
    },
    {
      "feature": "LIVENESS",
      "config": {
        "face_liveness_method": "PASSIVE"
      }
    },
    {
      "feature": "FACE_MATCH"
    }
  ]
}
```## Workflow del questionarioCrea prima il questionario con `POST /v3/questionnaires/`. Poi usa il `questionnaire_id` restituito come `questionnaire_uuid`.```json theme={null}
{
  "workflow_label": "Source of Funds",
  "features": [
    {
      "feature": "QUESTIONNAIRE",
      "config": {
        "questionnaire_uuid": "11111111-2222-3333-4444-555555555555",
        "review_questionnaire_manually": true
      }
    }
  ]
}
```L'ordine delle funzionalità è importante. Ad esempio, inserisci `OCR` prima di `FACE_MATCH`, `NFC`, `DATABASE_VALIDATION` o dei controlli AML sugli utenti che dipendono dai dati del documento.## Riferimento della configurazione delle funzionalitàOgni elemento di `features[]` ha questa struttura:```json theme={null}
{
  "feature": "OCR",
  "label": "ID Verification",
  "config": {}
}
```L'oggetto `config` esatto dipende dalla funzionalità. Consulta l'elenco completo dei campi, i valori predefiniti, le impostazioni obbligatorie, gli intervalli di valori ed esempi in [Configurazioni delle funzionalità del workflow](https://docs.didit.me/it/management-api/workflows/feature-configs.md).Per `OCR`, ometti `config.documents_allowed`, invia `null` oppure invia `{}` per consentire ogni tipo di documento supportato. Includilo solo se vuoi limitare il workflow a paesi o tipi di documento specifici. Per `QUESTIONNAIRE`, `config.questionnaire_uuid` è obbligatorio.## Impostazioni opzionali del workflowPuoi includere questi campi di primo livello quando crei un workflow per controllare il comportamento delle sessioni. Sono tutti facoltativi e, se omessi, vengono usati i valori predefiniti del workflow.| Campo | Tipo | Descrizione |
| - | - | - |
| `is_white_label_enabled` | booleano | Abilita la personalizzazione White label per le sessioni create con questo workflow. Predefinito `false`. |
| `is_desktop_allowed` | booleano | Consente l'esecuzione del flusso di verifica sui browser desktop. Predefinito `false`. |
| `max_retry_attempts` | intero (0–10) | Numero massimo di nuovi tentativi consentiti dopo una verifica rifiutata. `0` blocca l'utente dopo il primo rifiuto. Predefinito `3`. |
| `retry_window_days` | intero (1–365) o `null` | Finestra mobile in giorni usata per contare i nuovi tentativi (`null` = limite complessivo senza finestra temporale). Predefinito `7`. |
| `session_expiration_time` | intero (3600–2419200) | Secondi prima che una sessione non completata scada. Minimo 1 ora, massimo 4 settimane. Predefinito `604800` (7 giorni). |
| `response_attributes` | oggetto o `null` | Limita i campi restituiti per ogni funzionalità, ad esempio `{"id_verifications": ["full_name", "document_number"], "face_matches": null}`. `null` o una chiave mancante restituisce tutti i campi. Un elenco vuoto restituisce solo `status`, `warnings`, `node_id`, `is_nfc_skipped` e `skip_reason`, mentre gli altri campi vengono restituiti come `null`. |```json theme={null}
{
  "workflow_label": "Standard KYC",
  "is_white_label_enabled": true,
  "is_desktop_allowed": true,
  "max_retry_attempts": 5,
  "retry_window_days": 14,
  "session_expiration_time": 86400,
  "features": [
    { "feature": "OCR" },
    { "feature": "LIVENESS" },
    { "feature": "FACE_MATCH" }
  ]
}
```## Grafo dei flussi con ramificazioni e nodiUna lista `features` lineare non può esprimere "se X allora Y". Per le **ramificazioni condizionali** (rifiutare in base a uno stato, instradare in base a un campo estratto, far dipendere un passaggio da una corrispondenza), un passaggio di richiesta di **Document AI** o i nodi terminali di **rifiuto/decisione**, i flussi di Didit usano un **grafo di nodi**: la stessa struttura prodotta dal builder visuale della console.> [!NOTE]
>
> Il modo più semplice per costruire un flusso a grafo è il **[Didit MCP](https://docs.didit.me/it/integration/mcp/overview.md)**: chiedi a un agente in linguaggio naturale ("dopo la verifica dell'identità, se la professione è ≈ 'Software Engineer', aggiungi un passaggio di prova dei fondi") e lui costruirà, convaliderà e salverà il grafo per te. La struttura descritta di seguito è quella prodotta da questi strumenti e dalla console.### Struttura del grafoUn grafo è `{ start_node, nodes }`. `nodes` è una mappa dall'ID del nodo al nodo. Gli archi sono impliciti: ogni nodo punta in avanti tramite `next` (incondizionato) e/o `branches[].goto` (condizionato).| Tipo di nodo | Struttura |
| - | - |
| `feature` | `{ "node_type": "feature", "feature": "OCR", "config": {…}, "next": "<id>", "branches": [] }` |
| `branch` | `{ "node_type": "branch", "branches": [ { "id", "logic": "and"\|"or", "rules": [<rule>], "goto": "<id>" } ], "next": "<fallback id>" }` |
| `status` (terminale) | `{ "node_type": "status", "session_status": "Approved"\|"Declined"\|"In Review"\|"Determine" }` |
| `action` | `{ "node_type": "action", "actions": [ { "type": "add_tag"\|"add_review"\|… } ], "next": "<id>" }` |
| `webhook` | `{ "node_type": "webhook", "config": { "url", "method" }, "branches": [], "next": "<id>" }` |`start_node` deve essere un nodo di tipo feature. I rami vengono valutati in ordine e **vince il primo che corrisponde**; un `rules: []` vuoto è il ramo generico "altrimenti" (va messo per ultimo). `session_status: "Determine"` decide automaticamente in base all'esito combinato delle funzionalità.> [!WARNING]
>
> **Ogni nodo di tipo branch deve avere un ramo "altrimenti" esplicito**: un ramo con `rules: []` che instrada le sessioni che non corrispondono a nessuna delle condizioni. Non affidarti al `next` del nodo branch come fallback: un `next` isolato viene visualizzato come un arco ambiguo nel generatore visuale ed è facile da dimenticare. Quando invii un nodo branch con un `next` e senza un ramo "altrimenti" esplicito, l'API **converte quel `next` in un ramo "altrimenti" esplicito** al posto tuo (quindi `{ …, "branches": [<conditions>], "next": "liveness" }` viene salvato come `branches: [<conditions>, { "id": "else", "rules": [], "goto": "liveness" }]`). Scrivi tu il ramo "altrimenti" per essere esplicito: la conversione è una rete di sicurezza, non un sostituto.### Regole di ramificazione e operatoriOgni regola confronta un **campo** con un **valore** tramite un **operatore**:```json theme={null}
{ "field": "kyc.extra_fields.profession", "operator": "fuzzy_match", "value": "Software Engineer", "score": 80 }
```- **I campi** indicano un esito di una funzionalità (`kyc.status`, `aml.status`, `document_ai.status`, `face_match.score`, …) oppure un valore estratto (`kyc.full_name`, `kyc.age`, `kyc.nationality`, `kyc.extra_fields.profession`, un campo di Document AI `document_ai.<key>`, `expected_details.*`). Un ramo può leggere solo un campo la cui funzionalità sia stata eseguita su ogni percorso che porta al ramo stesso.
- **Operatori:** `equals`, `not_equals`, `greater_than(_or_equals)`, `less_than(_or_equals)`, `contains`, `not_contains`, `in`, `not_in`, `is_empty`, `is_not_empty`, `regex` e **`fuzzy_match`** (solo per campi stringa; richiede un `score` da 0 a 100, cioè una soglia di somiglianza, ad esempio professione simile almeno all'80% a "Software Engineer").### Ramificazione in base al motivo per cui l'NFC non è stato eseguitoDopo un nodo `NFC`, usa `nfc.skip_reason` con `equals` o `in` per instradare le sessioni in base al motivo per cui la lettura del chip non è avvenuta. Il campo ha questi sette valori stabili:- `USER_SKIPPED`
- `DOCUMENT_WITHOUT_CHIP`
- `CHIP_CERTIFICATE_UNAVAILABLE`
- `DEVICE_WITHOUT_NFC`
- `INTEGRATION_WITHOUT_NFC_ACCESS`
- `MRZ_KEY_UNAVAILABLE`
- `JPKI_UNAVAILABLE`Consulta [Motivi di mancata NFC](https://docs.didit.me/it/core-technology/nfc-verification/warnings-nfc-verification.md#motivi-di-esclusione) per il significato di ciascun valore. Quando l'NFC ha esito positivo, `nfc.skip_reason` è `null`, quindi né una regola `equals` né una regola `in` per un motivo di salto corrispondono.Il campo viene risolto anche per i nodi NFC **saltati a causa di un controllo**. Questo include i casi in cui non esistono né un report né un avviso NFC, e i casi in cui `allow_nfc_skip` è `false` ma il gate sul documento o sul dispositivo impedisce l'esecuzione dell'NFC. Puoi quindi instradare queste sessioni senza fare affidamento su una voce di `nfc_verifications[]`.> [!NOTE]
>
> Le sessioni memorizzate prima che la capacità NFC diventasse tri-stato possono avere `has_device_nfc_support: false`. Una sessione web meno recente può quindi risolversi in `DEVICE_WITHOUT_NFC` invece che in `INTEGRATION_WITHOUT_NFC_ACCESS`.Questo grafo usa `equals` per il salto dell'utente, `in` per i salti dovuti a gate o capacità, e un ramo "altrimenti" esplicito per l'NFC riuscito. Il ramo si trova dopo il nodo `NFC`, e ogni destinazione punta in avanti verso un nodo terminale:```json theme={null}
{
  "start_node": "ocr",
  "nodes": {
    "ocr": {
      "node_type": "feature",
      "feature": "OCR",
      "next": "nfc"
    },
    "nfc": {
      "node_type": "feature",
      "feature": "NFC",
      "config": { "allow_nfc_skip": false },
      "next": "route_nfc"
    },
    "route_nfc": {
      "node_type": "branch",
      "branches": [
        {
          "id": "user_skipped",
          "logic": "and",
          "rules": [
            { "field": "nfc.skip_reason", "operator": "equals", "value": "USER_SKIPPED" }
          ],
          "goto": "manual_review"
        },
        {
          "id": "nfc_unavailable",
          "logic": "and",
          "rules": [
            {
              "field": "nfc.skip_reason",
              "operator": "in",
              "value": [
                "DOCUMENT_WITHOUT_CHIP",
                "CHIP_CERTIFICATE_UNAVAILABLE",
                "DEVICE_WITHOUT_NFC",
                "INTEGRATION_WITHOUT_NFC_ACCESS",
                "MRZ_KEY_UNAVAILABLE",
                "JPKI_UNAVAILABLE"
              ]
            }
          ],
          "goto": "manual_review"
        },
        {
          "id": "else",
          "logic": "and",
          "rules": [],
          "goto": "approved"
        }
      ]
    },
    "manual_review": { "node_type": "status", "session_status": "In Review" },
    "approved": { "node_type": "status", "session_status": "Approved" }
  }
}
```Per esprimere "l'NFC non è stato eseguito per nessun motivo noto", usa `in` con **tutti e sette** i valori. Mantieni un ramo "altrimenti" esplicito per l'NFC riuscito (`nfc.skip_reason: null`) e per qualsiasi valore futuro che il tuo grafo non riconosce ancora.### Document AI (ad esempio prova dei fondi)Un nodo di funzionalità `DOCUMENT_AI` richiede uno o più documenti ed estrae campi denominati:```json theme={null}
{
  "node_type": "feature",
  "feature": "DOCUMENT_AI",
  "label": "Proof of Funds",
  "config": {
    "document_ai_documents": [
      {
        "document_key": "proof_of_funds",
        "title": "Proof of Funds",
        "description": "Upload a recent bank statement.",
        "fields": [
          {
            "key": "account_holder",
            "name": "Account holder",
            "instruction": "The full name of the account holder as printed on the statement.",
            "type": "text",
            "required": true,
            "is_full_name": true
          },
          {
            "key": "closing_balance",
            "name": "Closing balance",
            "instruction": "The closing balance amount.",
            "type": "number",
            "required": true
          }
        ]
      }
    ],
    "document_ai_missing_required_fields_action": "REVIEW",
    "document_ai_name_mismatch_action": "REVIEW"
  },
  "next": "final_decision"
}
```Ogni campo configurato diventa un valore utilizzabile in una ramificazione, `document_ai.<key>`, e l'esito del passaggio è `document_ai.status`.Imposta `"is_full_name": true` sul campo che contiene il nome completo del titolare del documento. Il suo valore estratto viene confrontato con il nome completo dell'identità verificata (il nome ottenuto tramite OCR o verifica dell'identità, oppure i dettagli attesi della sessione), e `document_ai_name_mismatch_action` decide cosa succede in caso di mancata corrispondenza sotto `document_ai_name_match_score_threshold`. Il flag è **facoltativo**: un documento può non contrassegnare alcun campo, e **al massimo un campo per documento** può impostarlo. Consulta [Configurazioni delle funzionalità dei flussi → Document AI](https://docs.didit.me/it/management-api/workflows/feature-configs.md#document-ai) per ogni campo di configurazione di Document AI.### Endpoint e ciclo di vita della bozzaLa modifica del grafo avviene per applicazione e usa gli endpoint della console e della gestione (Bearer dell'utente + `read:workflows`/`write:workflows`). Un grafo può essere modificato solo su una versione in **bozza**, quindi il ciclo di vita è **crea bozza → imposta grafo → pubblica**:| Metodo | Percorso | Scopo |
| - | - | - |
| `GET` | `…/verification-settings/{settings_uuid}/workflow-graph/` | Grafo attuale + `status`/`is_editable` |
| `POST` | `…/application/{app}/workflow-graph/validate/` (body `{ "graph": … }`) | Validazione di prova (simulazione) |
| `GET` | `…/application/{app}/workflow-graph/field-definitions/` | Campi utilizzabili nei rami e operatori validi |
| `POST` | `…/verification-settings/{settings_uuid}/create-draft/` | Bozza modificabile a partire da una versione pubblicata |
| `PUT` | `…/verification-settings/{settings_uuid}/workflow-graph/` (body `{ "graph": … }`) | Salvare il grafo |
| `PATCH` | `…/verification-settings/{settings_uuid}/` (body `{ "status": "published" }`) | Pubblicare la bozza |#### Avvisi di validazioneL'endpoint di validazione risponde con `{ "is_valid": true, "graph": …, "warnings": [] }`. Un avviso è **consultivo**: il grafo è valido, può essere salvato e pubblicato anche in presenza di avvisi. Ogni voce indica il nodo di ramo e il braccio a cui si riferisce, così chi costruisce il flusso può individuare con precisione la condizione:```json theme={null}
{
  "type": "branch_outcome_contradiction",
  "node_id": "after_ocr",
  "branch_id": "b1",
  "severity": "warning",
  "message": "Branch 1 only matches sessions where ID Verification Status is Approved or In Review, but every path from it ends in the session outcome \"Declined\". Check the condition or the outcome.",
  "params": { "branchNumber": 1, "field": "ID Verification Status", "allowed": "Approved or In Review", "outcome": "Declined" }
}
```| `type` | Significato |
| - | - |
| `branch_outcome_contradiction` | La condizione del braccio e l'esito che ogni percorso da esso raggiunge sono in contrasto. Ad esempio, `kyc.status NOT EQUALS Declined` instradato verso un esito `Declined`. L'esito può trovarsi anche diversi passaggi più avanti nel percorso. |
| `branch_condition_unreachable` | Il braccio non può mai corrispondere, perché ogni percorso che raggiunge questo ramo limita già il campo di stato a valori che la condizione esclude. |
| `else_branch_unreachable` | Il braccio else non può mai essere eseguito: gli altri bracci coprono già tutti i valori che il campo può assumere in questo punto. |Un grafo non valido risponde con `400` includendo `errors` **e** un elenco `warnings` redatto al meglio. Solo `errors` bloccano il salvataggio.### Esempio svolto: indirizzare gli ingegneri del software verso la prova dei fondi> Dopo la verifica dell'identità: se è stata **rifiutata**, rifiuta la sessione; se la **professione del titolare ≈ "Software Engineer"** (somiglianza ≥80%), richiedi un documento di **prova dei fondi**; poi prendi una decisione finale.```json theme={null}
{
  "start_node": "ocr_1",
  "nodes": {
    "ocr_1": { "node_type": "feature", "feature": "OCR", "label": "ID Verification", "next": "after_ocr" },
    "after_ocr": {
      "node_type": "branch",
      "label": "Route after ID verification",
      "branches": [
        { "id": "declined", "logic": "or", "rules": [ { "field": "kyc.status", "operator": "equals", "value": "Declined" } ], "goto": "decline" },
        { "id": "software_engineer", "logic": "and", "rules": [ { "field": "kyc.extra_fields.profession", "operator": "fuzzy_match", "value": "Software Engineer", "score": 80 } ], "goto": "proof_of_funds" },
        { "id": "else", "logic": "and", "rules": [], "goto": "final_decision" }
      ]
    },
    "proof_of_funds": {
      "node_type": "feature",
      "feature": "DOCUMENT_AI",
      "label": "Proof of Funds",
      "config": {
        "document_ai_documents": [
          { "document_key": "proof_of_funds", "title": "Proof of Funds", "description": "Upload a recent bank statement.",
            "fields": [
              { "key": "account_holder", "name": "Account holder", "type": "text", "required": true },
              { "key": "closing_balance", "name": "Closing balance", "type": "number", "required": true }
            ] }
        ],
        "document_ai_missing_required_fields_action": "REVIEW"
      },
      "next": "final_decision"
    },
    "final_decision": { "node_type": "status", "session_status": "Determine", "label": "Final decision" },
    "decline": { "node_type": "status", "session_status": "Declined", "label": "Declined" }
  }
}
```Il nodo finale usa `session_status: "Determine"`, che **decide automaticamente** (approva / rifiuta / in revisione) in base alle funzionalità effettivamente eseguite nella sessione. Così sia il percorso della prova dei fondi sia quello else si risolvono correttamente, senza che alcun ramo legga `document_ai.status` (che non esiste nel percorso che salta Document AI). Ricorda: un ramo può leggere solo un campo la cui funzionalità è stata eseguita su *ogni* percorso che lo raggiunge.Invia il grafo come `{ "graph": <above> }` all'endpoint `…/workflow-graph/` (prima convalida). Per creare un **nuovo** workflow da zero usa `didit_workflow_set_graph` dell'MCP; per **modificare un workflow esistente** usa `didit_workflow_edit_graph` (piccole operazioni che conservano i nodi esistenti e le liste di elementi consentiti). Vedi [Didit MCP](https://docs.didit.me/it/integration/mcp/tools.md).


## OpenAPI

````yaml it/openapi-25.json POST /v3/workflows/
openapi: 3.0.0
info:
  version: 3.0.0
  title: Didit Verification API
  description: API di verifica dell'identità. Autenticati con l'intestazione x-api-key.
servers:
  - url: https://verification.didit.me
security: []
tags: []
paths:
  /v3/workflows/:
    post:
      tags:
        - Workflows
      summary: Crea workflow
      description: >-
        Crea un workflow semplice. Invia `features` nell'ordine di esecuzione
        (si applicano le regole di ordinamento); il valore predefinito è
        `published` v1. Il corpo accetta SOLO i campi elencati in
        `SimpleWorkflowRequest`: qualsiasi altra chiave (incluso
        `workflow_type`) viene rifiutata con un 400. Non idempotente: ogni
        chiamata crea un nuovo workflow. Massimo 50 workflow per applicazione.
      operationId: create_workflow
      requestBody:
        required: true
        content:
          application/json:
            schema:
              $ref: '#/components/schemas/SimpleWorkflowRequest'
            examples:
              Simple KYC:
                summary: >-
                  Workflow lineare di verifica dell'identità, rilevamento della
                  vitalità e confronto facciale
                value:
                  workflow_label: Standard KYC
                  features:
                    - feature: OCR
                      config:
                        duplicated_user_action: review
                    - feature: LIVENESS
                      config:
                        face_liveness_method: passive
                    - feature: FACE_MATCH
                      config:
                        face_match_score_decline_threshold: 40
                        face_match_score_review_threshold: 60
              KYC + AML:
                summary: Esegui AML dopo la verifica dell'identità
                value:
                  workflow_label: KYC with AML
                  features:
                    - feature: OCR
                    - feature: AML
                      config:
                        aml_score_review_threshold: 70
                        aml_score_approve_threshold: 30
              Questionnaire workflow:
                summary: Esegui un questionario creato con POST /v3/questionnaires/
                value:
                  workflow_label: Source of Funds
                  features:
                    - feature: QUESTIONNAIRE
                      config:
                        questionnaire_uuid: 11111111-2222-3333-4444-555555555555
                        review_questionnaire_manually: true
      responses:
        '201':
          description: >-
            Workflow creato. Il corpo della risposta contiene l'INTERA
            configurazione del workflow — la stessa serializzazione non
            elaborata di `VerificationSettings` restituita da `GET
            /v3/workflows/{settings_uuid}/` (~190 campi: ogni toggle di
            funzionalità, soglia, azione e il `workflow_graph` generato), non
            l'elemento sintetico dell'elenco. Salva lo `uuid` (esposto anche
            come `workflow_id`) per poterlo referenziare nelle successive
            chiamate di creazione della sessione. Nota: `features` è una stringa
            unita da `" + "` e i prezzi sono numeri JSON.
          content:
            application/json:
              schema:
                $ref: '#/components/schemas/WorkflowDetail'
              examples:
                Created (abbreviated):
                  summary: Campi chiave dell'oggetto di configurazione completo
                  value:
                    uuid: c3d4e5f6-7890-12cd-ef34-333333333333
                    workflow_id: c3d4e5f6-7890-12cd-ef34-333333333333
                    workflow_label: Standard KYC
                    workflow_type: null
                    is_default: false
                    is_archived: false
                    total_price: 0.15
                    min_price: 0.15
                    max_price: 0.15
                    features: OCR + LIVENESS + FACE_MATCH
                    is_simple_workflow: false
                    is_editable: false
                    workflow_url: https://verify.didit.me/u/w9TkPOx8RWG3UAz5ZRQGVQ
                    workflow_graph:
                      start_node: ocr
                      nodes: ... one node per feature plus a final status node ...
                    max_retry_attempts: 3
                    retry_window_days: 7
                    session_expiration_time: 604800
                    version: 1
                    status: published
                    published_at: '2026-05-17T10:30:00Z'
                    created_at: '2026-05-17T10:30:00Z'
                    updated_at: '2026-05-17T10:30:00Z'
        '400':
          description: >-
            Errore di validazione. Cause comuni: una chiave di primo livello
            esterna alla whitelist accettata (ad es. `workflow_type`), un valore
            `feature` sconosciuto, una funzionalità posizionata prima della
            relativa dipendenza (ad es. `FACE_MATCH` prima di `OCR`), una
            funzionalità passiva (`AML`, `IP_ANALYSIS`, `DATABASE_VALIDATION`,
            `KYB_KEY_PEOPLE`) utilizzata come primo passaggio, una funzionalità
            `QUESTIONNAIRE` il cui `config.questionnaire_uuid` non esiste per
            questa applicazione, una funzionalità `SIGNATURE` che andrebbe in
            produzione senza alcun documento da firmare, una funzionalità
            `SIGNATURE` con `signature_level: "aes"` mentre Advanced (AES) non è
            disponibile (`aes_not_available`, vedi `signature_availability` sul
            workflow), oppure una soglia fuori intervallo. Il corpo è un oggetto
            di errore di validazione DRF — le chiavi sono i nomi dei campi, i
            valori sono array di messaggi leggibili.
          content:
            application/json:
              examples:
                Advanced (AES) not available:
                  value:
                    features:
                      signature_level:
                        - >-
                          Node 'signature': Advanced (AES) is not available yet
                          (aes_not_available).
                Signature step with no document:
                  value:
                    features:
                      non_field_errors:
                        - >-
                          Node 'signature': Add a document to sign, or let each
                          session bring its own.
                Missing feature:
                  value:
                    features:
                      - At least one feature is required.
                Unknown top-level field:
                  value:
                    workflow_type:
                      - >-
                        The v3 workflow API only accepts `workflow_label`,
                        `is_default`, `status`, `features`,
                        `is_white_label_enabled`, `is_desktop_allowed`,
                        `max_retry_attempts`, `retry_window_days`,
                        `session_expiration_time`, `face_liveness_max_attempts`,
                        `face_match_max_attempts`.
                Unknown feature:
                  value:
                    features:
                      - >-
                        Invalid feature 'FOO'. Allowed values: AGE_ESTIMATION,
                        AML, DATABASE_VALIDATION, EMAIL_VERIFICATION,
                        FACE_MATCH, IP_ANALYSIS, KYB_DOCUMENTS, KYB_KEY_PEOPLE,
                        KYB_REGISTRY, LIVENESS, NFC, OCR, PHONE_VERIFICATION,
                        PROOF_OF_ADDRESS, QUESTIONNAIRE.
                Ordering:
                  value:
                    features:
                      - 'Feature ''FACE_MATCH'' must appear after one of: OCR.'
                Questionnaire not found:
                  value:
                    features: >-
                      Questionnaire '11111111-2222-3333-4444-555555555555' was
                      not found for this application. Create the questionnaire
                      first, then use its questionnaire_id.
        '403':
          description: >-
            La chiave API è mancante, malformata, scaduta o limitata a
            un'applicazione diversa. Esegui il refresh tramite l'[Auth
            API](/it/auth-api/get-credentials) e riprova.
          content:
            application/json:
              examples:
                Invalid token:
                  value:
                    detail: You do not have permission to perform this action.
        '415':
          description: >-
            Tipo di media non supportato. Imposta `Content-Type:
            application/json` e invia un corpo JSON.
        '429':
          description: Limite di frequenza superato. Riprova con backoff esponenziale.
        '500':
          description: >-
            Errore imprevisto del server durante la creazione del workflow. È
            sicuro effettuare un nuovo tentativo.
      security:
        - ApiKeyAuth: []
      x-codeSamples:
        - lang: curl
          label: cURL
          source: |-
            curl -X POST 'https://verification.didit.me/v3/workflows/' \
              -H 'x-api-key: YOUR_API_KEY' \
              -H 'Content-Type: application/json' \
              -d '{
                "workflow_label": "Standard KYC",
                "features": [
                  {"feature": "OCR"},
                  {"feature": "LIVENESS", "config": {"face_liveness_method": "passive"}},
                  {"feature": "FACE_MATCH", "config": {"face_match_score_decline_threshold": 40, "face_match_score_review_threshold": 60}}
                ]
              }'
        - lang: python
          label: Python
          source: |-
            from didit import Didit

            # Reads DIDIT_API_KEY.
            client = Didit()

            result = client.workflows.create(
                workflow_label="Standard KYC",
                features=[{
                    "feature": "OCR",
                    "config": {"duplicated_user_action": "review"},
                }, {
                    "feature": "LIVENESS",
                    "config": {"face_liveness_method": "passive"},
                }, {
                    "feature": "FACE_MATCH",
                    "config": {"face_match_score_decline_threshold": 40, "face_match_score_review_threshold": 60},
                }],
            )
            print(result)
        - lang: javascript
          label: Node.js
          source: |-
            import Didit from "@didit-protocol/sdk-node";

            // Reads DIDIT_API_KEY.
            const didit = new Didit();

            const result = await didit.workflows.create({
              workflow_label: "Standard KYC",
              features: [{
                feature: "OCR",
                config: { duplicated_user_action: "review" },
              }, {
                feature: "LIVENESS",
                config: { face_liveness_method: "passive" },
              }, {
                feature: "FACE_MATCH",
                config: { face_match_score_decline_threshold: 40, face_match_score_review_threshold: 60 },
              }],
            });
            console.log(result);
        - lang: go
          label: Go
          source: "ctx := context.Background()\n// Reads DIDIT_API_KEY.\nclient := didit.NewClient()\n\nresult, err := client.Workflows.Create(ctx, didit.SimpleWorkflowRequest{\n\tWorkflowLabel: didit.String(\"Standard KYC\"),\n\tFeatures: []didit.SimpleWorkflowFeature{{\n\t\tFeature: \"OCR\",\n\t\tConfig:  didit.Some(map[string]any{\"duplicated_user_action\": \"review\"}),\n\t}, {\n\t\tFeature: \"LIVENESS\",\n\t\tConfig:  didit.Some(map[string]any{\"face_liveness_method\": \"passive\"}),\n\t}, {\n\t\tFeature: \"FACE_MATCH\",\n\t\tConfig:  didit.Some(map[string]any{\"face_match_score_decline_threshold\": 40, \"face_match_score_review_threshold\": 60}),\n\t}},\n})\nif err != nil {\n\tlog.Fatal(err)\n}\nfmt.Printf(\"%+v\\n\", result)"
        - lang: java
          label: Java
          source: >-
            import java.util.List;

            import java.util.Map;

            import me.didit.Didit;

            import me.didit.types.SimpleWorkflowFeature;

            import me.didit.types.SimpleWorkflowRequest;


            // Reads DIDIT_API_KEY.

            Didit client = new Didit();


            var result =
            client.workflows().create(SimpleWorkflowRequest.builder()
                    .workflowLabel("Standard KYC")
                    .features(List.of(
                            SimpleWorkflowFeature.builder()
                                    .feature(SimpleWorkflowFeature.Feature.of("OCR"))
                                    .config(Map.of("duplicated_user_action", "review"))
                                    .build(),
                            SimpleWorkflowFeature.builder()
                                    .feature(SimpleWorkflowFeature.Feature.of("LIVENESS"))
                                    .config(Map.of("face_liveness_method", "passive"))
                                    .build(),
                            SimpleWorkflowFeature.builder()
                                    .feature(SimpleWorkflowFeature.Feature.of("FACE_MATCH"))
                                    .config(Map.of("face_match_score_decline_threshold", 40, "face_match_score_review_threshold", 60))
                                    .build()))
                    .build());
            System.out.println(result);
        - lang: php
          label: PHP
          source: |-
            <?php

            require 'vendor/autoload.php';

            use Didit\DiditClient;

            // Reads DIDIT_API_KEY.
            $didit = new DiditClient();

            $result = $didit->workflows->create([
                'workflow_label' => 'Standard KYC',
                'features' => [[
                    'feature' => 'OCR',
                    'config' => ['duplicated_user_action' => 'review'],
                ], [
                    'feature' => 'LIVENESS',
                    'config' => ['face_liveness_method' => 'passive'],
                ], [
                    'feature' => 'FACE_MATCH',
                    'config' => ['face_match_score_decline_threshold' => 40, 'face_match_score_review_threshold' => 60],
                ]],
            ]);
            print_r($result->toArray());
        - lang: ruby
          label: Ruby
          source: |-
            require "didit"

            # Reads DIDIT_API_KEY.
            didit = Didit::Client.new

            result = didit.workflows.create(
              workflow_label: "Standard KYC",
              features: [{
                feature: "OCR",
                config: { duplicated_user_action: "review" },
              }, {
                feature: "LIVENESS",
                config: { face_liveness_method: "passive" },
              }, {
                feature: "FACE_MATCH",
                config: { face_match_score_decline_threshold: 40, face_match_score_review_threshold: 60 },
              }]
            )
            puts result.to_h
        - lang: csharp
          label: .NET
          source: >-
            using System;

            using System.Collections.Generic;

            using System.Text.Json;

            using Didit;


            // Reads DIDIT_API_KEY.

            using var client = new DiditClient();


            var result = await client.Workflows.CreateAsync(new
            SimpleWorkflowRequest

            {
                WorkflowLabel = "Standard KYC",
                Features = new SimpleWorkflowFeature[]
                {
                    new()
                    {
                        Feature = "OCR",
                        Config = new Dictionary<string, object> { ["duplicated_user_action"] = "review" },
                    },
                    new()
                    {
                        Feature = "LIVENESS",
                        Config = new Dictionary<string, object> { ["face_liveness_method"] = "passive" },
                    },
                    new()
                    {
                        Feature = "FACE_MATCH",
                        Config = new Dictionary<string, object> { ["face_match_score_decline_threshold"] = 40, ["face_match_score_review_threshold"] = 60 },
                    },
                },
            });

            Console.WriteLine(JsonSerializer.Serialize(result));
components:
  schemas:
    SimpleWorkflowRequest:
      type: object
      description: >-
        Crea un workflow lineare semplice. L'API converte internamente questo
        elenco di funzionalità in un grafo di workflow basato su nodi. Whitelist
        dei campi RIGIDA: qualsiasi chiave non presente in questo elenco
        (incluso `workflow_type`) viene rifiutata con un 400.
      required:
        - features
      properties:
        workflow_label:
          type: string
          maxLength: 50
          default: ID Verification
          description: >-
            Nome visualizzato per il workflow (massimo 50 caratteri). Il valore
            predefinito è `"ID Verification"` se omesso: impostalo sempre
            affinché i workflow rimangano distinguibili.
          example: Standard KYC
        is_default:
          type: boolean
          description: Imposta questo workflow come predefinito per le nuove sessioni.
        status:
          type: string
          enum:
            - draft
            - published
          description: >-
            Ometti questo campo per creare un workflow pubblicato pronto per le
            sessioni. Usa `draft` solo quando vuoi salvare senza pubblicare.
        features:
          type: array
          minItems: 1
          description: >-
            Funzionalità di verifica in ordine di esecuzione. L'API collega
            ciascun elemento a quello successivo e aggiunge automaticamente un
            nodo di stato finale.
          items:
            $ref: '#/components/schemas/SimpleWorkflowFeature'
        is_white_label_enabled:
          type: boolean
          description: >-
            Abilita la personalizzazione White label per le sessioni create con
            questo workflow.
          default: false
        is_desktop_allowed:
          type: boolean
          description: Consenti l'esecuzione del flusso di verifica sui browser desktop.
          default: false
        max_retry_attempts:
          type: integer
          minimum: 0
          maximum: 10
          default: 3
          description: >-
            Numero massimo di nuovi tentativi consentiti dopo una verifica
            rifiutata. `0` blocca l'utente dopo il primo rifiuto.
        retry_window_days:
          type: integer
          minimum: 1
          maximum: 365
          nullable: true
          default: 7
          description: >-
            Finestra mobile in giorni utilizzata per contare i nuovi tentativi.
            `null` applica un limite complessivo.
        face_liveness_max_attempts:
          type: integer
          minimum: 1
          maximum: 3
          default: 3
          description: >-
            Numero massimo di invii per il rilevamento della vitalità per
            sessione. Valore predefinito 3: un tentativo iniziale più due nuovi
            tentativi. Questo valore viene copiato anche nei nodi di workflow
            generati per il rilevamento della vitalità e la stima dell'età, a
            meno che la configurazione a livello di funzionalità non lo
            sovrascriva.
        face_match_max_attempts:
          type: integer
          minimum: 1
          maximum: 3
          default: 3
          description: >-
            Numero massimo di invii per il confronto facciale per sessione.
            Valore predefinito 3: un tentativo iniziale più due nuovi tentativi.
            Questo valore viene copiato anche nei nodi di workflow generati per
            il confronto facciale, a meno che la configurazione a livello di
            funzionalità non lo sovrascriva.
        session_expiration_time:
          type: integer
          minimum: 3600
          maximum: 2419200
          default: 604800
          description: >-
            Secondi prima della scadenza di una sessione non completata. Minimo
            1 ora (3600), massimo 4 settimane (2419200).
    WorkflowDetail:
      type: object
      description: >-
        I campi sottostanti sono tipizzati; l'oggetto contiene inoltre ogni
        altra colonna della configurazione del workflow (attivazione delle
        funzionalità, soglie, azioni di avviso, documenti consentiti, callback)
        come proprietà aggiuntive. I campi non applicabili al `workflow_type`
        sono `null`.
      properties:
        uuid:
          type: string
          format: uuid
          description: >-
            Identificativo per versione (`settings_uuid`). Ogni versione di un
            workflow ha il proprio.
        workflow_id:
          type: string
          format: uuid
          description: >-
            Identificatore stabile condiviso da tutte le versioni del workflow.
            Le sessioni create con esso utilizzano l'ultima versione pubblicata.
        workflow_label:
          type: string
          description: Nome visualizzato del workflow.
        workflow_type:
          type: string
          nullable: true
          enum:
            - kyc
            - kyb
            - adaptive_age_verification
            - biometric_authentication
            - address_verification
            - questionnaire_verification
            - email_verification
            - phone_verification
            - null
          description: >-
            Modello di workflow. `null` per i workflow a grafo creati senza un
            modello.
        is_default:
          type: boolean
          description: Indica se questo è il workflow predefinito dell'applicazione.
        is_archived:
          type: boolean
          description: I workflow archiviati non possono avviare nuove sessioni.
        version:
          type: integer
          description: Numero di versione all'interno del workflow.
        status:
          type: string
          enum:
            - draft
            - published
          description: >-
            Le versioni in bozza sono modificabili; le versioni pubblicate sono
            immutabili e utilizzate per le nuove sessioni.
        published_at:
          type: string
          format: date-time
          nullable: true
          description: Quando è stata pubblicata questa versione. `null` per le bozze.
        is_simple_workflow:
          type: boolean
          description: >-
            `true` quando il workflow non presenta un `workflow_graph`
            personalizzato.
        is_editable:
          type: boolean
          description: '`true` solo per le versioni `draft`.'
        workflow_url:
          type: string
          format: uri
          description: URL pubblico che avvia una sessione con questo workflow.
        workflow_graph:
          type: object
          nullable: true
          additionalProperties: true
          description: >-
            Struttura a grafo del workflow (nodi e archi). `null` per i workflow
            semplici.
        features:
          type: string
          description: >-
            Funzionalità abilitate unite con `" + "`, ad esempio `"OCR +
            LIVENESS"`.
        total_price:
          type: number
          nullable: true
          description: >-
            Prezzo per sessione; per i workflow a grafo, il percorso più
            costoso.
        min_price:
          type: number
          nullable: true
          description: Prezzo del percorso più economico all'interno del workflow.
        max_price:
          type: number
          nullable: true
          description: Prezzo del percorso più costoso all'interno del workflow.
        special_add_on_prices:
          type: object
          nullable: true
          additionalProperties: true
          description: >-
            Prezzi delle funzionalità aggiuntive specifici per l'organizzazione,
            oppure `null` se non ne è impostato alcuno.
        max_retry_attempts:
          type: integer
          description: Nuovi tentativi consentiti dopo una verifica rifiutata.
        retry_window_days:
          type: integer
          nullable: true
          description: Giorni durante i quali sono consentiti i nuovi tentativi.
        session_expiration_time:
          type: integer
          description: Secondi prima della scadenza di una sessione non completata.
        questionnaire:
          type: object
          nullable: true
          additionalProperties: true
          description: Il questionario associato al workflow, o `null`.
        kyb_role_settings:
          type: array
          items:
            type: object
            additionalProperties: true
          description: Impostazioni di verifica per ruolo per le persone chiave KYB.
        created_at:
          type: string
          format: date-time
        updated_at:
          type: string
          format: date-time
        signature_availability:
          type: object
          description: >-
            Quali livelli di Firma elettronica possono essere utilizzati per
            firmare in questo momento. Verificalo prima di impostare
            `signature_level`. Restituito nei workflow `kyc`, `kyb` e a grafo.
          properties:
            simple:
              type: boolean
              description: Simple è sempre disponibile.
            aes:
              type: boolean
              description: >-
                Indica se la firma avanzata (FEA) può firmare ora. Finché è
                `false`, il salvataggio di un nuovo passaggio con
                `signature_level: "aes"` viene rifiutato con
                `aes_not_available`.
            qes:
              type: boolean
              description: >-
                Indica se la firma qualificata (FEQ) può firmare ora. È `false`
                finché non viene collegato un fornitore qualificato.
        is_bank_verification_enabled:
          type: boolean
          description: >-
            Indica se un workflow semplice di KYC o KYB esegue la verifica
            bancaria. Viene eseguita dopo i passaggi di contatto (e la verifica
            della posizione geografica) e prima della validazione su database,
            dello screening AML e dell'analisi IP. La verifica bancaria non è
            ancora disponibile a livello generale: l'attivazione viene rifiutata
            ovunque non sia abilitata.
        bank_verification_config:
          type: object
          additionalProperties: true
          description: >-
            Impostazioni della verifica bancaria di un workflow semplice, con le
            stesse chiavi accettate da un nodo `BANK_VERIFICATION` (ad esempio
            `bank_countries` e `bank_method_order`). Le chiavi sconosciute
            vengono ignorate. Ogni chiave è facoltativa e una chiave assente
            mantiene il proprio valore predefinito, pertanto `{}` indica tutti i
            valori predefiniti. Consulta [Impostazioni della verifica
            bancaria](/it/management-api/workflows/feature-configs#verifica-del-conto-bancario).
          example: {}
        is_geolocation_enabled:
          type: boolean
          description: >-
            Indica se un semplice workflow KYC esegue la verifica della
            posizione geografica. Viene eseguita dopo i passaggi di contatto e
            prima della verifica bancaria e della validazione su database. I
            workflow aziendali (KYB) non possono eseguirla.
        geolocation_config:
          type: object
          additionalProperties: true
          description: >-
            Impostazioni di verifica della posizione di un workflow semplice,
            con le stesse chiavi accettate da un nodo `GEOLOCATION` (ad esempio
            `is_geolocation_geofencing_enabled` e `geolocation_max_attempts`).
            Le chiavi sconosciute vengono ignorate. Ogni chiave è facoltativa e
            una chiave assente mantiene il valore predefinito, quindi `{}`
            indica tutti i valori predefiniti.
          example: {}
      additionalProperties: true
    SimpleWorkflowFeature:
      type: object
      required:
        - feature
      properties:
        feature:
          type: string
          description: >-
            Funzionalità da eseguire. Usa i valori in lettere maiuscole mostrati
            qui.
          enum:
            - OCR
            - NFC
            - LIVENESS
            - FACE_MATCH
            - PROOF_OF_ADDRESS
            - QUESTIONNAIRE
            - DOCUMENT_AI
            - PHONE_VERIFICATION
            - EMAIL_VERIFICATION
            - DATABASE_VALIDATION
            - AML
            - IP_ANALYSIS
            - AGE_ESTIMATION
            - KYB_REGISTRY
            - KYB_DOCUMENTS
            - KYB_KEY_PEOPLE
            - SIGNATURE
        config:
          type: object
          description: >-
            Impostazioni specifiche della funzionalità. Per `QUESTIONNAIRE`,
            includi `questionnaire_uuid` con il `questionnaire_id` restituito
            dall'endpoint di creazione del questionario. Per `SIGNATURE`, un
            workflow che passa in produzione richiede un documento da firmare:
            imposta `signature_documents_source` su `session` o `both` in modo
            che ogni sessione possa fornire il proprio, oppure invia `status:
            "draft"` e aggiungi i documenti prima della pubblicazione.
            `signature_level: "aes"` viene rifiutato con `aes_not_available`
            finché `signature_availability.aes` è `false` nel workflow. Per
            `OCR`, `methods` seleziona quali metodi di Verifica dell'identità
            ciascun paese può utilizzare (acquisizione del documento, verifica
            non documentale rispetto a fonti governative o altre fonti
            autorevoli e wallet di identità digitale); omettilo e ogni paese
            utilizzerà unicamente l'acquisizione del documento. Per `SIGNATURE`,
            consulta [Impostazioni della firma
            elettronica](/it/management-api/workflows/feature-configs#e-signature):
            `signature_level` accetta per ora `simple` (il valore predefinito,
            non richiede verifica dell'identità, email o telefono) e `aes`;
            inoltre, i documenti del workflow e i relativi campi vengono gestiti
            nella console, quindi tramite l'API invia documenti e campi per
            sessione con `signature_documents_source: "session"`.
          additionalProperties: true
        label:
          type: string
          description: Etichetta interna opzionale per questo nodo del workflow.
  securitySchemes:
    ApiKeyAuth:
      type: apiKey
      in: header
      name: x-api-key
      description: >-
        La chiave API della tua applicazione, da Developers -> API keys nella
        Business Console. La chiave primaria ha accesso completo. Una chiave con
        nome può avere un ambito definito: nessuno, lettura o scrittura per
        risorsa, limitata ad alcuni workflow o a sessioni approvate, a un elenco
        di indirizzi IP e a una data di scadenza. 401 indica che la chiave è
        errata, revocata o scaduta; 403 indica che non è stata inviata alcuna
        chiave, che la chiave non ha accesso a questa risorsa o azione, oppure
        che la richiesta proviene da un indirizzo non incluso nel suo elenco IP;
        404 su una route di sessione indica che la sessione non rientra nei
        workflow o negli stati previsti dalla chiave. Una chiave senza accesso
        ai media riceve gli URL di immagini, video e PDF come null, e una chiave
        senza permessi di scrittura sulle sessioni riceve link e token di
        sessione come null. Consulta https://docs.didit.me/console/api-keys.

````

This documentation is built and hosted on [Mintlify](https://mintlify.com), a developer documentation platform.