DonutworkHelp Center
Documentazione Donutwork

Workflow

Execute workflow runs, automate multi-stage operational tasks, and monitor execution lifecycle.

Workflow API

The Workflow API provides an engine for orchestrating complex, multi-stage business processes. By defining structured workflows, organizations can automate repetitive operational tasks, ensuring deterministic execution and full auditability. The engine supports dynamic payload injection for contextualized automation across the platform.


Workflow Management

List Workflows

Retrieve a comprehensive directory of all registered business workflows for your organization. This inventory includes the operational status, task count, and administrative metadata for each automation.

GET
/2026-02-01/workflow.json
Required permissionworkflow:read

Path, query, and header parameters

sizeinteger· query
Maximum records per page (max 100).
pageinteger· query
Target page index.

Responses

Workflow inventory successfully retrieved.

{
  "entities": "Workflow",
  "count": 12,
  "per_page": 100,
  "pages": {
    "current": 1,
    "max": 1
  },
  "elements": [
    {
      "id": "6724bffd...",
      "name": "Enterprise Onboarding Logic",
      "active": true,
      "tasks_count": 8,
      "description": "Orchestrates account provisioning and initial compliance checks."
    }
  ]
}

Request examples

curl --request GET 'https://api.hub.donutwork.com/2026-02-01/workflow.json' \
  --header 'Authorization: Bearer YOUR_API_KEY'
const response = await fetch('https://api.hub.donutwork.com/2026-02-01/workflow.json', {
  method: 'GET',
  headers: {
    Authorization: 'Bearer ' + process.env.DONUTWORK_API_KEY,
  },
});
const requestId = response.headers.get('x-request-id');
if (!response.ok) {
  const error = await response.text();
  throw new Error(`${response.status} ${error} [${requestId}]`);
}
const contentType = response.headers.get('content-type') ?? '';
const data = response.status === 204 ? null
  : contentType.includes('application/json') ? await response.json()
  : await response.text();
$request = curl_init('https://api.hub.donutwork.com/2026-02-01/workflow.json');
curl_setopt_array($request, [
    CURLOPT_CUSTOMREQUEST => 'GET',
    CURLOPT_RETURNTRANSFER => true,
    CURLOPT_HTTPHEADER => [
        'Authorization: Bearer ' . getenv('DONUTWORK_API_KEY'),
    ],
]);
$response = curl_exec($request);
if ($response === false) {
    $error = curl_error($request);
    curl_close($request);
    throw new RuntimeException($error);
}
$status = curl_getinfo($request, CURLINFO_RESPONSE_CODE);
$contentType = (string) curl_getinfo($request, CURLINFO_CONTENT_TYPE);
curl_close($request);
if ($status >= 400) { throw new RuntimeException($response); }
$data = $status === 204 ? null
    : (str_contains($contentType, 'application/json')
        ? json_decode($response, true, flags: JSON_THROW_ON_ERROR) : $response);

Replace uppercase placeholders and sample values with your own data and company resource IDs. Keep the API key on a trusted server. Reuse an idempotency key only for the same logical request and payload.


Build and inspect workflows

Use the block catalog as the source of truth for node names and data fields. Validate a graph before create/update, then simulate it with representative input. path_policy can be lenient or the stricter mode supported by the validator; preserve warnings and errors in your authoring UI.

Discover blocks and triggers

GET
/2026-02-01/workflow/blocks.json
Required permissionworkflow:read

Path, query, and header parameters

include_customboolean· query
Include company integration blocks. Default: true.
include_triggersboolean· query
Include available workflow triggers. Default: true.
pageinteger· query
Optional page number starting at 1.
sizeinteger· query
Optional page size from 1 to 200. Default when paginating: 50.

Responses

Company-aware block catalog, graph specification, operators, and triggers.

{
  "format": "workflow_block_catalog_v1",
  "workflow_payload_spec": {
    "format": "graph_v1",
    "required_root_keys": [
      "nodes",
      "edges"
    ]
  },
  "blocks": [],
  "pagination": {
    "page": 1,
    "size": 50,
    "total": 0,
    "has_next": false
  },
  "truncated": false
}

Request examples

curl --request GET 'https://api.hub.donutwork.com/2026-02-01/workflow/blocks.json' \
  --header 'Authorization: Bearer YOUR_API_KEY'
const response = await fetch('https://api.hub.donutwork.com/2026-02-01/workflow/blocks.json', {
  method: 'GET',
  headers: {
    Authorization: 'Bearer ' + process.env.DONUTWORK_API_KEY,
  },
});
const requestId = response.headers.get('x-request-id');
if (!response.ok) {
  const error = await response.text();
  throw new Error(`${response.status} ${error} [${requestId}]`);
}
const contentType = response.headers.get('content-type') ?? '';
const data = response.status === 204 ? null
  : contentType.includes('application/json') ? await response.json()
  : await response.text();
$request = curl_init('https://api.hub.donutwork.com/2026-02-01/workflow/blocks.json');
curl_setopt_array($request, [
    CURLOPT_CUSTOMREQUEST => 'GET',
    CURLOPT_RETURNTRANSFER => true,
    CURLOPT_HTTPHEADER => [
        'Authorization: Bearer ' . getenv('DONUTWORK_API_KEY'),
    ],
]);
$response = curl_exec($request);
if ($response === false) {
    $error = curl_error($request);
    curl_close($request);
    throw new RuntimeException($error);
}
$status = curl_getinfo($request, CURLINFO_RESPONSE_CODE);
$contentType = (string) curl_getinfo($request, CURLINFO_CONTENT_TYPE);
curl_close($request);
if ($status >= 400) { throw new RuntimeException($response); }
$data = $status === 204 ? null
    : (str_contains($contentType, 'application/json')
        ? json_decode($response, true, flags: JSON_THROW_ON_ERROR) : $response);

