Workflows
Create, manage, deploy, and execute automation workflows. Workflows are composed of interconnected nodes that define data processing pipelines.
All endpoints require authentication via X-API-Key header and the appropriate scope.
Workflow Object
{
"_id": "wf_abc123",
"name": "Daily ETL Pipeline",
"description": "Extracts data from MySQL, transforms, and loads to S3",
"status": "active",
"workflowType": "batch",
"version": 1,
"nodes": [
{
"id": "mysql-a1B2c3",
"type": "mysql",
"category": "sources",
"nodeId": "mysql-sources",
"label": "Extract Orders",
"nodeVersion": "1.1.3",
"position": { "x": 100, "y": 200 },
"config": {}
}
],
"connections": [
{
"id": "conn-d4E5f6",
"source": "mysql-a1B2c3",
"target": "s3-g7H8i9",
"sourcePort": "output",
"targetPort": "input"
}
],
"scopes": {},
"settings": {
"autoSave": true,
"timeout": 300000,
"retryAttempts": 3,
"enableLogging": true,
"logLevel": "info"
},
"tags": ["etl", "production"],
"organizationId": "org_xyz",
"ownerId": "user_456",
"ownerName": "Jane Smith",
"sharedWith": ["user_789"],
"isPublic": false,
"isTemplate": false,
"createdAt": "2025-01-15T10:30:00Z",
"lastUpdated": "2025-02-01T14:22:00Z"
}
Within a workflow, each node's id and each connection's id identify that node or connection in the graph; the workflow itself is identified by _id. nodeId is the node's catalog identity.
A deployed workflow also has deploymentStatus (see Deployment status) and a deployment record (its status, resources and timestamps). The object does not say where a deployed workflow runs inside the platform or which platform service addresses it was given when it deployed: none of them can be reached from outside the platform. The same holds for every response on this page that returns a workflow, a template or a version's definition.
GET /api/v1/workflows
List all workflows accessible to the authenticated user, the same workflows the workflow list shows. Templates are not included: list them with GET /api/v1/workflows/templates.
Scope: workflows:read
Query Parameters
| Parameter | Type | Required | Description |
|---|---|---|---|
status | string | No | Filter by status: draft, active, paused, archived |
tag | string | No | Filter by tag |
q | string | No | Search by name or description |
limit | integer | No | Number of results to return (default: 50, max: 200) |
cursor | string | No | meta.nextCursor of the previous page; omit for the first page |
sort | string | No | Sort field, prefixed with - for descending (default: -createdAt) |
Response 200 OK
{
"data": [
{
"_id": "wf_abc123",
"name": "Daily ETL Pipeline",
"description": "Extracts data from MySQL, transforms, and loads to S3",
"status": "active",
"workflowType": "batch",
"version": 1,
"scopes": {},
"settings": { "autoSave": true, "timeout": 300000, "retryAttempts": 3, "enableLogging": true, "logLevel": "info" },
"tags": ["etl", "production"],
"organizationId": "org_xyz",
"ownerId": "user_456",
"ownerName": "Jane Smith",
"sharedWith": [],
"isPublic": false,
"isTemplate": false,
"createdAt": "2025-01-15T10:30:00Z",
"lastUpdated": "2025-02-01T14:22:00Z"
}
],
"meta": {
"total": 42,
"limit": 50,
"nextCursor": null,
"requestId": "req_abc123"
}
}
POST /api/v1/workflows
Create a new workflow.
Scope: workflows:write
Request Body
{
"name": "Daily ETL Pipeline",
"description": "Extracts data from MySQL, transforms, and loads to S3",
"status": "draft",
"nodes": [],
"connections": [],
"tags": ["etl"],
"settings": {
"timeout": 3600,
"retryOnFailure": true,
"maxRetries": 3
}
}
| Field | Type | Required | Description |
|---|---|---|---|
name | string | Yes | Workflow name |
description | string | No | Workflow description |
status | string | No | Initial status (default: draft) |
nodes | array | No | Array of node objects |
connections | array | No | Array of connection objects |
tags | array | No | Array of tag strings |
settings | object | No | Workflow settings (timeout, retry, etc.) |
Response 201 Created
The new Workflow object; its _id is the workflow's id.
POST /api/v1/workflows/build
Create a whole workflow in one call: every node and every connection. Prefer it to creating an
empty workflow and adding nodes one at a time. The workflow is a draft; check it with
GET /api/v1/workflows/:id/structure-check and POST /api/v1/workflows/:id/validate, then run
or deploy it.
Scope: workflows:write
Request Body
| Field | Type | Required | Description |
|---|---|---|---|
name | string | Yes | Workflow name |
description | string | No | What it does |
workflowType | string | No | batch (default) or streaming |
nodes | array | Yes | Every node: { id, type, label?, config?, inputMappings?, category? }. id is your own reference, used in connections; category is needed when a node type exists in more than one category (for example postgresql as a source and a destination) |
connections | array | No | Every edge: { source, target, sourcePort?, targetPort? }, by node id. sourcePort is one of the source node's outputs (default output): continue for a loop's body, completed from a loop or map into its Loop Accumulator, if / else from a condition |
A connection to a node id not in nodes, or a node type that does not exist, is refused with
400, naming each problem.
Response 201 Created: the new Workflow object, its nodes with their ids.
POST /api/v1/workflows/from-template
Create a draft workflow from a template: a copy of the template's graph.
Scope: workflows:write
Request Body
| Field | Type | Required | Description |
|---|---|---|---|
templateId | string | Yes | A template, from GET /api/v1/workflows/templates |
name | string | No | Its name (default New workflow from <template>) |
description | string | No | Its description |
tags | string[] | No | Its tags |
Response 201 Created: the new Workflow object.
GET /api/v1/workflows/:id
Get a single workflow by ID.
Scope: workflows:read
Path Parameters
| Parameter | Type | Required | Description |
|---|---|---|---|
id | string | Yes | Workflow ID |
Response 200 OK
Returns the full Workflow object.
PATCH /api/v1/workflows/:id
Update an existing workflow.
Scope: workflows:write
Path Parameters
| Parameter | Type | Required | Description |
|---|---|---|---|
id | string | Yes | Workflow ID |
Request Body
{
"name": "Updated ETL Pipeline",
"description": "Updated description",
"nodes": [],
"connections": [],
"tags": ["etl", "v2"],
"settings": {
"timeout": 7200
}
}
| Field | Type | Required | Description |
|---|---|---|---|
name | string | No | Workflow name |
description | string | No | Workflow description |
nodes | array | No | Array of node objects |
connections | array | No | Array of connection objects |
tags | array | No | Array of tag strings |
settings | object | No | Workflow settings |
maxConcurrentExecutions | integer | No | Max concurrent runs: how many of the workflow's runs may be in flight at once, 1-10 (default 1). A Queue trigger keeps this many messages' runs going |
status | string | No | draft, active, paused or archived |
A deployed workflow is not updated (409, undeploy it first). A lifecycle policy the workflow's trigger cannot run under is refused (400): a Queue trigger needs always-on or scheduled-window.
Saving moves every node to the latest published version of its catalog node.
Response 200 OK
Returns the updated Workflow object, with inputsNoLongerDeclared: each node whose input mappings name an input its previous version declared and its latest version does not (nodeId, label, fromVersion, toVersion, inputs). Those mappings now feed nothing: remap them. It is empty when no node lost a mapped input.
DELETE /api/v1/workflows/:id
Delete a workflow. This action is irreversible.
Scope: workflows:write
Path Parameters
| Parameter | Type | Required | Description |
|---|---|---|---|
id | string | Yes | Workflow ID |
Response 204 No Content
POST /api/v1/workflows/:id/duplicate
Duplicate an existing workflow. Creates a copy with a new ID and draft status.
Scope: workflows:write
Path Parameters
| Parameter | Type | Required | Description |
|---|---|---|---|
id | string | Yes | Workflow ID to duplicate |
Response 201 Created
Returns the new Workflow object.
POST /api/v1/workflows/:id/save-as-template
Save a copy of a workflow as a template, named <workflow> Template. It is listed by
GET /api/v1/workflows/templates and can be instantiated with POST /api/v1/workflows/from-template.
Scope: workflows:write
| Parameter | Type | Required | Description |
|---|---|---|---|
id | string | Yes | The workflow to save as a template |
Response 201 Created: the new template, a Workflow object with isTemplate true.
POST /api/v1/workflows/:id/execute
Execute a workflow. Submits the workflow for execution and returns an execution ID.
Scope: workflows:execute
Path Parameters
| Parameter | Type | Required | Description |
|---|---|---|---|
id | string | Yes | Workflow ID |
Request Body
{
"definition": {
"nodes": [],
"connections": []
},
"triggerInputs": {
"key": "value"
}
}
| Field | Type | Required | Description |
|---|---|---|---|
definition | object | No | Override workflow definition for this execution |
triggerInputs | object | No | Input data to pass to the trigger node |
Response 201 Created
The call waits briefly for the run to finish. status is the run's status when the call returns; outputs (each node's output) and a short note are included once the run has finished. When the workflow has a trigger node, invocation describes how to call it.
{
"data": {
"executionId": "exec_xyz789",
"status": "completed",
"outputs": {
"node_1": { "rows": [] }
},
"note": "Every node's complete output (embedding vectors summarized by shape). Verify it is correct and non-empty."
},
"meta": {
"requestId": "req_abc123"
}
}
POST /api/v1/workflows/events
Emit a platform event. It starts every deployed workflow you may use whose Event trigger listens for eventType (its Event Types or Custom Event Types name it, or it names none), each run as you, with inputs {event_type, event_data, source, emitted_at}. A draft never starts. A workflow.completed or workflow.failed event is also emitted by the platform itself when a run ends (see Triggers: Event); an Event trigger with a Source Workflow hears those only from that workflow. Each entry of results has the started run's executionId, or the error that kept that workflow from starting.
Scope: workflows:execute
Request Body
{
"eventType": "user.created",
"eventData": {
"userId": "user_456"
},
"source": "platform"
}
| Field | Type | Required | Description |
|---|---|---|---|
eventType | string | Yes | Event type name |
eventData | object | No | Event payload |
source | string | No | Source label (default: platform) |
Response 200 OK
{
"data": {
"eventType": "user.created",
"workflowsTriggered": 2,
"workflowsFailed": 0,
"results": [
{
"workflowId": "wf_abc123",
"workflowName": "Send welcome email",
"executionId": "exec_xyz789"
}
]
},
"meta": {
"requestId": "req_abc123"
}
}
POST /api/v1/workflows/:id/deploy
Deploy a workflow. Provisions the required infrastructure and makes the workflow ready for execution. The deploy runs in the background: poll GET /workflows/:id/status until deploymentStatus is running, scheduled (a schedule-triggered workflow) or failed.
Scope: workflows:write
Path Parameters
| Parameter | Type | Required | Description |
|---|---|---|---|
id | string | Yes | Workflow ID |
Response 202 Accepted
The workflow, as GET /workflows/:id shows it, deploymentStatus queued.
POST /api/v1/workflows/:id/undeploy
Undeploy a workflow. Tears down the provisioned infrastructure.
Scope: workflows:write
Path Parameters
| Parameter | Type | Required | Description |
|---|---|---|---|
id | string | Yes | Workflow ID |
Response 200 OK
The workflow, as GET /workflows/:id shows it, back to draft.
POST /api/v1/workflows/:id/stop
Stop a deployed workflow by scaling it to zero pods ($0 compute cost). Preserves the deployment for a fast restart via /start.
Scope: workflows:write
Path Parameters
| Parameter | Type | Required | Description |
|---|---|---|---|
id | string | Yes | Workflow ID |
Response 202 Accepted
The workflow, as GET /workflows/:id shows it, deploymentStatus stopping.
POST /api/v1/workflows/:id/start
Start a stopped workflow by scaling its deployment back up (~10s restart). Much faster than a full deploy because all Kubernetes resources are preserved.
Scope: workflows:write
Path Parameters
| Parameter | Type | Required | Description |
|---|---|---|---|
id | string | Yes | Workflow ID |
Request Body
{
"replicas": 1
}
| Field | Type | Required | Description |
|---|---|---|---|
replicas | number | No | Number of replicas to start (default: 1) |
Response 202 Accepted
The workflow, as GET /workflows/:id shows it, deploymentStatus starting.
GET /api/v1/workflows/:id/lifecycle
Get the lifecycle policy and current deployment status for a workflow.
Scope: workflows:read
Path Parameters
| Parameter | Type | Required | Description |
|---|---|---|---|
id | string | Yes | Workflow ID |
Response 200 OK
{
"data": {
"workflowId": "wf_abc123",
"lifecyclePolicy": {
"type": "idle-shutdown",
"idleTimeoutMinutes": 30
},
"deploymentStatus": "running",
"stoppedReason": null
},
"meta": {
"requestId": "req_abc123"
}
}
PUT /api/v1/workflows/:id/lifecycle
Update the lifecycle policy for a workflow. Controls when the deployed pod is kept warm versus scaled down.
Scope: workflows:write
Path Parameters
| Parameter | Type | Required | Description |
|---|---|---|---|
id | string | Yes | Workflow ID |
Request Body
{
"type": "scheduled-window",
"schedule": {
"timezone": "America/Los_Angeles",
"windows": [
{
"days": [1, 2, 3, 4, 5],
"startTime": "09:00",
"endTime": "17:00"
}
]
}
}
| Field | Type | Required | Description |
|---|---|---|---|
type | string | Yes | One of always-on, idle-shutdown, on-demand, scheduled-window |
idleTimeoutMinutes | number | No | Minutes of idleness before shutdown (5–1440, default: 30). Required for idle-shutdown. |
schedule | object | No | Required for scheduled-window. Contains timezone and windows[] of { days: number[], startTime: "HH:MM", endTime: "HH:MM" }. days are 0 (Sunday) through 6 (Saturday). |
Response 200 OK
{
"data": {
"workflowId": "wf_abc123",
"lifecyclePolicy": {
"type": "scheduled-window",
"schedule": {
"timezone": "America/Los_Angeles",
"windows": [
{
"days": [1, 2, 3, 4, 5],
"startTime": "09:00",
"endTime": "17:00"
}
]
}
}
},
"meta": {
"requestId": "req_abc123"
}
}
A workflow whose trigger is a Queue trigger takes only always-on or scheduled-window (it consumes its queue while its pod runs); another policy is refused with 400.
GET /api/v1/workflows/:id/status
Get a workflow's deployment status, its live health and how to call it once it is deployed. Poll it after a deploy, start or stop.
Scope: workflows:read
Path Parameters
| Parameter | Type | Required | Description |
|---|---|---|---|
id | string | Yes | Workflow ID |
Response 200 OK
{
"data": {
"workflowId": "wf_abc123",
"deploymentStatus": "running",
"health": {
"status": "healthy",
"podStatus": "running",
"checkedAt": "2025-02-01T14:22:00Z",
"error": null,
"replicas": { "desired": 1, "ready": 1, "available": 1 }
},
"healthUpdatedAt": "2025-02-01T14:22:00Z",
"stoppedReason": null,
"invocation": {
"triggerType": "webhook",
"url": "https://app.strongly.ai/api/v1/webhooks/wf_abc123",
"method": "POST",
"provider": "generic",
"authRequired": true,
"secretConfigured": false
}
},
"meta": {
"requestId": "req_abc123"
}
}
The workflow is ready to take calls when health.replicas.ready is 1 or more. health is null until the workflow's first health check, and stays null for a scheduled workflow, which has no worker to check. stoppedReason says why a deploy, start or stop failed. invocation is how to call the deployed workflow: its address, method and the ways a call can authenticate (a webhook also lists them in authModes). A Form trigger gives its public form address (no authentication) and captcha, which says whether CAPTCHA is on and has its secret key. A Schedule trigger has no address: schedule is what runs (cron, dailyTime or intervalMinutes, with timezone). It is null for a workflow without a trigger.
GET /api/v1/workflows/:id/metrics
How a workflow's runs went over a window.
Scope: workflows:read
| Parameter | Type | Required | Description |
|---|---|---|---|
id | string | Yes | Workflow id |
window | string | No | 1h, 24h (default), 7d or 30d |
Response 200 OK: workflowId, window, since, totals (total, byStatus,
errorRate), duration (avgMs, sampleSize) and lastExecutionAt.
GET /api/v1/workflows/:id/logs
The latest log lines of a deployed workflow's pod.
Scope: workflows:read
| Parameter | Type | Required | Description |
|---|---|---|---|
id | string | Yes | Workflow id |
lines | integer | No | How many lines, newest last (at most 2000) |
container | string | No | A container of the pod (default the worker's) |
Response 200 OK: the log lines.
Deployment status
deploymentStatus | Meaning |
|---|---|
queued | A deploy was accepted. |
deploying | A deploy is in progress. |
running | The workflow is deployed and its worker is serving. A deploy, a start and an automatic start on a call all end here. |
scheduled | A schedule-triggered workflow is deployed: its schedule starts each run, and there is no worker to call. |
starting | A start is in progress. |
stopping | A stop is in progress. |
stopped | The workflow is deployed with its worker scaled to zero. |
undeploying | An undeploy is in progress. |
failed | A deploy, start, stop or undeploy failed, or the deployment no longer exists. stoppedReason says why. |
null | The workflow is not deployed. |
The platform keeps deploymentStatus in line with what is actually running: a worker that was scaled to zero or removed outside a request shows as stopped or failed within about 30 seconds, and a running worker's health is checked on the same cycle.
GET /api/v1/workflows/:id/executions
List a workflow's runs, newest first.
Scope: workflows:read
Path Parameters
| Parameter | Type | Required | Description |
|---|---|---|---|
id | string | Yes | Workflow ID |
Query Parameters
| Parameter | Type | Required | Description |
|---|---|---|---|
status | string | No | Filter by status, one of the run statuses |
triggerType | string | No | Filter by trigger type: manual, schedule, webhook, api |
since | string | No | ISO 8601 datetime. Return runs created at or after this time |
until | string | No | ISO 8601 datetime. Return runs created at or before this time |
limit | integer | No | Number of results to return (default: 50, max: 200) |
cursor | string | No | meta.nextCursor of the previous page; omit for the first page |
sort | string | No | Sort field, prefixed with - for descending (default: -created_at, newest first) |
Response 200 OK
The same list as GET /api/v1/workflows/-/executions for this workflow: execution objects without definition, spans and logs, with meta.total. Runs that share a sort value are ordered by _id, so paging with limit and cursor returns each run once. A workflow the caller cannot see answers 404.
POST /api/v1/workflows/:id/validate
Validate the workflow's structure and its control-flow wiring (loops, merges), and bring its loop scopes up to date. Checks for empty workflows, broken connections, disconnected nodes, nodes that need configuration, the absence of a trigger node, and loops missing their merge. valid: false means a blocking problem that would fail a deploy; each error carries a message, a suggestion and, where one exists, a machine-readable fix.
Scope: workflows:write
Path Parameters
| Parameter | Type | Required | Description |
|---|---|---|---|
id | string | Yes | Workflow ID |
Response 200 OK
{
"data": {
"valid": false,
"blockingCount": 1,
"errors": [
{
"nodeId": null,
"nodeType": "workflow",
"nodeLabel": "Daily ETL Pipeline",
"severity": "error",
"message": "This batch workflow has no trigger node, so it has no entry point and can never start",
"suggestion": "Add a trigger node (Schedule, Webhook, Chat, Event, etc.) as the workflow's starting point"
}
],
"scopeCount": 0
},
"meta": {
"requestId": "req_abc123"
}
}
GET /api/v1/workflows/:id/structure-check
Structural checks of a workflow: connections to missing nodes, nodes connected to nothing, no
trigger, service nodes with no service chosen. Run it with POST …/validate (which checks the
control flow and loop wiring) before you deploy.
Scope: workflows:read
| Parameter | Type | Required | Description |
|---|---|---|---|
id | string | Yes | Workflow id |
Response 200 OK: workflowId, valid, errors (each problem that stops a run),
warnings, nodeCount and connectionCount.
GET /api/v1/workflows/:id/dependency-access
What a workflow uses that you may not: add-ons, data sources, AI models, ML models, MCP servers, other workflows, avatars and custom nodes, each with its owner. A draft runs as whoever runs it and a deployment as whoever deploys it, so what is listed must be shared with that person first.
Scope: workflows:read
| Parameter | Type | Required | Description |
|---|---|---|---|
id | string | Yes | Workflow id |
for | string | Yes | run or deploy |
Response 200 OK: what you may not use, with each owner. An empty list means nothing stops you.
POST /api/v1/workflows/:id/arrange
Automatically arrange all nodes in the workflow in a clean left-to-right layout.
Scope: workflows:write
Path Parameters
| Parameter | Type | Required | Description |
|---|---|---|---|
id | string | Yes | Workflow ID |
Response 200 OK
{
"data": {
"workflowId": "wf_abc123",
"changed": true,
"nodesRepositioned": 4
},
"meta": {
"requestId": "req_abc123"
}
}
GET /api/v1/workflows/:id/versions
List all versions of a workflow.
Scope: workflows:read
Path Parameters
| Parameter | Type | Required | Description |
|---|---|---|---|
id | string | Yes | Workflow ID |
Response 200 OK
{
"data": {
"workflowId": "wf_abc123",
"currentVersion": 3,
"deployedVersion": 2,
"versions": [
{
"_id": "ver_003",
"workflowId": "wf_abc123",
"versionNumber": 3,
"name": "Refactored pipeline",
"isCurrent": true,
"isDeployed": false,
"createdBy": "user_456",
"createdByName": "Jane Smith",
"createdAt": "2025-02-01T14:22:00Z"
},
{
"_id": "ver_002",
"workflowId": "wf_abc123",
"versionNumber": 2,
"name": "Added error handling",
"isCurrent": false,
"isDeployed": true,
"deployedAt": "2025-01-20T08:20:00Z",
"deployedBy": "user_456",
"createdBy": "user_456",
"createdByName": "Jane Smith",
"createdAt": "2025-01-20T08:15:00Z"
}
]
},
"meta": {
"requestId": "req_abc123"
}
}
Versions are listed newest first, without their definitions (GET /api/v1/workflows/:id/versions/:versionId returns one version with its definition). name is the version's message. currentVersion is the workflow's current version and deployedVersion the version that is deployed; either is null when there is none.
GET /api/v1/workflows/:workflowId/versions/:id
Get one version of a workflow with its complete definition.
Scope: workflows:read
Path Parameters
| Parameter | Type | Required | Description |
|---|---|---|---|
workflowId | string | Yes | Workflow ID |
id | string | Yes | Version ID, from the version list |
Response 200 OK
{
"data": {
"workflowId": "wf_abc123",
"versionId": "ver_002",
"versionNumber": 2,
"message": "Added error handling",
"createdAt": "2025-01-20T08:15:00Z",
"createdBy": "user_456",
"isCurrent": false,
"isDeployed": true,
"git": null,
"definition": {
"name": "Daily ETL Pipeline",
"description": "Extracts data from MySQL, transforms, and loads to S3",
"workflowType": "batch",
"nodes": [],
"connections": [],
"settings": {},
"tags": ["etl", "production"],
"scopes": {},
"version": 2,
"exportedAt": "2025-02-01T14:22:00.000Z",
"exportedBy": "user_456"
}
},
"meta": {
"requestId": "req_abc123"
}
}
definition is the workflow as the version froze it: every node with its full configuration and every connection, plus its type, mode, settings and tags. It is the same definition a git push of the version commits. git names the commit when the version came from or went to git. A version migrated from the retired version store has no definition and answers 409 (no-snapshot).
POST /api/v1/workflows/:id/versions
Create a new version snapshot of the workflow.
Scope: workflows:write
Path Parameters
| Parameter | Type | Required | Description |
|---|---|---|---|
id | string | Yes | Workflow ID |
Request Body
{
"versionTag": "v2.1.0",
"description": "Added retry logic to S3 upload node"
}
| Field | Type | Required | Description |
|---|---|---|---|
versionTag | string | Yes | Version tag label |
description | string | No | Description of changes in this version |
Response 201 Created
{
"data": {
"workflowId": "wf_abc123",
"versionId": "ver_004",
"versionNumber": 4
},
"meta": {
"requestId": "req_abc123"
}
}
POST /api/v1/workflows/:workflowId/versions/:id/deploy
Deploy a saved version of a workflow: roll back to it or promote it. Its nodes and connections
become the deployment, marked as the deployed version. The deploy runs on: poll
GET /api/v1/workflows/:id/status until deploymentStatus is active or failed.
Scope: workflows:write
| Parameter | Type | Required | Description |
|---|---|---|---|
workflowId | string | Yes | Workflow id |
id | string | Yes | Version id, from GET /api/v1/workflows/:id/versions |
Request Body (optional)
| Field | Type | Description |
|---|---|---|
environmentId | string | Run at a saved environment's size |
cpu, memory, disk | string | Its size, for example "0.5", "1Gi", "5Gi" |
gpu, gpuType | string | GPUs and their type |
Response 202 Accepted: the workflow, deploymentStatus queued. A version that cannot be deployed (not found, no nodes, a dependency you cannot use, a pending governance gate, an environment that cannot run workers) is refused at once with its 4xx. Poll GET /api/v1/workflows/:id/status until deploymentStatus is active or failed.
Sharing
Who can reach a workflow is the platform's one sharing shape: its owner, its members
(role editor can use and change it, user can only use it) and its visibility
(public: every user can find and use it; in a multi-tenant deployment, every user of
its organization). Changing it stays with its owner and editors.
| Method | Path | Does | Scope |
|---|---|---|---|
| GET | /api/v1/workflows/:id/permissions | Owner, members (userId, role) and visibility | workflows:read |
| POST | /api/v1/workflows/:id/permissions/members | Share with a user: { "userId", "role": "editor" | "user" } | workflows:write |
| DELETE | /api/v1/workflows/:workflowId/permissions/members/:userId | Stop sharing with a user | workflows:write |
| PATCH | /api/v1/workflows/:id/permissions | { "visibility": "public" | "private" } | workflows:write |
Each, except a member's removal (204), answers the permissions as they are now:
{
"data": {
"resourceId": "<workflow id>",
"owner": "<user id>",
"members": [{ "userId": "<user id>", "role": "user" }],
"visibility": "private"
},
"meta": { "requestId": "req_abc123" }
}
GET /api/v1/workflows/:id/nodes
List nodes in a workflow with a summary of their inbound and outbound connection counts.
Scope: workflows:read
Path Parameters
| Parameter | Type | Required | Description |
|---|---|---|---|
id | string | Yes | Workflow ID |
Response 200 OK
{
"data": {
"workflowId": "wf_abc123",
"count": 2,
"nodes": [
{
"id": "mysql-a1B2c3",
"type": "mysql",
"label": "Extract Orders",
"category": "sources",
"position": { "x": 100, "y": 200 },
"hasConfig": true,
"inboundConnections": 0,
"outboundConnections": 1
}
]
},
"meta": {
"requestId": "req_abc123"
}
}
POST /api/v1/workflows/:id/nodes
Add a node to a workflow. The node metadata (type, category, S3 location, resources) is resolved from the workflow node catalog by nodeId. The position is auto-computed if not provided.
Scope: workflows:write
Path Parameters
| Parameter | Type | Required | Description |
|---|---|---|---|
id | string | Yes | Workflow ID |
Request Body
{
"nodeId": "mysql-sources",
"label": "Extract Orders",
"version": "1.1.3",
"position": { "x": 100, "y": 200 },
"config": {
"dataSource": "ds_001",
"query": "SELECT * FROM orders"
}
}
| Field | Type | Required | Description |
|---|---|---|---|
nodeId | string | Yes | Catalog node identifier (alias: nodeType for legacy callers) |
label | string | No | Custom label. Defaults to the catalog name. |
position | object | No | Position { x, y }. Auto-computed if omitted. |
config | object | No | Initial node configuration |
Response 201 Created
{
"data": {
"nodeId": "mysql-a1B2c3",
"workflowId": "wf_abc123",
"node": {
"id": "mysql-a1B2c3",
"type": "mysql",
"category": "sources",
"nodeId": "mysql-sources",
"label": "Extract Orders",
"nodeVersion": "1.1.3",
"source": {
"type": "mysql",
"category": "sources",
"nodeVersion": "1.1.3"
},
"position": { "x": 100, "y": 200 },
"data": {},
"config": {
"dataSource": "ds_001",
"query": "SELECT * FROM orders"
},
"resources": {}
}
},
"meta": {
"requestId": "req_abc123"
}
}
PATCH /api/v1/workflows/:workflowId/nodes/:id
Configure an existing node in a workflow. Merges the provided config into the node's existing config.
Scope: workflows:write
Path Parameters
| Parameter | Type | Required | Description |
|---|---|---|---|
workflowId | string | Yes | Workflow ID |
id | string | Yes | Node ID within the workflow |
Request Body
{
"config": {
"query": "SELECT * FROM orders WHERE created_at > NOW() - INTERVAL 1 DAY"
},
"label": "Extract Recent Orders"
}
| Field | Type | Required | Description |
|---|---|---|---|
config | object | No | Configuration key-value pairs to merge into the node |
label | string | No | Updated label for the node |
If the body is not wrapped in a config field, the body itself is treated as the config payload.
Response 200 OK
{
"data": {
"nodeId": "mysql-a1B2c3",
"workflowId": "wf_abc123",
"node": {
"id": "mysql-a1B2c3",
"type": "mysql",
"category": "sources",
"nodeId": "mysql-sources",
"label": "Extract Recent Orders",
"nodeVersion": "1.1.3",
"position": { "x": 100, "y": 200 },
"config": {
"dataSource": "ds_001",
"query": "SELECT * FROM orders WHERE created_at > NOW() - INTERVAL 1 DAY"
}
}
},
"meta": {
"requestId": "req_abc123"
}
}
DELETE /api/v1/workflows/:workflowId/nodes/:id
Remove a node and all of its incoming and outgoing connections from a workflow.
Scope: workflows:write
Path Parameters
| Parameter | Type | Required | Description |
|---|---|---|---|
workflowId | string | Yes | Workflow ID |
id | string | Yes | Node ID to remove |
Response 204 No Content. Its connections go with it.
PUT /api/v1/workflows/:workflowId/nodes/:id/input-mappings
Set input mappings for a node. Maps incoming data fields to the node's declared inputs.
Scope: workflows:write
Path Parameters
| Parameter | Type | Required | Description |
|---|---|---|---|
workflowId | string | Yes | Workflow ID |
id | string | Yes | Node ID |
Request Body
{
"inputMappings": {
"userId": "data.user.id",
"email": "data.user.email"
}
}
| Field | Type | Required | Description |
|---|---|---|---|
inputMappings | object | Yes | Object mapping target field names to source expressions |
Response 200 OK
{
"data": {
"nodeId": "mysql-a1B2c3",
"workflowId": "wf_abc123",
"inputMappings": {
"userId": "data.user.id",
"email": "data.user.email"
}
},
"meta": {
"requestId": "req_abc123"
}
}
PUT /api/v1/workflows/:workflowId/nodes/:id/passthrough-values
Set the passThroughValues for a node, saved to the node's config.passThroughValues. Each entry { "outputKey": "inputName" } copies the node's input named inputName (one of its inputMappings keys) into its output as data.<outputKey>. An input name the node does not map is skipped at run time.
Scope: workflows:write
Path Parameters
| Parameter | Type | Required | Description |
|---|---|---|---|
workflowId | string | Yes | Workflow ID |
id | string | Yes | Node ID |
Request Body
{
"values": {
"correlationId": "correlationId",
"tenantId": "tenantId"
}
}
| Field | Type | Required | Description |
|---|---|---|---|
values | object | Yes | Object mapping each output key to the name of one of the node's inputs |
Response 200 OK
{
"data": {
"nodeId": "mysql-a1B2c3",
"workflowId": "wf_abc123",
"passThroughValues": {
"correlationId": "correlationId",
"tenantId": "tenantId"
}
},
"meta": {
"requestId": "req_abc123"
}
}
POST /api/v1/workflows/:id/connections
Connect two nodes in a workflow. For agent nodes, set targetPort to ai or tools.
Scope: workflows:write
Path Parameters
| Parameter | Type | Required | Description |
|---|---|---|---|
id | string | Yes | Workflow ID |
Request Body
{
"sourceNodeId": "node_1",
"targetNodeId": "node_2",
"sourcePort": "output",
"targetPort": "input"
}
| Field | Type | Required | Description |
|---|---|---|---|
sourceNodeId | string | Yes | Source node ID |
targetNodeId | string | Yes | Target node ID |
sourcePort | string | No | Source port (default: output) |
targetPort | string | No | Target port (default: input). Use ai or tools for agent nodes. |
Response 201 Created
{
"data": {
"connectionId": "conn-abc123",
"workflowId": "wf_abc123",
"connection": {
"id": "conn-abc123",
"source": "node_1",
"target": "node_2",
"sourcePort": "output",
"targetPort": "input"
}
},
"meta": {
"requestId": "req_abc123"
}
}
DELETE /api/v1/workflows/:workflowId/connections/:id
Remove a connection between two nodes in a workflow.
Scope: workflows:write
Path Parameters
| Parameter | Type | Required | Description |
|---|---|---|---|
workflowId | string | Yes | Workflow ID |
id | string | Yes | Connection ID to remove |
Response 204 No Content
GET /api/v1/workflows/discover
Discover production workflows belonging to a marketplace app. A marketplace app running on the platform uses it to find the workflows it owns and the in-platform address to call each one at.
Scope: workflows:read
Query Parameters
| Parameter | Type | Required | Description |
|---|---|---|---|
appId | string | Yes | Marketplace app ID |
Response 200 OK
{
"data": [
{
"id": "wf_abc123",
"name": "Daily ETL Pipeline",
"description": "Extracts data from MySQL, transforms, and loads to S3",
"triggerType": "webhook",
"mode": "production",
"endpoints": {
"proxyUrl": "<in-platform address of the workflow's trigger endpoint>",
"method": "POST"
}
}
],
"meta": {
"requestId": "req_abc123"
}
}
endpoints.proxy_url is an address inside the platform. It is returned only to marketplace apps running on the platform, which call the workflow there with endpoints.method. A request made with an API key gets the same entries without proxyUrl, since the address cannot be reached from outside the platform:
{
"id": "wf_abc123",
"name": "Daily ETL Pipeline",
"description": "Extracts data from MySQL, transforms, and loads to S3",
"triggerType": "webhook",
"mode": "production",
"endpoints": {
"method": "POST"
}
}
GET /api/v1/workflows/templates
List available workflow templates.
Scope: workflows:read
Response 200 OK
{
"data": [
{
"_id": "wf_tmpl_001",
"name": "Basic ETL Pipeline",
"description": "A starter template for extract-transform-load workflows",
"isTemplate": true,
"tags": ["etl", "starter"],
"nodes": [],
"connections": [],
"createdAt": "2025-01-01T00:00:00Z"
}
],
"meta": {
"requestId": "req_abc123"
}
}
GET /api/v1/workflows/stats
Get aggregate statistics for the workflows accessible to the authenticated user (the workflows GET /api/v1/workflows lists; templates are not counted).
Scope: workflows:read
Response 200 OK
{
"data": {
"total": 42,
"active": 18,
"paused": 5,
"draft": 15,
"archived": 4
},
"meta": {
"requestId": "req_abc123"
}
}
Workflow tools (MCP servers)
A workflow tool is an MCP server from the platform's catalog (see Workflow Tools). Activating one registers it for you with your own configuration values; your calls through it use them, and no one else's do.
POST /api/v1/workflow-tools/:id/activate
Register the MCP server for you (again, to register it anew).
Scope: workflows:write
| Parameter | Type | Required | Description |
|---|---|---|---|
id | string | Yes | The workflow tool |
Request Body
| Field | Type | Required | Description |
|---|---|---|---|
config | object | No | Your configuration values for the server (its keys, as the tool defines them) |
Response 200 OK: for an MCP server, your registration of it: { toolId, mcpId, state, registeredAt, updatedAt }, state active. For any other tool, the tool's node as GET /workflow-nodes/:id shows it, its deployment starting.
POST /api/v1/workflow-tools/:id/deactivate
Remove your registration of the MCP server.
Scope: workflows:write
| Parameter | Type | Required | Description |
|---|---|---|---|
id | string | Yes | The workflow tool |
Response 200 OK: your registration, { toolId, mcpId, state }, state not_active.
PATCH /api/v1/workflow-tools/:id
Switch a registered MCP server's tools on or off, keeping the registration.
Scope: workflows:write
| Parameter | Type | Required | Description |
|---|---|---|---|
id | string | Yes | The workflow tool |
Request Body
| Field | Type | Required | Description |
|---|---|---|---|
enabled | boolean | Yes | Whether its tools are on |
Response 200 OK: your registration, { toolId, mcpId, state, registeredAt, updatedAt }, state active or disabled.