POST /v1/workflows/validate
Validate a workflow definition without saving

Request Body required

Workflow definition and name to validate

application/json
One of:
Option 1
Option 2
definition object REQUIRED
Definition is the inline workflow graph to analyze. Same shape as a saved workflow's definition. Unlike the ephemeral-run endpoint there is no trigger restriction — analysis is read-only and method-agnostic.
blockExpansions object
Set on run snapshots only (not workflow DB)
inputSchema object[]
Array of:
description string
key string
required boolean
type string
"string", "number", "boolean", "object", "array"
nodes object REQUIRED
sensitivePropKeys string[]
Legacy: kept for old runs; no longer populated for new workflows
Array of:
triggerNodeIds string[]
TriggerNodeIDs lists the node IDs that are trigger (root) nodes. Every workflow declares this — single-trigger workflows ship ["root"] (the legacy node id), multi-trigger workflows list every trigger node id. Treating single-trigger as a forest-of-1 removes the two-path branching throughout the BE + FE; older rows without the field are backfilled by migration 000282 and the field-missing path stays as a read-side safety net (see FindTriggerNodeIDs) but is no longer exercised by saves. "root" is also a runtime alias for "the trigger that fired this run" — {{root.X}} variable references resolve to the firing trigger regardless of which trigger fired. Don't repurpose the literal "root" as a trigger id on a multi-trigger workflow.
Array of:
variableDefs object[]
VariableDefs is a snapshot of the workflow's declared variables at run creation time. The canonical source lives on the workflows row (Workflow.VariableDefs column). Snapshotted into the run definition so the worker can resolve {{$vars.x}} lookups and route variable-action writes to the right scope without an extra DB round trip.
Array of:
default unknown
description string
lifetime string
Enum: persist, reset
name string
type string
Enum: number, text, boolean, list, object
name string

Responses

200 OK
application/json
code integer
data object
errors object[]
Array of:
code string
Optional stable machine code for consumers to branch on (e.g. pure_variable_type_mismatch)
field string
Optional field name
message string
Human readable error message
nodeId string
Optional node ID
nodeName string
Optional node display name
type string
name, nodes, root, connectivity, pattern, variable
valid boolean
message string
requestId string
400 Bad Request
curl -X POST 'https://api.example.com/v1/workflows/validate' \  -H 'Authorization: Bearer YOUR_API_TOKEN' \  -H 'Content-Type: application/json' \  -d '{}'
const response = await fetch('https://api.example.com/v1/workflows/validate', {  method: 'POST',  headers: {      "Authorization": "Bearer YOUR_API_TOKEN",      "Content-Type": "application/json"  },  body: JSON.stringify({})});const data = await response.json();console.log(data);
import requestsheaders = {    'Authorization': 'Bearer YOUR_API_TOKEN'}response = requests.post('https://api.example.com/v1/workflows/validate', headers=headers, json={})print(response.json())
package mainimport (	"fmt"	"io"	"net/http"	"strings")func main() {	body := strings.NewReader(`{}`)	req, _ := http.NewRequest("POST", "https://api.example.com/v1/workflows/validate", body)	req.Header.Set("Authorization", "Bearer YOUR_API_TOKEN")	req.Header.Set("Content-Type", "application/json")	resp, _ := http.DefaultClient.Do(req)	defer resp.Body.Close()	result, _ := io.ReadAll(resp.Body)	fmt.Println(string(result))}
200 Response
{  "code": 200,  "data": {    "errors": [      {        "code": "<string>",        "field": "<string>",        "message": "<string>",        "nodeId": "<string>",        "nodeName": "<string>",        "type": "<string>"      }    ],    "valid": true  },  "message": "success",  "requestId": "abc-123"}