Replace uppercase placeholders and sample values with your own data and company resource IDs. Keep the API key on a trusted server. Reuse an idempotency key only for the same logical request and payload.

Validate a graph

POST
/2026-02-01/workflow/validate.json
Required permissionworkflow:read

Path, query, and header parameters

No path, query, or additional header parameters.

Request Body

JSON
{
  "workflow": {
    "workflow_graph": {
      "nodes": [
        {
          "id": "1",
          "name": "initial_trigger",
          "data": {},
          "position": {
            "x": 0,
            "y": 0
          }
        },
        {
          "id": "2",
          "name": "end",
          "data": {},
          "position": {
            "x": 300,
            "y": 0
          }
        }
      ],
      "edges": [
        {
          "source": "1",
          "sourceHandle": "output_1",
          "target": "2",
          "targetHandle": "input_1"
        }
      ]
    },
    "sample_context": {
      "customer_id": "CUSTOMER_ID"
    },
    "path_policy": "lenient"
  }
}
workflow.workflow_graphgraph_v1
Candidate graph. graph and flow are accepted aliases.
workflow.workflow_idstring
Validate a stored company workflow when no graph is supplied.
workflow.sample_contextobject
Representative runtime fields used for path and capability validation.
workflow.path_policystring
Path validation policy. Default: lenient.

Responses

Normalized graph plus structured warnings and errors.

{
  "valid": true,
  "errors": [],
  "warnings": [],
  "path_policy": "lenient",
  "normalized_graph": {
    "nodes": [],
    "edges": []
  }
}

Neither a graph nor workflow_id was supplied.

{
  "error": "workflow.workflow_graph (or workflow.graph/workflow.flow) or workflow.workflow_id is required."
}

Request examples

curl --request POST 'https://api.hub.donutwork.com/2026-02-01/workflow/validate.json' \
  --header 'Authorization: Bearer YOUR_API_KEY' \
  --header 'Content-Type: application/json' \
  --data '{
  "workflow": {
    "workflow_graph": {
      "nodes": [
        {
          "id": "1",
          "name": "initial_trigger",
          "data": {},
          "position": {
            "x": 0,
            "y": 0
          }
        },
        {
          "id": "2",
          "name": "end",
          "data": {},
          "position": {
            "x": 300,
            "y": 0
          }
        }
      ],
      "edges": [
        {
          "source": "1",
          "sourceHandle": "output_1",
          "target": "2",
          "targetHandle": "input_1"
        }
      ]
    },
    "sample_context": {
      "customer_id": "CUSTOMER_ID"
    },
    "path_policy": "lenient"
  }
}'
const response = await fetch('https://api.hub.donutwork.com/2026-02-01/workflow/validate.json', {
  method: 'POST',
  headers: {
    Authorization: 'Bearer ' + process.env.DONUTWORK_API_KEY,
    'Content-Type': 'application/json',
  },
  body: JSON.stringify({
    "workflow": {
      "workflow_graph": {
        "nodes": [
          {
            "id": "1",
            "name": "initial_trigger",
            "data": {},
            "position": {
              "x": 0,
              "y": 0
            }
          },
          {
            "id": "2",
            "name": "end",
            "data": {},
            "position": {
              "x": 300,
              "y": 0
            }
          }
        ],
        "edges": [
          {
            "source": "1",
            "sourceHandle": "output_1",
            "target": "2",
            "targetHandle": "input_1"
          }
        ]
      },
      "sample_context": {
        "customer_id": "CUSTOMER_ID"
      },
      "path_policy": "lenient"
    }
  }),
});
const requestId = response.headers.get('x-request-id');
if (!response.ok) {
  const error = await response.text();
  throw new Error(`${response.status} ${error} [${requestId}]`);
}
const contentType = response.headers.get('content-type') ?? '';
const data = response.status === 204 ? null
  : contentType.includes('application/json') ? await response.json()
  : await response.text();
$request = curl_init('https://api.hub.donutwork.com/2026-02-01/workflow/validate.json');
curl_setopt_array($request, [
    CURLOPT_CUSTOMREQUEST => 'POST',
    CURLOPT_RETURNTRANSFER => true,
    CURLOPT_HTTPHEADER => [
        'Authorization: Bearer ' . getenv('DONUTWORK_API_KEY'),
        'Content-Type: application/json',
    ],
    CURLOPT_POSTFIELDS => '{
  "workflow": {
    "workflow_graph": {
      "nodes": [
        {
          "id": "1",
          "name": "initial_trigger",
          "data": {},
          "position": {
            "x": 0,
            "y": 0
          }
        },
        {
          "id": "2",
          "name": "end",
          "data": {},
          "position": {
            "x": 300,
            "y": 0
          }
        }
      ],
      "edges": [
        {
          "source": "1",
          "sourceHandle": "output_1",
          "target": "2",
          "targetHandle": "input_1"
        }
      ]
    },
    "sample_context": {
      "customer_id": "CUSTOMER_ID"
    },
    "path_policy": "lenient"
  }
}',
]);
$response = curl_exec($request);
if ($response === false) {
    $error = curl_error($request);
    curl_close($request);
    throw new RuntimeException($error);
}
$status = curl_getinfo($request, CURLINFO_RESPONSE_CODE);
$contentType = (string) curl_getinfo($request, CURLINFO_CONTENT_TYPE);
curl_close($request);
if ($status >= 400) { throw new RuntimeException($response); }
$data = $status === 204 ? null
    : (str_contains($contentType, 'application/json')
        ? json_decode($response, true, flags: JSON_THROW_ON_ERROR) : $response);

Replace uppercase placeholders and sample values with your own data and company resource IDs. Keep the API key on a trusted server. Reuse an idempotency key only for the same logical request and payload.

Simulate an ad-hoc graph

POST
/2026-02-01/workflow/simulate.json
Required permissionworkflow:read

Path, query, and header parameters

No path, query, or additional header parameters.

Request Body

