Graph Validation
Graph Compose validates workflow graphs before execution to catch structural and configuration errors early. Validation runs both client-side via the SDK and server-side via the API.
The API performs validation automatically before every workflow execution. You do not need to call the validate endpoint separately unless you want pre-flight feedback during development.
What validation checks
Both the SDK .validate() method and the POST /workflows/validate endpoint run the same set of checks:
| Check | Description |
|---|---|
| Schema compliance | The workflow graph matches the expected Zod schema structure. |
| Node ID format | Node IDs contain only alphanumeric characters and underscores. No dashes. |
| URL validity | HTTP node URLs are well-formed. |
| GET body restriction | GET requests do not include a request body. |
| JSONata syntax | Template expressions inside {{ '{{' }} }} delimiters are syntactically valid. |
| Dependency existence | Every dependency references a node that exists in the graph. |
| Circular dependencies | The directed acyclic graph (DAG) has no cycles. |
| continueTo targets | Every continueTo.to value references an existing node, and the target node includes the source in its dependencies. |
| Hanging nodes | In multi-node workflows, every node has at least one connection (as a dependency or dependant). |
| Disconnected subgraphs | All nodes belong to a single connected component. No isolated groups. |
| Error boundary coverage | Error boundaries protect at least one node, and all protected node IDs exist. |
| forEach loop structure | forEach / endForEach boundaries are valid (pairing, dependencies, and no cross-boundary dependencies). |
| ADK structure | Agent and tool references within ADK nodes are valid. |
The server-side endpoint also checks tier-based limits such as maximum node count and allowed node types based on your subscription.
This page covers pre-submission structural validation. For runtime data validation (input and output JSON Schema on individual nodes), see Validation.
Client SDK validation
Call .validate() on a GraphCompose instance to run all structural checks locally. This returns a ClientValidationResult with an isValid boolean and an errors array.
import { GraphCompose } from '@graph-compose/client'
const graph = new GraphCompose()
graph
.node("get_user")
.get("https://api.example.com/users/{{ context.userId }}")
.end()
.node("process_data")
.post("https://api.example.com/process")
.withBody({
name: "{{ results.get_user.data.name }}"
})
.withDependencies(["get_user"])
.end()
const validation = graph.validate()
if (!validation.isValid) {
for (const err of validation.errors) {
console.error(`[${err.name}] ${err.message}`)
}
}
Server-side validation via SDK
The SDK also provides a .validateApi() method that sends your workflow to the server for validation. This includes tier-based limit checks in addition to the structural checks above.
Server-side validation via SDK
const graph = new GraphCompose({
token: process.env.GRAPH_COMPOSE_TOKEN
})
graph
.node("fetch_data")
.get("https://api.example.com/data")
.end()
const result = await graph.validateApi()
if (result.success && result.data?.isValid) {
console.log("Workflow passed server validation")
} else {
console.error("Validation errors:", result.data?.errors)
}
REST API validation
Validate a workflow graph directly via the API without the SDK. Send the workflow definition to POST /workflows/validate and inspect the response. The organization is determined from your API key.
Validate a workflow via REST
const API_KEY = process.env.GRAPH_COMPOSE_TOKEN
// 1. Define the workflow graph
const workflow = {
nodes: [
{
id: "get_data",
type: "http",
http: {
method: "GET",
url: "https://api.example.com/data"
}
},
{
id: "process_data",
type: "http",
dependencies: ["get_data"],
http: {
method: "POST",
url: "https://api.example.com/process",
body: {
input: "{{ results.get_data.data.value }}"
}
}
}
],
context: {}
}
// 2. Send to the validate endpoint
const response = await fetch("https://api.graphcompose.io/workflows/validate", {
method: "POST",
headers: {
"Authorization": `Bearer ${API_KEY}`,
"Content-Type": "application/json"
},
body: JSON.stringify({ workflow })
})
const result = await response.json()
// 3. Check the validation result
if (result.success && result.data.isValid) {
console.log("Workflow is valid")
} else {
for (const err of result.data.errors) {
console.error(`[${err.name}] ${err.message}`)
}
}
The API returns a standard ApiResponse wrapper. The data field contains the validation result:
{
"success": true,
"message": "Workflow validation successful",
"data": {
"isValid": true,
"errors": []
}
}
The success field indicates whether the API call itself succeeded. A response with success: true and isValid: false means the request was processed but the workflow has errors.
Validation errors
Each error in the errors array has a name and message. The name identifies the error type and the message provides details.
| Error name | Cause |
|---|---|
ValidationError | Schema violations, invalid URLs, GET requests with a body, or invalid JSONata syntax. |
InvalidNodeIdError | A node ID contains characters other than alphanumeric and underscores. |
MissingDependencyError | A node lists a dependency or protected node that does not exist in the graph. |
CircularDependencyError | The dependency graph contains a cycle. |
InvalidConditionTargetError | A continueTo.to value references a node that does not exist. |
MissingConditionDependencyError | A continueTo target node does not include the source node in its dependencies. |
HangingNodeError | A node in a multi-node workflow has no connections to any other node. |
DisconnectedSubgraphError | The workflow contains two or more groups of nodes with no edges between them. |