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.

What validation checks

Both the SDK .validate() method and the POST /workflows/validate endpoint run the same set of checks:

CheckDescription
Schema complianceThe workflow graph matches the expected Zod schema structure.
Node ID formatNode IDs contain only alphanumeric characters and underscores. No dashes.
URL validityHTTP node URLs are well-formed.
GET body restrictionGET requests do not include a request body.
JSONata syntaxTemplate expressions inside {{ '{{' }} }} delimiters are syntactically valid.
Dependency existenceEvery dependency references a node that exists in the graph.
Circular dependenciesThe directed acyclic graph (DAG) has no cycles.
continueTo targetsEvery continueTo.to value references an existing node, and the target node includes the source in its dependencies.
Hanging nodesIn multi-node workflows, every node has at least one connection (as a dependency or dependant).
Disconnected subgraphsAll nodes belong to a single connected component. No isolated groups.
Error boundary coverageError boundaries protect at least one node, and all protected node IDs exist.
forEach loop structureforEach / endForEach boundaries are valid (pairing, dependencies, and no cross-boundary dependencies).
ADK structureAgent 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.

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": []
  }
}

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 nameCause
ValidationErrorSchema violations, invalid URLs, GET requests with a body, or invalid JSONata syntax.
InvalidNodeIdErrorA node ID contains characters other than alphanumeric and underscores.
MissingDependencyErrorA node lists a dependency or protected node that does not exist in the graph.
CircularDependencyErrorThe dependency graph contains a cycle.
InvalidConditionTargetErrorA continueTo.to value references a node that does not exist.
MissingConditionDependencyErrorA continueTo target node does not include the source node in its dependencies.
HangingNodeErrorA node in a multi-node workflow has no connections to any other node.
DisconnectedSubgraphErrorThe workflow contains two or more groups of nodes with no edges between them.

Next steps