JSON
{
  "workflow": {
    "workflow_graph": {
      "nodes": [
        {
          "id": "1",
          "name": "initial_trigger",
          "data": {},
          "position": {
            "x": 0,
            "y": 0
          }
        },
        {
          "id": "2",
          "name": "end",
          "data": {},
          "position": {
            "x": 300,
            "y": 0
          }
        }
      ],
      "edges": [
        {
          "source": "1",
          "target": "2"
        }
      ]
    },
    "data": {
      "customer_id": "CUSTOMER_ID"
    },
    "path_policy": "lenient"
  }
}
workflow.workflow_graphgraph_v1
Candidate graph. Omit only when workflow_id is supplied.
workflow.workflow_idstring
Stored workflow to simulate when no graph is supplied.
workflow.dataobject
Runtime input. Simulation does not execute real provider side effects.
workflow.path_policystring
Path validation policy. Default: lenient.

Responses

Dry-run stages, structured warnings, and structured errors.

{
  "dry_run": true,
  "status": "simulated",
  "errors": [],
  "warnings": [],
  "path_policy": "lenient",
  "stages": []
}

Request examples

curl --request POST 'https://api.hub.donutwork.com/2026-02-01/workflow/simulate.json' \
  --header 'Authorization: Bearer YOUR_API_KEY' \
  --header 'Content-Type: application/json' \
  --data '{
  "workflow": {
    "workflow_graph": {
      "nodes": [
        {
          "id": "1",
          "name": "initial_trigger",
          "data": {},
          "position": {
            "x": 0,
            "y": 0
          }
        },
        {
          "id": "2",
          "name": "end",
          "data": {},
          "position": {
            "x": 300,
            "y": 0
          }
        }
      ],
      "edges": [
        {
          "source": "1",
          "target": "2"
        }
      ]
    },
    "data": {
      "customer_id": "CUSTOMER_ID"
    },
    "path_policy": "lenient"
  }
}'
const response = await fetch('https://api.hub.donutwork.com/2026-02-01/workflow/simulate.json', {
  method: 'POST',
  headers: {
    Authorization: 'Bearer ' + process.env.DONUTWORK_API_KEY,
    'Content-Type': 'application/json',
  },
  body: JSON.stringify({
    "workflow": {
      "workflow_graph": {
        "nodes": [
          {
            "id": "1",
            "name": "initial_trigger",
            "data": {},
            "position": {
              "x": 0,
              "y": 0
            }
          },
          {
            "id": "2",
            "name": "end",
            "data": {},
            "position": {
              "x": 300,
              "y": 0
            }
          }
        ],
        "edges": [
          {
            "source": "1",
            "target": "2"
          }
        ]
      },
      "data": {
        "customer_id": "CUSTOMER_ID"
      },
      "path_policy": "lenient"
    }
  }),
});
const requestId = response.headers.get('x-request-id');
if (!response.ok) {
  const error = await response.text();
  throw new Error(`${response.status} ${error} [${requestId}]`);
}
const contentType = response.headers.get('content-type') ?? '';
const data = response.status === 204 ? null
  : contentType.includes('application/json') ? await response.json()
  : await response.text();
$request = curl_init('https://api.hub.donutwork.com/2026-02-01/workflow/simulate.json');
curl_setopt_array($request, [
    CURLOPT_CUSTOMREQUEST => 'POST',
    CURLOPT_RETURNTRANSFER => true,
    CURLOPT_HTTPHEADER => [
        'Authorization: Bearer ' . getenv('DONUTWORK_API_KEY'),
        'Content-Type: application/json',
    ],
    CURLOPT_POSTFIELDS => '{
  "workflow": {
    "workflow_graph": {
      "nodes": [
        {
          "id": "1",
          "name": "initial_trigger",
          "data": {},
          "position": {
            "x": 0,
            "y": 0
          }
        },
        {
          "id": "2",
          "name": "end",
          "data": {},
          "position": {
            "x": 300,
            "y": 0
          }
        }
      ],
      "edges": [
        {
          "source": "1",
          "target": "2"
        }
      ]
    },
    "data": {
      "customer_id": "CUSTOMER_ID"
    },
    "path_policy": "lenient"
  }
}',
]);
$response = curl_exec($request);
if ($response === false) {
    $error = curl_error($request);
    curl_close($request);
    throw new RuntimeException($error);
}
$status = curl_getinfo($request, CURLINFO_RESPONSE_CODE);
$contentType = (string) curl_getinfo($request, CURLINFO_CONTENT_TYPE);
curl_close($request);
if ($status >= 400) { throw new RuntimeException($response); }
$data = $status === 204 ? null
    : (str_contains($contentType, 'application/json')
        ? json_decode($response, true, flags: JSON_THROW_ON_ERROR) : $response);

Replace uppercase placeholders and sample values with your own data and company resource IDs. Keep the API key on a trusted server. Reuse an idempotency key only for the same logical request and payload.

Create a workflow

POST
/2026-02-01/workflow.json
Required permissionworkflow:write

Path, query, and header parameters

No path, query, or additional header parameters.

Request Body

JSON
{
  "workflow": {
    "name": "Customer onboarding",
    "description": "Provision downstream systems",
    "trigger": "Customer.create",
    "workflow_graph": {
      "nodes": [
        {
          "id": "1",
          "name": "initial_trigger",
          "data": {},
          "position": {
            "x": 0,
            "y": 0
          }
        },
        {
          "id": "2",
          "name": "end",
          "data": {},
          "position": {
            "x": 300,
            "y": 0
          }
        }
      ],
      "edges": [
        {
          "source": "1",
          "target": "2"
        }
      ]
    },
    "path_policy": "lenient",
    "metadata": {
      "owner": "platform"
    }
  }
}
workflow.namestringRequired
Non-empty workflow name.
workflow.descriptionstring
Operator-facing purpose.
workflow.triggerstring
Catalog trigger name. Default: manual.
workflow.workflow_graphgraph_v1Required
Nodes and edges validated against the company block catalog.
workflow.sample_contextobject
Representative data for validation.
workflow.path_policystring
Path validation policy. Default: lenient.
workflow.metadataobject
Application metadata.

