Processes In-Depth
Process CRUD, the visual editor, node placement, edges, locking, forking, import/export.
A process is a visual workflow -- a directed graph of nodes connected by edges. When triggered, the execution engine traverses the graph, running each node and propagating data along edges.
Process Structure
Process
├── name: string
├── description: string
├── max_retries: int -- retry count on failure (0 = no retries)
├── is_running: bool -- whether input listeners are active
├── locked: bool -- prevents edits
├── error_webhook_url: string -- URL to POST error details on failure
│
├── Nodes[]
│ ├── id: UUID
│ ├── type: string -- "inputNode", "outputNode", "scriptNode", etc.
│ ├── position: { x, y } -- canvas coordinates
│ └── config: JSONB -- type-specific configuration
│
├── Edges[]
│ ├── source_node_id: UUID
│ ├── target_node_id: UUID
│ ├── source_handle: string
│ └── target_handle: string
│
└── Versions[]
├── version_number: int
├── label: string
└── snapshot: JSONB -- full graph at that pointCreating a Process
- Navigate to Processes.
- Click New Process.
- Enter a name and optional description.
- You are placed in the visual editor.
From Import
Import a process from JSON:
API: POST /processes/bulk-import/{orgId}
{
"name": "Imported Process",
"description": "...",
"nodes": [
{
"type": "inputNode",
"position": { "x": 100, "y": 200 },
"config": { ... }
}
],
"edges": [
{
"sourceNodeId": "node-1-uuid",
"targetNodeId": "node-2-uuid",
"sourceHandle": "output",
"targetHandle": "input"
}
],
"schemaDependencies": {
"schema-uuid": { ... }
}
}Schema dependencies are resolved during import -- if a referenced schema doesn't exist, it is created from the dependency data.
The Visual Editor
The editor is a canvas powered by ReactFlow. Key interactions:
| Action | How |
|---|---|
| Add a node | Drag from the Node Palette (left panel) onto the canvas |
| Connect nodes | Drag from an output port (right side of node) to an input port (left side of another node) |
| Move a node | Drag the node body; position auto-saves |
| Delete a node | Select and press Delete, or right-click > Delete |
| Delete an edge | Click the edge, then press Delete |
| Zoom | Scroll wheel or pinch |
| Pan | Click and drag on empty canvas |
| Auto-layout | Toolbar button to automatically arrange nodes |
Node Palette Categories
| Category | Nodes |
|---|---|
| I/O | Input, Output, SQL, Database |
| AI | ML Model, Schematization |
| Logic | Script, Conditional, Loop, Wait |
| Operations | Integrations, Web Automation, OCR |
Nodes
Node Properties
Every node has these common properties:
| Property | Type | Description |
|---|---|---|
id | UUID | Unique identifier (auto-generated) |
type | string | Node type identifier |
position | {x, y} | Canvas position |
config | JSONB | All type-specific settings |
Node Config
The config object varies by node type. Common fields across many types:
| Field | Description |
|---|---|
label | Display name (used in template references) |
passthroughFiles | Whether to forward input files to output |
The label is critical -- it is how other nodes reference this node's output in templates:
{{input["My Node Label"]["field_name"]}}If you rename a node's label, you must update all templates that reference it.
See Node Reference for every type's config.
Edges
Edge Properties
| Property | Description |
|---|---|
source_node_id | The node producing data |
target_node_id | The node consuming data |
source_handle | Output port name (default: "output") |
target_handle | Input port name (default: "input") |
Handle Names
Most nodes have a single "output" handle and "input" handle. Special cases:
| Node Type | Output Handles |
|---|---|
| Conditional (conditions mode) | "true", "false" |
| Conditional (switch mode) | One handle per case label, plus default |
| Schematization | One handle per selected data block name |
| Loop | "start" (to loop body), "output" (final results) |
| Script (multi-output) | One handle per configured output port name |
Only nodes connected to the active output handle receive data. Nodes on inactive branches are skipped.
Running a Process
Starting
- Click Start in the process toolbar, or
- API:
POST /processes/start/{processId}
This activates all input listeners (Gmail polling, webhook endpoint, cron scheduler). The process status changes to is_running: true.
Stopping
- Click Stop in the toolbar, or
- API:
POST /processes/stop/{processId}
This shuts down all input listeners. In-flight executions continue to completion, but no new executions start.
Manual Trigger
You can trigger a single execution without starting persistent listeners:
API: POST /processes/test/{processId}
This sends test data directly to the execution engine.
Execution Flow
When a trigger fires:
- The input node's data is captured.
- A
ProcessExecutionrecord is created with statusrunning. - The execution engine loads the full graph (nodes + edges).
- The graph is traversed concurrently -- nodes run as soon as all their parent nodes complete.
- Data propagates along edges (each node's output is merged into its children's input).
- On completion, the execution record is updated with status, output data, duration, and node states.
See Execution Engine for the internal mechanics.
Execution History
Every execution is recorded and queryable:
API: GET /processes/executions/{processId}?page=1&pageSize=20
Execution Record
| Field | Type | Description |
|---|---|---|
id | UUID | Execution record ID |
execution_id | string | Engine-assigned execution ID |
status | string | running, completed, failed, timeout, cancelled |
started_at | timestamp | When execution began |
completed_at | timestamp | When execution ended |
duration_seconds | int | Wall-clock duration |
input_data | JSONB | Data from the trigger |
output_data | JSONB | Final output data |
error_message | string | Error details if failed |
triggered_by_user_id | UUID | User who triggered (if manual) |
trigger_node_id | string | Which input node fired |
node_states | JSONB | Status and data for every node |
graph_snapshot | JSONB | Snapshot of the process graph at execution time |
Node States
The node_states field is a JSON array with one entry per node:
[
{
"node_id": "uuid",
"node_name": "Email Input",
"node_type": "inputNode",
"status": "completed",
"started_at": "2026-04-12T10:00:00Z",
"completed_at": "2026-04-12T10:00:01Z",
"duration_ms": 1200,
"input_data": { ... },
"output_data": { ... },
"error": null
}
]Execution Statistics
API: GET /processes/execution-stats/{processId}
Returns aggregated statistics: total runs, success rate, average duration, failure breakdown.
Error Webhook
Configure an error webhook URL to receive notifications when executions fail.
Field: error_webhook_url on the process.
When an execution fails, the engine POSTs error details to this URL including the process ID, execution ID, status, and failed node information.
Locking
Lock a process to prevent edits while it runs in production.
| Operation | API |
|---|---|
| Lock | POST /processes/lock/{processId} |
| Unlock | POST /processes/unlock/{processId} |
When locked:
- Nodes and edges cannot be added, modified, or deleted
- Process settings cannot be changed
- The process can still be started/stopped
- Executions still run normally
Forking (Duplicating)
Create a copy of a process:
API: POST /processes/fork/{processId}
{
"name": "Steel Order Quoter (Copy)"
}This duplicates all nodes, edges, and configs. Schema references point to the same schemas (not duplicated).
Export
Export a process as JSON:
API: GET /processes/{processId}/export
The export includes:
- Process metadata
- All nodes with configs
- All edges
- Schema dependencies (inline schema definitions for portability)
Process Validation (Linting)
API: POST /processes/{processId}/lint
Checks:
- At least one input node exists
- At least one output node exists
- All nodes are connected (no orphans)
- Required node config fields are populated
- Schema references are valid
- Edge source/target nodes exist
- No circular dependencies (DAG validation)