Skip to main content

Validation API

The Validation API provides server-side workflow graph validation and sub-workflow loop detection. The canvas calls this endpoint automatically on every change (debounced at 500 ms), but it can also be called directly for programmatic validation.

Validate Workflow

Validate the graph structure of a workflow and detect circular sub-workflow references.

POST /api/workflows/validate

Request Body

{
"workflowData": {
"nodes": {
"n1": {
"entityId": "550e8400-e29b-41d4-a716-446655440001",
"name": "Check User Type",
"entityType": "event",
"tfCondition": "Single Path",
"presentation": { "type": "circle", "position": { "x": 100, "y": 200 } }
},
"n2": {
"entityId": "550e8400-e29b-41d4-a716-446655440010",
"name": "Generate Welcome",
"entityType": "prompt",
"presentation": { "type": "brain", "position": { "x": 350, "y": 200 } }
}
},
"connectors": {
"c1": { "source": "n1", "target": "n2", "sourcePort": "out", "targetPort": "in" }
},
"workflow": {
"ref": "n1",
"children": [{ "ref": "n2", "children": [] }]
}
},
"workflowId": "660e8400-e29b-41d4-a716-446655440099"
}
FieldTypeRequiredDescription
workflowDataWorkflowData | nullYesThe serialized workflow graph to validate (see Workflow Data Schema)
workflowIdstringNoThe ID of the workflow being validated. Required for sub-workflow loop detection — without it, only graph checks run

Response (200)

{
"errors": [
{ "type": "warning", "message": "1 node(s) are not linked to entities." }
],
"valid": true
}
FieldTypeDescription
errorsValidationError[]Array of validation errors and warnings
validbooleantrue if no errors of type "error" exist (warnings do not affect validity)

ValidationError

interface ValidationError {
type: 'error' | 'warning';
message: string;
}

Authentication

Requires an authenticated session (Auth0 cookie). Returns 401 Unauthorized if not authenticated.


Graph Validation Rules

The endpoint checks the following structural rules against the workflowData:

RuleSeverityMessage
Empty workflowErrorWorkflow is empty. Add at least one Event node to start.
No entry EventErrorNo entry point Event node found. The workflow must start with an Event node that has no incoming connections.
Multiple entry EventsErrorMultiple entry point Event nodes found. Only one Event should have no incoming connections.
Orphaned nodesError{count} orphaned node(s) not connected to the workflow.
Unlinked entitiesWarning{count} node(s) are not linked to entities.

An entry Event is defined as an Event node (entityType: "event") that is not the target of any connector. The orphan check performs a BFS from the single entry Event and flags any nodes not reached.

Logic-branch and workflow nodes are excluded from the "unlinked entities" check since they do not require an entityId.


Sub-Workflow Loop Detection

When workflowId is provided and the graph contains workflow-type nodes (nodes with entityType: "workflow" and a workflowId), the server performs recursive loop detection:

  1. Collect all sub-workflow IDs referenced by workflow nodes in the current graph
  2. For each referenced workflow, load its workflowData from the database
  3. Recursively check the referenced workflow's sub-workflow nodes
  4. If any descendant references a workflow already on the current path, report a cycle

Example: Circular Reference Detected

If workflow A references workflow B, and workflow B references workflow A:

{
"errors": [
{
"type": "error",
"message": "Circular sub-workflow reference detected: wf-A-id → wf-B-id → wf-A-id"
}
],
"valid": false
}

Example: Valid Workflow with Sub-Workflows

A workflow that references two other workflows with no cycles:

Request:

{
"workflowData": {
"nodes": {
"n1": {
"entityId": "evt-uuid",
"name": "Route Order",
"entityType": "event",
"tfCondition": "True/False",
"presentation": { "type": "circle", "position": { "x": 100, "y": 200 } }
},
"n2": {
"name": "true",
"entityType": "logic-branch",
"branchValue": "true",
"presentation": { "type": "logic-branch", "position": { "x": 300, "y": 100 } }
},
"n3": {
"name": "Process Domestic",
"entityType": "workflow",
"workflowId": "domestic-wf-uuid",
"presentation": { "type": "rounded-square", "position": { "x": 500, "y": 100 } }
},
"n4": {
"name": "false",
"entityType": "logic-branch",
"branchValue": "false",
"presentation": { "type": "logic-branch", "position": { "x": 300, "y": 300 } }
},
"n5": {
"name": "Process International",
"entityType": "workflow",
"workflowId": "intl-wf-uuid",
"presentation": { "type": "rounded-square", "position": { "x": 500, "y": 300 } }
}
},
"connectors": {
"c1": { "source": "n1", "target": "n2", "sourcePort": "true", "targetPort": "in" },
"c2": { "source": "n2", "target": "n3", "sourcePort": "out", "targetPort": "in" },
"c3": { "source": "n1", "target": "n4", "sourcePort": "false", "targetPort": "in" },
"c4": { "source": "n4", "target": "n5", "sourcePort": "out", "targetPort": "in" }
},
"workflow": {
"ref": "n1",
"children": [
{ "ref": "n2", "value": "true", "children": [{ "ref": "n3", "children": [] }] },
{ "ref": "n4", "value": "false", "children": [{ "ref": "n5", "children": [] }] }
]
}
},
"workflowId": "parent-wf-uuid"
}

Response:

{
"errors": [],
"valid": true
}

Saving Behavior

The workflow save endpoints (POST /api/workflows and PUT /api/workflows/{id}) also run graph validation server-side:

  • Active workflows (isActive: true) with graph errors are rejected (400 Bad Request)
  • Inactive workflows can always be saved regardless of validation state, allowing work-in-progress drafts
  • Warnings never block saving

Error Responses

StatusCause
401Not authenticated
500Internal server error (e.g., database failure during loop detection)
{
"error": "Failed to validate workflow",
"details": "Database connection timeout"
}