Responses

Validated workflow created; non-blocking validation feedback can appear in warnings.

{
  "workflow": {
    "id": "WORKFLOW_ID",
    "name": "Customer onboarding",
    "trigger": "Customer.create"
  },
  "warnings": []
}

Graph is missing or invalid.

{
  "error": "workflow graph payload is required.",
  "errors": [],
  "warnings": []
}

Request examples

curl --request POST 'https://api.hub.donutwork.com/2026-02-01/workflow.json' \
  --header 'Authorization: Bearer YOUR_API_KEY' \
  --header 'Content-Type: application/json' \
  --data '{
  "workflow": {
    "name": "Customer onboarding",
    "description": "Provision downstream systems",
    "trigger": "Customer.create",
    "workflow_graph": {
      "nodes": [
        {
          "id": "1",
          "name": "initial_trigger",
          "data": {},
          "position": {
            "x": 0,
            "y": 0
          }
        },
        {
          "id": "2",
          "name": "end",
          "data": {},
          "position": {
            "x": 300,
            "y": 0
          }
        }
      ],
      "edges": [
        {
          "source": "1",
          "target": "2"
        }
      ]
    },
    "path_policy": "lenient",
    "metadata": {
      "owner": "platform"
    }
  }
}'
const response = await fetch('https://api.hub.donutwork.com/2026-02-01/workflow.json', {
  method: 'POST',
  headers: {
    Authorization: 'Bearer ' + process.env.DONUTWORK_API_KEY,
    'Content-Type': 'application/json',
  },
  body: JSON.stringify({
    "workflow": {
      "name": "Customer onboarding",
      "description": "Provision downstream systems",
      "trigger": "Customer.create",
      "workflow_graph": {
        "nodes": [
          {
            "id": "1",
            "name": "initial_trigger",
            "data": {},
            "position": {
              "x": 0,
              "y": 0
            }
          },
          {
            "id": "2",
            "name": "end",
            "data": {},
            "position": {
              "x": 300,
              "y": 0
            }
          }
        ],
        "edges": [
          {
            "source": "1",
            "target": "2"
          }
        ]
      },
      "path_policy": "lenient",
      "metadata": {
        "owner": "platform"
      }
    }
  }),
});
const requestId = response.headers.get('x-request-id');
if (!response.ok) {
  const error = await response.text();
  throw new Error(`${response.status} ${error} [${requestId}]`);
}
const contentType = response.headers.get('content-type') ?? '';
const data = response.status === 204 ? null
  : contentType.includes('application/json') ? await response.json()
  : await response.text();
$request = curl_init('https://api.hub.donutwork.com/2026-02-01/workflow.json');
curl_setopt_array($request, [
    CURLOPT_CUSTOMREQUEST => 'POST',
    CURLOPT_RETURNTRANSFER => true,
    CURLOPT_HTTPHEADER => [
        'Authorization: Bearer ' . getenv('DONUTWORK_API_KEY'),
        'Content-Type: application/json',
    ],
    CURLOPT_POSTFIELDS => '{
  "workflow": {
    "name": "Customer onboarding",
    "description": "Provision downstream systems",
    "trigger": "Customer.create",
    "workflow_graph": {
      "nodes": [
        {
          "id": "1",
          "name": "initial_trigger",
          "data": {},
          "position": {
            "x": 0,
            "y": 0
          }
        },
        {
          "id": "2",
          "name": "end",
          "data": {},
          "position": {
            "x": 300,
            "y": 0
          }
        }
      ],
      "edges": [
        {
          "source": "1",
          "target": "2"
        }
      ]
    },
    "path_policy": "lenient",
    "metadata": {
      "owner": "platform"
    }
  }
}',
]);
$response = curl_exec($request);
if ($response === false) {
    $error = curl_error($request);
    curl_close($request);
    throw new RuntimeException($error);
}
$status = curl_getinfo($request, CURLINFO_RESPONSE_CODE);
$contentType = (string) curl_getinfo($request, CURLINFO_CONTENT_TYPE);
curl_close($request);
if ($status >= 400) { throw new RuntimeException($response); }
$data = $status === 204 ? null
    : (str_contains($contentType, 'application/json')
        ? json_decode($response, true, flags: JSON_THROW_ON_ERROR) : $response);

Replace uppercase placeholders and sample values with your own data and company resource IDs. Keep the API key on a trusted server. Reuse an idempotency key only for the same logical request and payload.

Retrieve a workflow

GET
/2026-02-01/workflow/{workflowId}.json
Required permissionworkflow:read

Path, query, and header parameters

workflowIdstring· pathRequired
Company-scoped workflow ID.
include_graphboolean· query
Include workflow_graph in the response. Default: false.

Responses

Workflow summary and optional graph.

{
  "workflow": {
    "id": "WORKFLOW_ID",
    "name": "Customer onboarding",
    "workflow_graph": {
      "nodes": [],
      "edges": []
    }
  }
}

Workflow not found in this company.

{
  "error": "Workflow not found."
}

Request examples

curl --request GET 'https://api.hub.donutwork.com/2026-02-01/workflow/WORKFLOWID.json' \
  --header 'Authorization: Bearer YOUR_API_KEY'
const response = await fetch('https://api.hub.donutwork.com/2026-02-01/workflow/WORKFLOWID.json', {
  method: 'GET',
  headers: {
    Authorization: 'Bearer ' + process.env.DONUTWORK_API_KEY,
  },
});
const requestId = response.headers.get('x-request-id');
if (!response.ok) {
  const error = await response.text();
  throw new Error(`${response.status} ${error} [${requestId}]`);
}
const contentType = response.headers.get('content-type') ?? '';
const data = response.status === 204 ? null
  : contentType.includes('application/json') ? await response.json()
  : await response.text();
