Studio

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

FieldTypeRequiredDefaultDescription
WorkflowResourceoptionalThe definition — trigger, nodes, edges, version history
NodeConfigoptionalA single step: action, condition, loop, sub-workflow, delay, etc.
EdgeConfigoptionalA connection between nodes with optional condition expression
RunResourceoptionalA single execution instance with status, input, output, and trace
TriggerConfigoptionalWhat starts the workflow: schedule, webhook, record event, or manual
VersionResourceoptionalA snapshot of the workflow definition; published before production activation

Workflow API

GET
/api/workflows

List all workflows in the workspace

POST
/api/workflows

Create a new workflow

GET
/api/workflows/:id

Get a single workflow

PATCH
/api/workflows/:id

Update workflow metadata or config

DELETE
/api/workflows/:id

Delete a workflow

POST
/api/workflows/:id/activate

Activate a workflow (must have a published version)

POST
/api/workflows/:id/deactivate

Deactivate without deleting

POST
/api/workflows/:id/trigger

Manually trigger a workflow run (always async — returns an executionId)

GET
/api/workflows/:id/executions

List all executions (runs) for a workflow

GET
/api/workflows/:id/versions

List version history

Create a workflow

bash
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

bash
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

json
{
  "trigger": {
    "type": "schedule",
    "cron": "0 9 * * MON-FRI",   // POSIX cron — 9am weekdays
    "timezone": "America/New_York"
  }
}

Webhook (inbound)

json
{
  "trigger": {
    "type": "webhook",
    "method": "POST",
    "authentication": "hmac_sha256",  // or "none", "bearer", "basic"
    "secret": "wh_secret_..."
  }
}
// FlowOS generates: https://acme.flowos.io/webhooks/wf_01HZ.../inbound

Record Event

json
{
  "trigger": {
    "type": "record_created",     // record_created | record_updated | record_deleted
    "tableId": "tbl_incidents",
    "conditions": [
      { "field": "severity", "operator": "equals", "value": "P1" }
    ]
  }
}

ITSM Event

json
{
  "trigger": {
    "type": "itsm_event",
    "event": "incident.escalated",    // incident.*, change.*, problem.*
    "conditions": [
      { "field": "assignedTeam", "operator": "equals", "value": "infrastructure" }
    ]
  }
}

Node Types

FieldTypeRequiredDefaultDescription
actionNodeoptionalExecute an integration action (email, HTTP, Jira, Slack, etc.)
conditionNodeoptionalBranch flow based on a boolean expression using {{variables}}
loopNodeoptionalIterate over an array; each item runs the inner sub-graph
delayNodeoptionalPause execution for a fixed duration or until a condition is met
transformNodeoptionalMap/filter/reduce data using JSONata or JavaScript expressions
sub_workflowNodeoptionalCall another workflow synchronously or asynchronously
approvalNodeoptionalPause and wait for a human approval via UI or email
http_requestNodeoptionalMake an arbitrary outbound HTTP call with full header/body control
set_variableNodeoptionalSet a workflow-scoped variable accessible by later nodes
logNodeoptionalWrite to the run trace log for debugging

HTTP Request node example

json
{
  "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 by set_variable nodes
  • {{workspace.*}} — Workspace config values
  • {{secrets.*}} — Secrets stored in the platform vault (never logged)
  • {{env.*}} — Environment variables (non-sensitive)

Workflow Runs API

GET
/api/workflows/:id/executions

List executions with status, duration, trigger info

GET
/api/workflows/:id/executions/:execId

Get full execution detail including per-node trace

POST
/api/workflows/:id/executions/:execId/cancel

Cancel a running execution

POST
/api/workflows/:id/executions/:execId/replay

Re-run a workflow from the beginning with the same trigger payload

POST
/api/workflows/:id/executions/:execId/resume

Resume a paused execution (e.g. after approval)

json
// 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.

POST
/api/workflows/:id/publish

Validate, snapshot as a new version, and activate the workflow

GET
/api/workflows/:id/versions

List saved versions (id, version number, label, createdAt)

POST
/api/workflows/:id/versions/:versionId/restore

Overwrite the live workflow definition with a previous version's snapshot

bash
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"
  }
}
In CI/CD pipelines, use POST /publish then POST /activate to promote workflow changes. Rollback to a previous version using the versionId from the versions list.