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.
workflow:readPath, query, and header parameters
sizepageResponses
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
workflow:readPath, query, and header parameters
include_custominclude_triggerspagesizeResponses
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
workflow:readPath, query, and header parameters
No path, query, or additional header parameters.
Request Body
{
"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_graphworkflow.workflow_idworkflow.sample_contextworkflow.path_policyResponses
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
workflow:readPath, query, and header parameters
No path, query, or additional header parameters.
Request Body
{
"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_graphworkflow.workflow_idworkflow.dataworkflow.path_policyResponses
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
workflow:writePath, query, and header parameters
No path, query, or additional header parameters.
Request Body
{
"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.nameRequiredworkflow.descriptionworkflow.triggerworkflow.workflow_graphRequiredworkflow.sample_contextworkflow.path_policyworkflow.metadataResponses
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
workflow:readPath, query, and header parameters
workflowIdRequiredinclude_graphResponses
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
workflow:writePath, query, and header parameters
workflowIdRequiredinclude_graphRequest Body
{
"workflow": {
"name": "Customer onboarding v2",
"description": "Updated flow",
"path_policy": "lenient"
}
}workflow.nameworkflow.descriptionworkflow.triggerworkflow.workflow_graphworkflow.metadataResponses
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
workflow:readPath, query, and header parameters
workflowIdRequiredRequest Body
{
"workflow": {
"data": {
"customer_id": "CUSTOMER_ID"
},
"path_policy": "lenient"
}
}workflow.dataworkflow.workflow_graphworkflow.path_policyResponses
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
workflow_approvals:readPath, query, and header parameters
pagesizeworkflowIdeventTypeentityIdResponses
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
workflow_approvals:writePath, query, and header parameters
approvalIdRequiredRequest Body
{
"approval": {
"decision": "approved",
"comment": "Reviewed by finance"
}
}approval.decisionRequiredapproval.commentapproval.approved_byResponses
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.
workflow:writePath, query, and header parameters
workflowIdRequiredRequest Body
{
"data": {
"customer_id": "cust_9921",
"priority": "high",
"metadata": {
"source": "api_v2"
}
}
}dataRequiredResponses
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.
workflow_traces:readPath, query, and header parameters
workflowIdRequiredsizepageResponses
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.
workflow_traces:readPath, query, and header parameters
workflowIdRequiredtraceIdRequiredResponses
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.