$request = curl_init('https://api.hub.donutwork.com/2026-02-01/workflow/WORKFLOWID.json');
curl_setopt_array($request, [
    CURLOPT_CUSTOMREQUEST => 'GET',
    CURLOPT_RETURNTRANSFER => true,
    CURLOPT_HTTPHEADER => [
        'Authorization: Bearer ' . getenv('DONUTWORK_API_KEY'),
    ],
]);
$response = curl_exec($request);
if ($response === false) {
    $error = curl_error($request);
    curl_close($request);
    throw new RuntimeException($error);
}
$status = curl_getinfo($request, CURLINFO_RESPONSE_CODE);
$contentType = (string) curl_getinfo($request, CURLINFO_CONTENT_TYPE);
curl_close($request);
if ($status >= 400) { throw new RuntimeException($response); }
$data = $status === 204 ? null
    : (str_contains($contentType, 'application/json')
        ? json_decode($response, true, flags: JSON_THROW_ON_ERROR) : $response);

Replace uppercase placeholders and sample values with your own data and company resource IDs. Keep the API key on a trusted server. Reuse an idempotency key only for the same logical request and payload.

Update a workflow

PUT
/2026-02-01/workflow/{workflowId}.json
Required permissionworkflow:write

Path, query, and header parameters

workflowIdstring· pathRequired
Company-scoped workflow ID.
include_graphboolean· query
Return the normalized graph after update.

Request Body

JSON
{
  "workflow": {
    "name": "Customer onboarding v2",
    "description": "Updated flow",
    "path_policy": "lenient"
  }
}
workflow.namestring
Replacement non-empty name.
workflow.descriptionstring
Replacement description.
workflow.triggerstring
Replacement trigger; empty becomes manual.
workflow.workflow_graphgraph_v1
Replacement graph, validated before persistence.
workflow.metadataobject
Replacement application metadata.

Responses

Updated workflow and optional warnings.

{
  "workflow": {
    "id": "WORKFLOW_ID",
    "name": "Customer onboarding v2"
  },
  "warnings": []
}

Workflow not found in this company.

{
  "error": "Workflow not found."
}

Request examples

curl --request PUT 'https://api.hub.donutwork.com/2026-02-01/workflow/WORKFLOWID.json' \
  --header 'Authorization: Bearer YOUR_API_KEY' \
  --header 'Content-Type: application/json' \
  --data '{
  "workflow": {
    "name": "Customer onboarding v2",
    "description": "Updated flow",
    "path_policy": "lenient"
  }
}'
const response = await fetch('https://api.hub.donutwork.com/2026-02-01/workflow/WORKFLOWID.json', {
  method: 'PUT',
  headers: {
    Authorization: 'Bearer ' + process.env.DONUTWORK_API_KEY,
    'Content-Type': 'application/json',
  },
  body: JSON.stringify({
    "workflow": {
      "name": "Customer onboarding v2",
      "description": "Updated flow",
      "path_policy": "lenient"
    }
  }),
});
const requestId = response.headers.get('x-request-id');
if (!response.ok) {
  const error = await response.text();
  throw new Error(`${response.status} ${error} [${requestId}]`);
}
const contentType = response.headers.get('content-type') ?? '';
const data = response.status === 204 ? null
  : contentType.includes('application/json') ? await response.json()
  : await response.text();
$request = curl_init('https://api.hub.donutwork.com/2026-02-01/workflow/WORKFLOWID.json');
curl_setopt_array($request, [
    CURLOPT_CUSTOMREQUEST => 'PUT',
    CURLOPT_RETURNTRANSFER => true,
    CURLOPT_HTTPHEADER => [
        'Authorization: Bearer ' . getenv('DONUTWORK_API_KEY'),
        'Content-Type: application/json',
    ],
    CURLOPT_POSTFIELDS => '{
  "workflow": {
    "name": "Customer onboarding v2",
    "description": "Updated flow",
    "path_policy": "lenient"
  }
}',
]);
$response = curl_exec($request);
if ($response === false) {
    $error = curl_error($request);
    curl_close($request);
    throw new RuntimeException($error);
}
$status = curl_getinfo($request, CURLINFO_RESPONSE_CODE);
$contentType = (string) curl_getinfo($request, CURLINFO_CONTENT_TYPE);
curl_close($request);
if ($status >= 400) { throw new RuntimeException($response); }
$data = $status === 204 ? null
    : (str_contains($contentType, 'application/json')
        ? json_decode($response, true, flags: JSON_THROW_ON_ERROR) : $response);

Replace uppercase placeholders and sample values with your own data and company resource IDs. Keep the API key on a trusted server. Reuse an idempotency key only for the same logical request and payload.

Simulate a stored workflow

POST
/2026-02-01/workflow/{workflowId}/simulate.json
Required permissionworkflow:read

Path, query, and header parameters

workflowIdstring· pathRequired
Company-scoped workflow to simulate.

Request Body

JSON
{
  "workflow": {
    "data": {
      "customer_id": "CUSTOMER_ID"
    },
    "path_policy": "lenient"
  }
}
workflow.dataobject
Representative runtime input.
workflow.workflow_graphgraph_v1
Optional candidate graph; when omitted the stored graph is used.
workflow.path_policystring
Path validation policy. Default: lenient.

Responses

Dry-run result with no real provider side effects.

{
  "dry_run": true,
  "status": "simulated",
  "errors": [],
  "warnings": [],
  "stages": []
}

Request examples

