Workflow Studio
Build, manage, and trigger process automations. Every workflow is defined, executed, and inspectable via the REST API.
Overview
A workflow is a directed graph of nodes connected by edges. Workflows run in response to triggers — scheduled times, webhook calls, record changes, or manual API invocations. Each execution produces a workflow run with a full node-by-node log.
Key Concepts
| Field | Type | Required | Default | Description |
|---|---|---|---|---|
| Workflow | Resource | optional | — | The definition — trigger, nodes, edges, version history |
| Node | Config | optional | — | A single step: action, condition, loop, sub-workflow, delay, etc. |
| Edge | Config | optional | — | A connection between nodes with optional condition expression |
| Run | Resource | optional | — | A single execution instance with status, input, output, and trace |
| Trigger | Config | optional | — | What starts the workflow: schedule, webhook, record event, or manual |
| Version | Resource | optional | — | A snapshot of the workflow definition; published before production activation |
Workflow API
/api/workflowsList all workflows in the workspace
/api/workflowsCreate a new workflow
/api/workflows/:idGet a single workflow
/api/workflows/:idUpdate workflow metadata or config
/api/workflows/:idDelete a workflow
/api/workflows/:id/activateActivate a workflow (must have a published version)
/api/workflows/:id/deactivateDeactivate without deleting
/api/workflows/:id/triggerManually trigger a workflow run (always async — returns an executionId)
/api/workflows/:id/executionsList all executions (runs) for a workflow
/api/workflows/:id/versionsList version history
Create a workflow
POST /api/workflows
{
"name": "Employee Onboarding",
"description": "Automate new hire provisioning",
"trigger": {
"type": "record_created",
"tableId": "tbl_employees"
},
"nodes": [
{
"id": "send-email",
"type": "action",
"action": "email.send",
"config": {
"to": "{{trigger.record.email}}",
"subject": "Welcome to Acme!",
"templateId": "tpl_welcome"
},
"position": { "x": 300, "y": 100 }
},
{
"id": "create-jira",
"type": "action",
"action": "jira.createIssue",
"config": {
"project": "OPS",
"summary": "Onboard {{trigger.record.name}}",
"assignee": "{{workspace.itManager}}"
},
"position": { "x": 300, "y": 250 }
}
],
"edges": [
{ "from": "send-email", "to": "create-jira" }
]
}Trigger a workflow manually
POST /api/workflows/wf_01HZ4KPQRSTUV/trigger
{
"payload": {
"userId": "usr_123",
"department": "Engineering",
"startDate": "2026-07-01"
}
// "startFromNodeId" may also be set to begin execution partway through the graph
}
// Response (202 Accepted — this endpoint is always asynchronous)
{
"success": true,
"data": { "executionId": "run_01HZ9MNPQRSTUV" }
}Trigger Types
Schedule
{
"trigger": {
"type": "schedule",
"cron": "0 9 * * MON-FRI", // POSIX cron — 9am weekdays
"timezone": "America/New_York"
}
}Webhook (inbound)
{
"trigger": {
"type": "webhook",
"method": "POST",
"authentication": "hmac_sha256", // or "none", "bearer", "basic"
"secret": "wh_secret_..."
}
}
// FlowOS generates: https://acme.flowos.io/webhooks/wf_01HZ.../inboundRecord Event
{
"trigger": {
"type": "record_created", // record_created | record_updated | record_deleted
"tableId": "tbl_incidents",
"conditions": [
{ "field": "severity", "operator": "equals", "value": "P1" }
]
}
}ITSM Event
{
"trigger": {
"type": "itsm_event",
"event": "incident.escalated", // incident.*, change.*, problem.*
"conditions": [
{ "field": "assignedTeam", "operator": "equals", "value": "infrastructure" }
]
}
}Node Types
| Field | Type | Required | Default | Description |
|---|---|---|---|---|
| action | Node | optional | — | Execute an integration action (email, HTTP, Jira, Slack, etc.) |
| condition | Node | optional | — | Branch flow based on a boolean expression using {{variables}} |
| loop | Node | optional | — | Iterate over an array; each item runs the inner sub-graph |
| delay | Node | optional | — | Pause execution for a fixed duration or until a condition is met |
| transform | Node | optional | — | Map/filter/reduce data using JSONata or JavaScript expressions |
| sub_workflow | Node | optional | — | Call another workflow synchronously or asynchronously |
| approval | Node | optional | — | Pause and wait for a human approval via UI or email |
| http_request | Node | optional | — | Make an arbitrary outbound HTTP call with full header/body control |
| set_variable | Node | optional | — | Set a workflow-scoped variable accessible by later nodes |
| log | Node | optional | — | Write to the run trace log for debugging |
HTTP Request node example
{
"id": "notify-pagerduty",
"type": "http_request",
"config": {
"method": "POST",
"url": "https://events.pagerduty.com/v2/enqueue",
"headers": {
"Authorization": "Token token={{secrets.PAGERDUTY_KEY}}",
"Content-Type": "application/json"
},
"body": {
"routing_key": "{{secrets.PD_ROUTING_KEY}}",
"event_action": "trigger",
"payload": {
"summary": "{{trigger.record.title}}",
"severity": "critical",
"source": "FlowOS"
}
},
"retries": 3,
"retryDelayMs": 2000
}
}Template Expressions
Node config values can reference runtime data using {{variable}} syntax (Mustache-compatible). Available contexts within a node:
- •
{{trigger.*}}— Trigger payload (record data, webhook body, schedule info) - •
{{nodes.nodeId.output.*}}— Output of a previously executed node - •
{{vars.*}}— Variables set byset_variablenodes - •
{{workspace.*}}— Workspace config values - •
{{secrets.*}}— Secrets stored in the platform vault (never logged) - •
{{env.*}}— Environment variables (non-sensitive)
Workflow Runs API
/api/workflows/:id/executionsList executions with status, duration, trigger info
/api/workflows/:id/executions/:execIdGet full execution detail including per-node trace
/api/workflows/:id/executions/:execId/cancelCancel a running execution
/api/workflows/:id/executions/:execId/replayRe-run a workflow from the beginning with the same trigger payload
/api/workflows/:id/executions/:execId/resumeResume a paused execution (e.g. after approval)
// Execution detail response
{
"data": {
"id": "run_01HZ9MNPQRSTUV",
"workflowId": "wf_01HZ4KPQRSTUV",
"status": "completed", // running | completed | failed | cancelled | waiting | simulating
"triggeredBy": "manual",
"triggerPayload": { "userId": "usr_123" },
"durationMs": 1423,
"startedAt": "2026-06-01T10:00:00Z",
"completedAt": "2026-06-01T10:00:01.423Z",
"nodeExecutions": [
{
"nodeId": "send-email",
"nodeType": "action",
"status": "done",
"startedAt": "2026-06-01T10:00:00.100Z",
"durationMs": 312,
"output": { "messageId": "msg_abc123" },
"error": null
},
{
"nodeId": "create-jira",
"nodeType": "action",
"status": "done",
"startedAt": "2026-06-01T10:00:00.420Z",
"durationMs": 1000,
"output": { "issueKey": "OPS-42", "issueId": "10042" },
"error": null
}
]
}
}Version Management
Workflows use explicit versioning. A workflow must have a published version before it can be activated in production. Draft changes don't affect active runs.
/api/workflows/:id/publishValidate, snapshot as a new version, and activate the workflow
/api/workflows/:id/versionsList saved versions (id, version number, label, createdAt)
/api/workflows/:id/versions/:versionId/restoreOverwrite the live workflow definition with a previous version's snapshot
POST /api/workflows/wf_01HZ4.../publish
{
"message": "Add Slack notification for P1 incidents"
}
// Response
{
"data": {
"versionId": "ver_01HZ9...",
"versionNumber": 7,
"publishedAt": "2026-06-01T10:05:00Z",
"publishedBy": "usr_01HZ...",
"message": "Add Slack notification for P1 incidents"
}
}POST /publish then POST /activate to promote workflow changes. Rollback to a previous version using the versionId from the versions list.