> ## 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.

# API para crear workflows: construye tu flujo KYC

> Crea vía API un workflow de verificación de Didit con funciones ordenadas: KYC, KYB, AML y biometría en un flujo. Pago por uso, sin contratos.



Prompt de la API para crear flujos
        Copiar el prompt al portapapeles



      
      

\<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>



      
      

Pegar en

Usa este endpoint para crear flujos de verificación (workflows) mediante la API sin tener que construir tú mismo los nodos del grafo del flujo. Envía un array `features` con las comprobaciones en el orden en que los usuarios deben completarlas. Didit lo convierte internamente en un flujo basado en nodos y añade automáticamente el paso de estado final.Este endpoint `POST /v3/workflows/` solo crea flujos **simples y lineales**, es decir, una lista plana de `features`. Para **ramificaciones, enrutamiento condicional, Document AI y nodos de rechazo o decisión**, usa el **[formato de grafo](#ramificaciones-y-flujos-basados-en-nodos-grafo)** basado en nodos que se documenta más abajo (es el que impulsa el editor visual de la consola y las herramientas de flujos del [MCP de Didit](https://docs.didit.me/es/integration/mcp/overview.md)).Consulta [Configuración de funciones del flujo](https://docs.didit.me/es/management-api/workflows/feature-configs.md) para ver todos los campos `config` que acepta cada función.## Flujo KYC básico```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"
    }
  ]
}
```## Flujo con cuestionarioPrimero crea el cuestionario con `POST /v3/questionnaires/`. Después usa el `questionnaire_id` devuelto como `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
      }
    }
  ]
}
```El orden de las funciones importa. Por ejemplo, coloca `OCR` antes de `FACE_MATCH`, `NFC`, `DATABASE_VALIDATION` o de las comprobaciones de AML de los usuarios que dependen de los datos del documento.## Referencia de configuración de funcionesCada elemento de `features[]` tiene esta estructura:```json theme={null}
{
  "feature": "OCR",
  "label": "ID Verification",
  "config": {}
}
```El objeto `config` exacto depende de la función. Revisa la lista completa de campos, los valores predeterminados, la configuración obligatoria, los rangos de valores y los ejemplos en [Configuración de funciones del flujo](https://docs.didit.me/es/management-api/workflows/feature-configs.md).Para `OCR`, omite `config.documents_allowed`, envía `null` o envía `{}` para permitir todos los tipos de documento admitidos. Inclúyelo solo cuando quieras restringir el flujo a países o tipos de documento concretos. Para `QUESTIONNAIRE`, `config.questionnaire_uuid` es obligatorio.## Configuración opcional del flujoAl crear un flujo puedes incluir cualquiera de estos campos de nivel superior para controlar el comportamiento de las sesiones. Todos son opcionales y, si se omiten, se usan los valores predeterminados del flujo.| Campo | Tipo | Descripción |
| - | - | - |
| `is_white_label_enabled` | boolean | Habilita la personalización de marca blanca en las sesiones creadas con este flujo. Valor predeterminado: `false`. |
| `is_desktop_allowed` | boolean | Permite que el proceso de verificación se ejecute en navegadores de escritorio. Valor predeterminado: `false`. |
| `max_retry_attempts` | integer (0–10) | Número máximo de reintentos permitidos tras una verificación rechazada. `0` bloquea al usuario tras el primer rechazo. Valor predeterminado: `3`. |
| `retry_window_days` | integer (1–365) o `null` | Ventana móvil en días que se usa para contar los reintentos (`null` = límite sin plazo). Valor predeterminado: `7`. |
| `session_expiration_time` | integer (3600–2419200) | Segundos que transcurren antes de que expire una sesión sin terminar. Mínimo 1 hora, máximo 4 semanas. Valor predeterminado: `604800` (7 días). |
| `response_attributes` | object o `null` | Limita los campos que se devuelven para cada función, por ejemplo `{"id_verifications": ["full_name", "document_number"], "face_matches": null}`. `null` o una clave ausente devuelve todos los campos. Una lista vacía devuelve solo `status`, `warnings`, `node_id`, `is_nfc_skipped` y `skip_reason`, y el resto de campos se devuelven como `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" }
  ]
}
```## Ramificaciones y flujos basados en nodos (grafo)Una lista lineal de `features` no puede expresar "si X, entonces Y". Para la **ramificación condicional** (rechazar según un estado, enrutar según un campo extraído, condicionar un paso a una coincidencia), un paso de solicitud de **Document AI** o nodos terminales de **rechazo/decisión**, los flujos de Didit usan un **grafo de nodos**, la misma estructura que genera el constructor visual de la consola.> [!NOTE]
>
> La forma más sencilla de crear un flujo basado en grafo es el **[Didit MCP](https://docs.didit.me/es/integration/mcp/overview.md)**: pídele a un agente en lenguaje natural ("después de la verificación de identidad, si la profesión es ≈ 'Software Engineer', añade un paso de prueba de fondos") y construirá, validará y guardará el grafo por ti. La estructura que se describe a continuación es la que generan esas herramientas (y la consola).### Estructura del grafoUn grafo es `{ start_node, nodes }`. `nodes` es un mapa de id de nodo → nodo. Las aristas son implícitas: cada nodo apunta hacia delante mediante `next` (incondicional) o `branches[].goto` (condicional), o ambos.| Tipo de nodo | Estructura |
| - | - |
| `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` (terminal) | `{ "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` debe ser un nodo de función. Las ramas se evalúan en orden y **gana la primera coincidencia**; un `rules: []` vacío es el "else" que recoge todo lo demás (déjalo al final). `session_status: "Determine"` decide automáticamente a partir de los resultados combinados de las funciones.> [!WARNING]
>
> **Todo nodo de ramificación debe tener una rama else explícita**: una rama con `rules: []` que enrute las sesiones que no cumplen ninguna de las condiciones. No dependas del `next` del nodo de ramificación como alternativa: un `next` suelto aparece como una arista ambigua en el constructor y es fácil omitirlo. Cuando envías un nodo de ramificación con un `next` y sin rama else, la API **convierte ese `next` en una rama else explícita** por ti (así, `{ …, "branches": [<conditions>], "next": "liveness" }` se guarda como `branches: [<conditions>, { "id": "else", "rules": [], "goto": "liveness" }]`). Crea tú mismo la rama else para que sea explícita; la conversión es una red de seguridad, no un sustituto.### Reglas de ramificación y operadoresCada regla compara un **campo** con un **valor** mediante un **operador**:```json theme={null}
{ "field": "kyc.extra_fields.profession", "operator": "fuzzy_match", "value": "Software Engineer", "score": 80 }
```- **Campos**: nombran un resultado de función (`kyc.status`, `aml.status`, `document_ai.status`, `face_match.score`, …) o un valor extraído (`kyc.full_name`, `kyc.age`, `kyc.nationality`, `kyc.extra_fields.profession`, un campo de Document AI `document_ai.<key>`, `expected_details.*`). Una rama solo puede leer un campo cuya función se haya ejecutado en todas las rutas que llegan a ella.
- **Operadores:** `equals`, `not_equals`, `greater_than(_or_equals)`, `less_than(_or_equals)`, `contains`, `not_contains`, `in`, `not_in`, `is_empty`, `is_not_empty`, `regex` y **`fuzzy_match`** (solo campos de texto; requiere un `score` de 0 a 100, un umbral de similitud; por ejemplo, profesión con una similitud ≥ 80% con "Software Engineer").### Ramificar según el motivo por el que no se ejecutó NFCDespués de un nodo `NFC`, usa `nfc.skip_reason` con `equals` o `in` para enrutar las sesiones según el motivo por el que no se produjo la lectura del chip. El campo tiene estos siete valores estables:- `USER_SKIPPED`
- `DOCUMENT_WITHOUT_CHIP`
- `CHIP_CERTIFICATE_UNAVAILABLE`
- `DEVICE_WITHOUT_NFC`
- `INTEGRATION_WITHOUT_NFC_ACCESS`
- `MRZ_KEY_UNAVAILABLE`
- `JPKI_UNAVAILABLE`Consulta [Motivos de omisión de NFC](https://docs.didit.me/es/core-technology/nfc-verification/warnings-nfc-verification.md#motivos-de-omisión) para saber qué significa cada valor. Cuando NFC se completa correctamente, `nfc.skip_reason` es `null`, por lo que no coincide ninguna regla `equals` ni `in` para un motivo de omisión.El campo también se resuelve para los nodos NFC **omitidos por una condición previa**. Esto incluye los casos en los que no existe ningún informe ni advertencia de NFC, y los casos en los que `allow_nfc_skip` es `false` pero la condición del documento o del dispositivo impide que NFC se ejecute. Por tanto, puedes enrutar estas sesiones sin depender de una entrada de `nfc_verifications[]`.> [!NOTE]
>
> Las sesiones almacenadas antes de que la capacidad NFC pasara a tener tres estados pueden tener `has_device_nfc_support: false`. Por tanto, una sesión web antigua puede resolverse como `DEVICE_WITHOUT_NFC` en lugar de `INTEGRATION_WITHOUT_NFC_ACCESS`.Este grafo usa `equals` para una omisión por parte del usuario, `in` para las omisiones por condición previa o por capacidad, y una rama else explícita para el NFC completado correctamente. La ramificación va después del nodo `NFC` y cada destino apunta hacia delante a un nodo terminal:```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" }
  }
}
```Para expresar "no se realizó NFC por ningún motivo conocido", usa `in` con **los siete** valores. Mantén una rama else explícita para el NFC completado correctamente (`nfc.skip_reason: null`) y para cualquier valor futuro que tu grafo aún no reconozca.### Document AI (p. ej., prueba de fondos)Un nodo de función `DOCUMENT_AI` solicita uno o más documentos y extrae campos con nombre:```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"
}
```Cada campo configurado se convierte en un valor `document_ai.<key>` utilizable en ramificaciones, y el resultado del paso es `document_ai.status`.Define `"is_full_name": true` en el campo que contiene el nombre completo del titular del documento. Su valor extraído se compara con el nombre completo de la identidad verificada (el nombre del OCR o de la verificación de identidad, o los datos esperados de la sesión), y `document_ai_name_mismatch_action` decide qué ocurre ante una discrepancia con una puntuación inferior a `document_ai_name_match_score_threshold`. Este indicador es **opcional** (un documento puede no marcar ningún campo) y **como máximo un campo por documento** puede definirlo. Consulta [Configuración de funciones del flujo → Document AI](https://docs.didit.me/es/management-api/workflows/feature-configs.md#document-ai) para ver todos los campos de configuración de Document AI.### Endpoints y ciclo de vida del borradorLa edición del grafo se realiza por aplicación y usa los endpoints de la consola o de gestión (Bearer de usuario + `read:workflows`/`write:workflows`). Un grafo solo puede editarse en una versión en **borrador**, por lo que el ciclo de vida es **crear borrador → definir grafo → publicar**:| Método | Ruta | Propósito |
| - | - | - |
| `GET` | `…/verification-settings/{settings_uuid}/workflow-graph/` | Grafo actual + `status`/`is_editable` |
| `POST` | `…/application/{app}/workflow-graph/validate/` (cuerpo `{ "graph": … }`) | Validación de prueba, sin guardar |
| `GET` | `…/application/{app}/workflow-graph/field-definitions/` | Campos utilizables en ramas + operadores válidos |
| `POST` | `…/verification-settings/{settings_uuid}/create-draft/` | Borrador editable a partir de una versión publicada |
| `PUT` | `…/verification-settings/{settings_uuid}/workflow-graph/` (cuerpo `{ "graph": … }`) | Guardar el grafo |
| `PATCH` | `…/verification-settings/{settings_uuid}/` (cuerpo `{ "status": "published" }`) | Publicar el borrador |#### Advertencias de validaciónEl endpoint de validación responde `{ "is_valid": true, "graph": …, "warnings": [] }`. Una advertencia es **informativa**: el grafo es válido, se guarda y se publica aunque haya advertencias. Cada entrada indica el nodo de rama y el brazo al que se refiere, para que un editor de flujos pueda señalar la condición exacta:```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` | Qué significa |
| - | - |
| `branch_outcome_contradiction` | La condición del brazo y el resultado al que llegan todas las rutas que parten de él se contradicen; por ejemplo, `kyc.status NOT EQUALS Declined` dirigido a un resultado `Declined`. El resultado puede estar varios pasos más adelante en la ruta. |
| `branch_condition_unreachable` | El brazo nunca puede coincidir, porque todas las rutas que llegan a esta rama ya limitan ese campo de estado a valores que la condición excluye. |
| `else_branch_unreachable` | El brazo else nunca puede ejecutarse: los demás brazos ya cubren todos los valores que el campo puede tener en ese punto. |Un grafo no válido responde `400` con `errors` **y** una lista `warnings` elaborada en la medida de lo posible. Solo `errors` impide guardar.### Ejemplo práctico: dirigir a los ingenieros de software a la prueba de fondos> Después de la verificación de identidad: si fue **rechazada**, rechaza la sesión; si la **profesión del titular ≈ "Software Engineer"** (≥80% de similitud), solicita un documento de **prueba de fondos**; después, toma una decisión final.```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" }
  }
}
```El nodo final usa `session_status: "Determine"`, que **decide automáticamente** (aprobar / rechazar / en revisión) en función de las funciones que realmente se ejecutaron en la sesión. Así, tanto la ruta de prueba de fondos como la ruta else se resuelven correctamente sin que ninguna rama lea `document_ai.status` (que no existe en la ruta que omite Document AI). Recuerda: una rama solo puede leer un campo cuya función se haya ejecutado en *todas* las rutas que llegan a ella.Envíalo como `{ "graph": <above> }` al endpoint `…/workflow-graph/` (valídalo primero). Para crear un flujo **nuevo** desde cero, usa `didit_workflow_set_graph` del MCP; para **modificar un flujo existente**, usa `didit_workflow_edit_graph` (operaciones pequeñas que conservan los nodos existentes y las listas de permitidos). Consulta el [MCP de Didit](https://docs.didit.me/es/integration/mcp/tools.md).


## OpenAPI

````yaml es/openapi-25.json POST /v3/workflows/
openapi: 3.0.0
info:
  version: 3.0.0
  title: Didit Verification API
  description: API de verificación de identidad. Autentícate con el encabezado x-api-key.
servers:
  - url: https://verification.didit.me
security: []
tags: []
paths:
  /v3/workflows/:
    post:
      tags:
        - Workflows
      summary: Crear flujo
      description: >-
        Crea un flujo simple. Envía `features` en el orden de ejecución (se
        aplican reglas de orden); por defecto es `published` v1. El cuerpo
        acepta SOLO los campos indicados en `SimpleWorkflowRequest`: cualquier
        otra clave (incluida `workflow_type`) se rechaza con un 400. No es
        idempotente: cada llamada crea un flujo nuevo. Máximo 50 flujos por
        aplicación.
      operationId: create_workflow
      requestBody:
        required: true
        content:
          application/json:
            schema:
              $ref: '#/components/schemas/SimpleWorkflowRequest'
            examples:
              Simple KYC:
                summary: >-
                  Flujo lineal de verificación de identidad, prueba de vida y
                  coincidencia facial
                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: >-
                  Ejecutar el análisis AML después de la verificación de
                  identidad
                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: Ejecutar un cuestionario creado 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: >-
            Flujo creado. El cuerpo de la respuesta es la configuración COMPLETA
            del flujo: la misma serialización sin procesar de
            `VerificationSettings` que devuelve `GET
            /v3/workflows/{settings_uuid}/` (~190 campos: cada interruptor de
            función, umbral, acción y el `workflow_graph` generado), no el
            elemento compacto de la lista. Guarda el `uuid` (también expuesto
            como `workflow_id`) para poder referenciarlo en llamadas posteriores
            de creación de sesiones. Nota: `features` es una cadena unida con `"
            + "` y los precios son números JSON.
          content:
            application/json:
              schema:
                $ref: '#/components/schemas/WorkflowDetail'
              examples:
                Created (abbreviated):
                  summary: Campos clave del objeto de configuración 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: >-
            Error de validación. Causas comunes: una clave de nivel superior
            fuera de la lista de claves aceptadas (p. ej., `workflow_type`), un
            valor de `feature` desconocido, una función colocada antes de su
            dependencia (p. ej., `FACE_MATCH` antes de `OCR`), una función
            pasiva (`AML`, `IP_ANALYSIS`, `DATABASE_VALIDATION`,
            `KYB_KEY_PEOPLE`) usada como primer paso, una función
            `QUESTIONNAIRE` cuyo `config.questionnaire_uuid` no existe para esta
            aplicación, una función `SIGNATURE` que pasaría a producción sin
            ningún documento que firmar, una función `SIGNATURE` con
            `signature_level: "aes"` cuando Advanced (AES) no está disponible
            (`aes_not_available`, consulta `signature_availability` en el flujo)
            o un umbral fuera de rango. El cuerpo es un objeto de error de
            validación de DRF: las claves son nombres de campo y los valores son
            arrays de mensajes legibles.
          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: >-
            Falta la clave de API, tiene un formato incorrecto, ha expirado o
            está limitada a otra aplicación. Renuévala mediante la [Auth
            API](/es/auth-api/get-credentials) y vuelve a intentarlo.
          content:
            application/json:
              examples:
                Invalid token:
                  value:
                    detail: You do not have permission to perform this action.
        '415':
          description: >-
            Tipo de contenido no admitido. Define `Content-Type:
            application/json` y envía un cuerpo JSON.
        '429':
          description: Límite de solicitudes superado. Reintenta con retroceso exponencial.
        '500':
          description: >-
            Error inesperado del servidor al crear el flujo. Se puede reintentar
            sin riesgo.
      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 flujo de verificación (workflow) lineal simple. La API convierte
        internamente esta lista de funciones en un grafo de flujo basado en
        nodos. Lista ESTRICTA de campos permitidos: cualquier clave que no
        figure aquí (incluida `workflow_type`) se rechaza con un 400.
      required:
        - features
      properties:
        workflow_label:
          type: string
          maxLength: 50
          default: ID Verification
          description: >-
            Nombre visible del flujo de verificación (máx. 50 caracteres). Si se
            omite, el valor predeterminado es `"ID Verification"`: defínelo
            siempre para que los flujos se puedan distinguir.
          example: Standard KYC
        is_default:
          type: boolean
          description: >-
            Establece este flujo de verificación (workflow) como predeterminado
            para las nuevas sesiones.
        status:
          type: string
          enum:
            - draft
            - published
          description: >-
            Omite este campo para crear un flujo publicado y listo para
            sesiones. Usa `draft` solo cuando quieras guardarlo sin publicarlo.
        features:
          type: array
          minItems: 1
          description: >-
            Funciones de verificación en orden de ejecución. La API enlaza cada
            elemento con el siguiente y añade automáticamente un nodo de estado
            final.
          items:
            $ref: '#/components/schemas/SimpleWorkflowFeature'
        is_white_label_enabled:
          type: boolean
          description: >-
            Activa la personalización de marca blanca para las sesiones creadas
            con este flujo de verificación (workflow).
          default: false
        is_desktop_allowed:
          type: boolean
          description: >-
            Permite que el flujo de verificación se ejecute en navegadores de
            escritorio.
          default: false
        max_retry_attempts:
          type: integer
          minimum: 0
          maximum: 10
          default: 3
          description: >-
            Número máximo de reintentos permitidos tras una verificación
            rechazada. `0` bloquea al usuario tras el primer rechazo.
        retry_window_days:
          type: integer
          minimum: 1
          maximum: 365
          nullable: true
          default: 7
          description: >-
            Ventana móvil en días que se usa para contar los reintentos. `null`
            aplica un límite sobre todo el historial.
        face_liveness_max_attempts:
          type: integer
          minimum: 1
          maximum: 3
          default: 3
          description: >-
            Número máximo de envíos de prueba de vida por sesión. Valor
            predeterminado: 3 (un intento inicial más dos reintentos). Este
            valor también se copia en los nodos de prueba de vida y de
            estimación de edad generados en el flujo, salvo que la configuración
            a nivel de función lo sobrescriba.
        face_match_max_attempts:
          type: integer
          minimum: 1
          maximum: 3
          default: 3
          description: >-
            Número máximo de envíos de coincidencia facial por sesión. Valor
            predeterminado: 3 (un intento inicial más dos reintentos). Este
            valor también se copia en los nodos de coincidencia facial generados
            en el flujo, salvo que la configuración a nivel de función lo
            sobrescriba.
        session_expiration_time:
          type: integer
          minimum: 3600
          maximum: 2419200
          default: 604800
          description: >-
            Segundos antes de que expire una sesión sin finalizar. Mínimo 1 hora
            (3600), máximo 4 semanas (2419200).
    WorkflowDetail:
      type: object
      description: >-
        Los campos siguientes están tipados; el objeto también incluye todas las
        demás columnas de la configuración del flujo (activación de funciones,
        umbrales, acciones ante advertencias, documentos permitidos, callbacks)
        como propiedades adicionales. Los campos que no se aplican al
        `workflow_type` son `null`.
      properties:
        uuid:
          type: string
          format: uuid
          description: >-
            Identificador por versión (`settings_uuid`). Cada versión de un
            flujo tiene el suyo.
        workflow_id:
          type: string
          format: uuid
          description: >-
            Identificador estable compartido por todas las versiones del flujo.
            Las sesiones creadas con él usan la última versión publicada.
        workflow_label:
          type: string
          description: Nombre visible del flujo.
        workflow_type:
          type: string
          nullable: true
          enum:
            - kyc
            - kyb
            - adaptive_age_verification
            - biometric_authentication
            - address_verification
            - questionnaire_verification
            - email_verification
            - phone_verification
            - null
          description: >-
            Plantilla del flujo. `null` en los flujos de grafo creados sin
            plantilla.
        is_default:
          type: boolean
          description: Si este es el flujo predeterminado de la aplicación.
        is_archived:
          type: boolean
          description: Los flujos archivados no pueden iniciar nuevas sesiones.
        version:
          type: integer
          description: Número de versión dentro del flujo.
        status:
          type: string
          enum:
            - draft
            - published
          description: >-
            Las versiones en borrador son editables; las versiones publicadas
            son inmutables y se usan para las nuevas sesiones.
        published_at:
          type: string
          format: date-time
          nullable: true
          description: Cuándo se publicó esta versión. `null` para los borradores.
        is_simple_workflow:
          type: boolean
          description: '`true` cuando el flujo no tiene un `workflow_graph` personalizado.'
        is_editable:
          type: boolean
          description: '`true` solo para las versiones `draft`.'
        workflow_url:
          type: string
          format: uri
          description: URL pública que inicia una sesión con este flujo.
        workflow_graph:
          type: object
          nullable: true
          additionalProperties: true
          description: >-
            Estructura de grafo del flujo de verificación (workflow) (nodos y
            aristas). `null` para flujos simples.
        features:
          type: string
          description: >-
            Funciones habilitadas unidas con `" + "`, por ejemplo `"OCR +
            LIVENESS"`.
        total_price:
          type: number
          nullable: true
          description: Precio por sesión; en los flujos de tipo grafo, la ruta más cara.
        min_price:
          type: number
          nullable: true
          description: Precio de la ruta más económica del flujo.
        max_price:
          type: number
          nullable: true
          description: Precio de la ruta más cara del flujo.
        special_add_on_prices:
          type: object
          nullable: true
          additionalProperties: true
          description: >-
            Precios de complementos específicos de la organización, o `null` si
            no hay ninguno configurado.
        max_retry_attempts:
          type: integer
          description: Reintentos permitidos tras una verificación rechazada.
        retry_window_days:
          type: integer
          nullable: true
          description: Días durante los que se permiten los reintentos.
        session_expiration_time:
          type: integer
          description: Segundos antes de que expire una sesión sin finalizar.
        questionnaire:
          type: object
          nullable: true
          additionalProperties: true
          description: El cuestionario asociado al flujo, o `null`.
        kyb_role_settings:
          type: array
          items:
            type: object
            additionalProperties: true
          description: >-
            Configuración de verificación por rol para las personas clave de
            KYB.
        created_at:
          type: string
          format: date-time
        updated_at:
          type: string
          format: date-time
        signature_availability:
          type: object
          description: >-
            Qué niveles de Firma electrónica pueden firmar en este momento.
            Consúltalo antes de definir `signature_level`. Se devuelve en los
            flujos `kyc`, `kyb` y de grafo.
          properties:
            simple:
              type: boolean
              description: Simple siempre está disponible.
            aes:
              type: boolean
              description: >-
                Indica si Avanzada (AES) ya puede firmar. Mientras sea `false`,
                guardar un nuevo paso con `signature_level: "aes"` se rechaza
                con `aes_not_available`.
            qes:
              type: boolean
              description: >-
                Indica si Cualificada (QES) ya puede firmar. Es `false` hasta
                que se conecte un proveedor cualificado.
        is_bank_verification_enabled:
          type: boolean
          description: >-
            Indica si un flujo de verificación (workflow) simple de KYC o KYB
            ejecuta la verificación bancaria. Se ejecuta después de los pasos de
            contacto (y de la verificación de ubicación) y antes de la
            validación de base de datos, el análisis AML y el análisis de IP. La
            verificación bancaria aún no está disponible de forma general: se
            rechaza su activación donde no esté habilitada.
        bank_verification_config:
          type: object
          additionalProperties: true
          description: >-
            Configuración de verificación bancaria de un flujo simple, con las
            mismas claves que admite un nodo `BANK_VERIFICATION` (por ejemplo,
            `bank_countries` y `bank_method_order`). Las claves desconocidas se
            descartan. Todas las claves son opcionales y una clave ausente
            mantiene su valor predeterminado, por lo que `{}` significa todos
            los valores predeterminados. Consulta [Configuración de verificación
            bancaria](/es/management-api/workflows/feature-configs#verificación-bancaria).
          example: {}
        is_geolocation_enabled:
          type: boolean
          description: >-
            Indica si un flujo de verificación (workflow) simple de KYC ejecuta
            la verificación de ubicación. Se ejecuta después de los pasos de
            contacto y antes de la verificación bancaria y la validación de base
            de datos. Los flujos de empresas (KYB) no pueden ejecutarla.
        geolocation_config:
          type: object
          additionalProperties: true
          description: >-
            Configuración de verificación de ubicación de un flujo simple, con
            las mismas claves que admite un nodo `GEOLOCATION` (por ejemplo,
            `is_geolocation_geofencing_enabled` y `geolocation_max_attempts`).
            Las claves desconocidas se descartan. Todas las claves son
            opcionales y una clave ausente conserva su valor predeterminado, por
            lo que `{}` significa todos los valores predeterminados.
          example: {}
      additionalProperties: true
    SimpleWorkflowFeature:
      type: object
      required:
        - feature
      properties:
        feature:
          type: string
          description: >-
            Función que se va a ejecutar. Usa los valores en mayúsculas que se
            muestran aquí.
          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: >-
            Configuración específica de la función. Para `QUESTIONNAIRE`,
            incluye `questionnaire_uuid` con el `questionnaire_id` que devuelve
            el endpoint de creación de cuestionarios. Para `SIGNATURE`, un flujo
            que pasa a producción necesita un documento para firmar: establece
            `signature_documents_source` en `session` o `both` para que cada
            sesión pueda aportar el suyo, o envía `status: "draft"` y añade los
            documentos antes de publicar. `signature_level: "aes"` se rechaza
            con `aes_not_available` mientras `signature_availability.aes` sea
            `false` en el flujo. Para `OCR`, `methods` selecciona qué métodos de
            verificación de identidad puede usar cada país (captura de
            documento, consulta sin documento contra una fuente gubernamental u
            otra fuente oficial, y wallets de identidad digital); si lo omites,
            todos los países usan solo captura de documento. Para `SIGNATURE`,
            consulta la [configuración de firma
            electrónica](/es/management-api/workflows/feature-configs#firma-electrónica):
            por ahora `signature_level` acepta `simple` (el valor
            predeterminado, sin necesidad de verificación de identidad, correo
            electrónico ni teléfono) y `aes`, y los documentos del flujo y sus
            campos se gestionan en la consola, así que a través de la API envía
            los documentos y campos por sesión con `signature_documents_source:
            "session"`.
          additionalProperties: true
        label:
          type: string
          description: Etiqueta interna opcional para este nodo del flujo.
  securitySchemes:
    ApiKeyAuth:
      type: apiKey
      in: header
      name: x-api-key
      description: >-
        La clave de API de tu aplicación, que encontrarás en Developers -> API
        keys en la Business Console. La clave principal tiene acceso completo.
        Una clave con nombre puede tener un alcance limitado: ninguno, lectura o
        escritura por recurso, limitada a algunos flujos o a sesiones aprobadas,
        a una lista de direcciones IP y a una fecha de caducidad. 401 significa
        que la clave es incorrecta, se revocó o expiró; 403 significa que no se
        envió ninguna clave, que la clave no tiene acceso a este recurso o
        acción, o que la solicitud provino de una dirección fuera de su lista de
        IP; 404 en una ruta de sesión significa que la sesión está fuera de los
        flujos o estados de la clave. Una clave sin acceso a contenido
        multimedia recibe las URL de imágenes, videos y PDF como null, y una
        clave sin permiso de escritura en sesiones recibe los enlaces y tokens
        de sesión como null. Consulta https://docs.didit.me/console/api-keys.

````

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