curl --request POST 'https://api.hub.donutwork.com/2026-02-01/workflow/WORKFLOWID/simulate.json' \
  --header 'Authorization: Bearer YOUR_API_KEY' \
  --header 'Content-Type: application/json' \
  --data '{
  "workflow": {
    "data": {
      "customer_id": "CUSTOMER_ID"
    },
    "path_policy": "lenient"
  }
}'
const response = await fetch('https://api.hub.donutwork.com/2026-02-01/workflow/WORKFLOWID/simulate.json', {
  method: 'POST',
  headers: {
    Authorization: 'Bearer ' + process.env.DONUTWORK_API_KEY,
    'Content-Type': 'application/json',
  },
  body: JSON.stringify({
    "workflow": {
      "data": {
        "customer_id": "CUSTOMER_ID"
      },
      "path_policy": "lenient"
    }
  }),
});
const requestId = response.headers.get('x-request-id');
if (!response.ok) {
  const error = await response.text();
  throw new Error(`${response.status} ${error} [${requestId}]`);
}
const contentType = response.headers.get('content-type') ?? '';
const data = response.status === 204 ? null
  : contentType.includes('application/json') ? await response.json()
  : await response.text();
$request = curl_init('https://api.hub.donutwork.com/2026-02-01/workflow/WORKFLOWID/simulate.json');
curl_setopt_array($request, [
    CURLOPT_CUSTOMREQUEST => 'POST',
    CURLOPT_RETURNTRANSFER => true,
    CURLOPT_HTTPHEADER => [
        'Authorization: Bearer ' . getenv('DONUTWORK_API_KEY'),
        'Content-Type: application/json',
    ],
    CURLOPT_POSTFIELDS => '{
  "workflow": {
    "data": {
      "customer_id": "CUSTOMER_ID"
    },
    "path_policy": "lenient"
  }
}',
]);
$response = curl_exec($request);
if ($response === false) {
    $error = curl_error($request);
    curl_close($request);
    throw new RuntimeException($error);
}
$status = curl_getinfo($request, CURLINFO_RESPONSE_CODE);
$contentType = (string) curl_getinfo($request, CURLINFO_CONTENT_TYPE);
curl_close($request);
if ($status >= 400) { throw new RuntimeException($response); }
$data = $status === 204 ? null
    : (str_contains($contentType, 'application/json')
        ? json_decode($response, true, flags: JSON_THROW_ON_ERROR) : $response);

Replace uppercase placeholders and sample values with your own data and company resource IDs. Keep the API key on a trusted server. Reuse an idempotency key only for the same logical request and payload.

Human approval queue

List pending approvals

GET
/2026-02-01/workflow/approvals.json
Required permissionworkflow_approvals:read

Path, query, and header parameters

pageinteger· query
Page number starting at 1. Default: 1.
sizeinteger· query
Records per page. Default: 50.
workflowIdstring· query
Restrict to one company workflow.
eventTypestring· query
Restrict to the event that created the wait.
entityIdstring· query
Restrict to one event entity.

Responses

Pending company approvals.

{
  "status": "fetch",
  "page": 1,
  "size": 50,
  "count": 1,
  "max_pages": 1,
  "elements": [
    {
      "approval_id": "APPROVAL_ID",
      "workflow_id": "WORKFLOW_ID"
    }
  ]
}

Request examples

curl --request GET 'https://api.hub.donutwork.com/2026-02-01/workflow/approvals.json' \
  --header 'Authorization: Bearer YOUR_API_KEY'
const response = await fetch('https://api.hub.donutwork.com/2026-02-01/workflow/approvals.json', {
  method: 'GET',
  headers: {
    Authorization: 'Bearer ' + process.env.DONUTWORK_API_KEY,
  },
});
const requestId = response.headers.get('x-request-id');
if (!response.ok) {
  const error = await response.text();
  throw new Error(`${response.status} ${error} [${requestId}]`);
}
const contentType = response.headers.get('content-type') ?? '';
const data = response.status === 204 ? null
  : contentType.includes('application/json') ? await response.json()
  : await response.text();
$request = curl_init('https://api.hub.donutwork.com/2026-02-01/workflow/approvals.json');
curl_setopt_array($request, [
    CURLOPT_CUSTOMREQUEST => 'GET',
    CURLOPT_RETURNTRANSFER => true,
    CURLOPT_HTTPHEADER => [
        'Authorization: Bearer ' . getenv('DONUTWORK_API_KEY'),
    ],
]);
$response = curl_exec($request);
if ($response === false) {
    $error = curl_error($request);
    curl_close($request);
    throw new RuntimeException($error);
}
$status = curl_getinfo($request, CURLINFO_RESPONSE_CODE);
$contentType = (string) curl_getinfo($request, CURLINFO_CONTENT_TYPE);
curl_close($request);
if ($status >= 400) { throw new RuntimeException($response); }
$data = $status === 204 ? null
    : (str_contains($contentType, 'application/json')
        ? json_decode($response, true, flags: JSON_THROW_ON_ERROR) : $response);

Replace uppercase placeholders and sample values with your own data and company resource IDs. Keep the API key on a trusted server. Reuse an idempotency key only for the same logical request and payload.

Decide and resume an approval

POST
/2026-02-01/workflow/approvals/{approvalId}/decision.json
Required permissionworkflow_approvals:write

Path, query, and header parameters

approvalIdstring· pathRequired
Pending company-scoped approval ID.

Request Body

JSON
{
  "approval": {
    "decision": "approved",
    "comment": "Reviewed by finance"
  }
}
approval.decisionstringRequired
Decision consumed by the workflow approval node.
approval.commentstring
Operator comment made available to resumed nodes.
approval.approved_bystring
Actor reference; defaults to the authenticated API-key ID.

Responses

One-shot decision applied and eligible next nodes resumed.

{
  "status": "success",
  "approval": {
    "approval_id": "APPROVAL_ID",
    "decision": "approved",
    "workflow_id": "WORKFLOW_ID",
    "resumed": true,
    "resume_errors": []
  }
}

Approval absent or already consumed.

{
  "error": "Approval not found or already consumed."
}

Request examples

curl --request POST 'https://api.hub.donutwork.com/2026-02-01/workflow/approvals/APPROVALID/decision.json' \
  --header 'Authorization: Bearer YOUR_API_KEY' \
  --header 'Content-Type: application/json' \
  --data '{
  "approval": {
    "decision": "approved",
    "comment": "Reviewed by finance"
  }
}'
const response = await fetch('https://api.hub.donutwork.com/2026-02-01/workflow/approvals/APPROVALID/decision.json', {
  method: 'POST',
  headers: {
    Authorization: 'Bearer ' + process.env.DONUTWORK_API_KEY,
    'Content-Type': 'application/json',
  },
  body: JSON.stringify({
    "approval": {
      "decision": "approved",
      "comment": "Reviewed by finance"
    }
  }),
});
const requestId = response.headers.get('x-request-id');
if (!response.ok) {
  const error = await response.text();
  throw new Error(`${response.status} ${error} [${requestId}]`);
}
const contentType = response.headers.get('content-type') ?? '';
const data = response.status === 204 ? null
  : contentType.includes('application/json') ? await response.json()
  : await response.text();
$request = curl_init('https://api.hub.donutwork.com/2026-02-01/workflow/approvals/APPROVALID/decision.json');
curl_setopt_array($request, [
    CURLOPT_CUSTOMREQUEST => 'POST',
    CURLOPT_RETURNTRANSFER => true,
    CURLOPT_HTTPHEADER => [
        'Authorization: Bearer ' . getenv('DONUTWORK_API_KEY'),
        'Content-Type: application/json',
    ],
    CURLOPT_POSTFIELDS => '{
  "approval": {
    "decision": "approved",
    "comment": "Reviewed by finance"
  }
}',
]);
$response = curl_exec($request);
if ($response === false) {
    $error = curl_error($request);
    curl_close($request);
    throw new RuntimeException($error);
}
$status = curl_getinfo($request, CURLINFO_RESPONSE_CODE);
$contentType = (string) curl_getinfo($request, CURLINFO_CONTENT_TYPE);
curl_close($request);
if ($status >= 400) { throw new RuntimeException($response); }
$data = $status === 204 ? null
    : (str_contains($contentType, 'application/json')
        ? json_decode($response, true, flags: JSON_THROW_ON_ERROR) : $response);

Replace uppercase placeholders and sample values with your own data and company resource IDs. Keep the API key on a trusted server. Reuse an idempotency key only for the same logical request and payload.

Workflow execution

Dispatch Workflow Execution

Trigger the execution of a specific business workflow. This endpoint accepts a dynamic data payload that is injected into the execution context, allowing tasks to operate on real-time request data.

POST
/2026-02-01/workflow/{workflowId}.json
Required permissionworkflow:write

Path, query, and header parameters

workflowIdstring· pathRequired
The unique identifier of the workflow to execute.

Request Body

JSON
{
  "data": {
    "customer_id": "cust_9921",
    "priority": "high",
    "metadata": {
      "source": "api_v2"
    }
  }
}
dataobjectRequired
Dynamic context data injected into the workflow execution.

Responses

Workflow execution completed. Detailed stage results returned.

{
  "success": true,
  "execution_id": "exe_8821abd033",
  "stages": [
    {
      "node": "validate_identity",
      "status": "completed",
      "output": {
        "valid": true
      }
    }
  ]
}

The specified workflow identifier does not exist.

{
  "error": "This workflow does not exists"
}

Request examples

curl --request POST 'https://api.hub.donutwork.com/2026-02-01/workflow/WORKFLOWID.json' \
  --header 'Authorization: Bearer YOUR_API_KEY' \
  --header 'Content-Type: application/json' \
  --data '{
  "data": {
    "customer_id": "cust_9921",
    "priority": "high",
    "metadata": {
      "source": "api_v2"
    }
  }
}'
const response = await fetch('https://api.hub.donutwork.com/2026-02-01/workflow/WORKFLOWID.json', {
  method: 'POST',
  headers: {
    Authorization: 'Bearer ' + process.env.DONUTWORK_API_KEY,
    'Content-Type': 'application/json',
  },
  body: JSON.stringify({
    "data": {
      "customer_id": "cust_9921",
      "priority": "high",
      "metadata": {
        "source": "api_v2"
      }
    }
  }),
});
const requestId = response.headers.get('x-request-id');
if (!response.ok) {
  const error = await response.text();
  throw new Error(`${response.status} ${error} [${requestId}]`);
}
const contentType = response.headers.get('content-type') ?? '';
const data = response.status === 204 ? null
  : contentType.includes('application/json') ? await response.json()
  : await response.text();
$request = curl_init('https://api.hub.donutwork.com/2026-02-01/workflow/WORKFLOWID.json');
curl_setopt_array($request, [
    CURLOPT_CUSTOMREQUEST => 'POST',
    CURLOPT_RETURNTRANSFER => true,
    CURLOPT_HTTPHEADER => [
        'Authorization: Bearer ' . getenv('DONUTWORK_API_KEY'),
        'Content-Type: application/json',
    ],
    CURLOPT_POSTFIELDS => '{
  "data": {
    "customer_id": "cust_9921",
    "priority": "high",
    "metadata": {
      "source": "api_v2"
    }
  }
}',
]);
$response = curl_exec($request);
if ($response === false) {
    $error = curl_error($request);
    curl_close($request);
    throw new RuntimeException($error);
}
$status = curl_getinfo($request, CURLINFO_RESPONSE_CODE);
$contentType = (string) curl_getinfo($request, CURLINFO_CONTENT_TYPE);
curl_close($request);
if ($status >= 400) { throw new RuntimeException($response); }
$data = $status === 204 ? null
    : (str_contains($contentType, 'application/json')
        ? json_decode($response, true, flags: JSON_THROW_ON_ERROR) : $response);

Replace uppercase placeholders and sample values with your own data and company resource IDs. Keep the API key on a trusted server. Reuse an idempotency key only for the same logical request and payload.

Auditability: Every workflow execution generates a unique execution_id and provides detailed telemetry for each stage (node) within the sequence, including input/output data and operational latency.


Workflow Tracing

List Workflow Traces

Retrieve paginated execution traces for a workflow.

GET
/2026-02-01/workflow/{workflowId}/traces.json
Required permissionworkflow_traces:read

Path, query, and header parameters

workflowIdstring· pathRequired
Workflow identifier.
sizeinteger· query
Records per page (default 50, max 100).
pageinteger· query
Page number starting from 1.

Responses

Trace list retrieved.

{
  "entities": "WorkflowTrace",
  "count": 2,
  "per_page": 50,
  "pages": {
    "current": 1,
    "max": 1
  },
  "elements": [
    {
      "id": "6803f0f1...",
      "workflow_id": "67ffac1f...",
      "uuid": "d76d716e-0f91-4f7f-b37f-4475e573f807",
      "run_date": "2026-04-20T08:41:12+00:00",
      "status": "executed"
    }
  ]
}

Workflow not found.

{
  "error": "This workflow does not exists"
}

Request examples

curl --request GET 'https://api.hub.donutwork.com/2026-02-01/workflow/WORKFLOWID/traces.json' \
  --header 'Authorization: Bearer YOUR_API_KEY'
const response = await fetch('https://api.hub.donutwork.com/2026-02-01/workflow/WORKFLOWID/traces.json', {
  method: 'GET',
  headers: {
    Authorization: 'Bearer ' + process.env.DONUTWORK_API_KEY,
  },
});
const requestId = response.headers.get('x-request-id');
if (!response.ok) {
  const error = await response.text();
  throw new Error(`${response.status} ${error} [${requestId}]`);
}
const contentType = response.headers.get('content-type') ?? '';
const data = response.status === 204 ? null
  : contentType.includes('application/json') ? await response.json()
  : await response.text();
$request = curl_init('https://api.hub.donutwork.com/2026-02-01/workflow/WORKFLOWID/traces.json');
curl_setopt_array($request, [
    CURLOPT_CUSTOMREQUEST => 'GET',
    CURLOPT_RETURNTRANSFER => true,
    CURLOPT_HTTPHEADER => [
        'Authorization: Bearer ' . getenv('DONUTWORK_API_KEY'),
    ],
]);
$response = curl_exec($request);
if ($response === false) {
    $error = curl_error($request);
    curl_close($request);
    throw new RuntimeException($error);
}
$status = curl_getinfo($request, CURLINFO_RESPONSE_CODE);
$contentType = (string) curl_getinfo($request, CURLINFO_CONTENT_TYPE);
curl_close($request);
if ($status >= 400) { throw new RuntimeException($response); }
$data = $status === 204 ? null
    : (str_contains($contentType, 'application/json')
        ? json_decode($response, true, flags: JSON_THROW_ON_ERROR) : $response);

Replace uppercase placeholders and sample values with your own data and company resource IDs. Keep the API key on a trusted server. Reuse an idempotency key only for the same logical request and payload.

Get Workflow Trace

Load full trace payload for one workflow run.

GET
/2026-02-01/workflow/{workflowId}/traces/{traceId}.json
Required permissionworkflow_traces:read

Path, query, and header parameters

workflowIdstring· pathRequired
Workflow identifier.
traceIdstring· pathRequired
Trace identifier.

Responses

Trace retrieved.

{
  "id": "6803f0f1...",
  "workflow_id": "67ffac1f...",
  "uuid": "d76d716e-0f91-4f7f-b37f-4475e573f807",
  "run_date": "2026-04-20T08:41:12+00:00",
  "status": "executed",
  "response": {
    "input": {
      "customer_id": "cust_9921"
    },
    "result": {
      "workflow_status": "executed"
    },
    "stages": [
      {
        "node_name": "validate_identity",
        "status": "SUCCESS",
        "duration": 0.1281
      }
    ]
  }
}

Trace not found for workflow.

{
  "error": "Trace not found for this workflow."
}

Request examples

curl --request GET 'https://api.hub.donutwork.com/2026-02-01/workflow/WORKFLOWID/traces/TRACEID.json' \
  --header 'Authorization: Bearer YOUR_API_KEY'
const response = await fetch('https://api.hub.donutwork.com/2026-02-01/workflow/WORKFLOWID/traces/TRACEID.json', {
  method: 'GET',
  headers: {
    Authorization: 'Bearer ' + process.env.DONUTWORK_API_KEY,
  },
});
const requestId = response.headers.get('x-request-id');
if (!response.ok) {
  const error = await response.text();
  throw new Error(`${response.status} ${error} [${requestId}]`);
}
const contentType = response.headers.get('content-type') ?? '';
const data = response.status === 204 ? null
  : contentType.includes('application/json') ? await response.json()
  : await response.text();
$request = curl_init('https://api.hub.donutwork.com/2026-02-01/workflow/WORKFLOWID/traces/TRACEID.json');
curl_setopt_array($request, [
    CURLOPT_CUSTOMREQUEST => 'GET',
    CURLOPT_RETURNTRANSFER => true,
    CURLOPT_HTTPHEADER => [
        'Authorization: Bearer ' . getenv('DONUTWORK_API_KEY'),
    ],
]);
$response = curl_exec($request);
if ($response === false) {
    $error = curl_error($request);
    curl_close($request);
    throw new RuntimeException($error);
}
$status = curl_getinfo($request, CURLINFO_RESPONSE_CODE);
$contentType = (string) curl_getinfo($request, CURLINFO_CONTENT_TYPE);
curl_close($request);
if ($status >= 400) { throw new RuntimeException($response); }
$data = $status === 204 ? null
    : (str_contains($contentType, 'application/json')
        ? json_decode($response, true, flags: JSON_THROW_ON_ERROR) : $response);

Replace uppercase placeholders and sample values with your own data and company resource IDs. Keep the API key on a trusted server. Reuse an idempotency key only for the same logical request and payload.

On this page