Executing Workflows
Submit a workflow graph for execution, then query its status and results. Graph Compose returns a workflow ID immediately and processes the graph asynchronously.
API endpoints
All endpoints are relative to https://api.graphcompose.io/api/v1.
| Endpoint | Method | Description |
|---|---|---|
/workflows | POST | Execute a workflow asynchronously |
/workflows/execute-sync | POST | Execute and wait for the final result |
/workflows/validate | POST | Validate a workflow graph without executing |
/workflows/{id} | GET | Get status, execution state, and node results |
/workflows/{id}/terminate | POST | Terminate a running workflow |
Each endpoint above links directly to its full schema in the Workflow API Reference.
Execute a workflow
View full schema in API Reference →
Define your nodes, then call graph.execute(). The method returns immediately with a workflow ID and a status of RUNNING.
import { GraphCompose } from '@graph-compose/client'
const graph = new GraphCompose({
token: process.env.GRAPH_COMPOSE_TOKEN
})
graph
.node('get_order')
.get('https://api.example.com/orders/{{ context.orderId }}')
.end()
.node('process_order')
.post('https://api.example.com/orders/process')
.withBody({
items: '{{ results.get_order.data.items }}'
})
.withDependencies(['get_order'])
.end()
const result = await graph.execute({
context: { orderId: 'order_123' }
})
console.log('Workflow ID:', result.data.workflowId)
console.log('Status:', result.data.status) // "RUNNING"
The execute() method accepts an optional context object and an optional webhookUrl. Context values are available to all nodes via {{ context.key }} template expressions. See Webhooks for completion notifications.
Get workflow status and results
View full schema in API Reference →
GET /workflows/{id} returns the complete workflow state in a single response: metadata, status, and the full execution results for every node that has completed.
There is no separate "get state" endpoint. This one call gives you everything.
const workflow = await graph.getWorkflowStatus('wf_abc123')
if (workflow.success && workflow.data) {
console.log('Status:', workflow.data.status)
// executionState contains context, executed node list, and results
const state = workflow.data.executionState
console.log('Executed nodes:', state.executed)
console.log('Order data:', state.results.get_order.data)
}
Understand the response
View response schemas in API Reference →
Every successful response follows this envelope:
Success envelope
{
"success": true,
"message": "Human-readable status message.",
"data": { ... }
}
Error responses use a different shape. They never appear inside a 200 response.
Error envelope (4xx / 5xx)
{
"success": false,
"error": {
"code": "NOT_FOUND",
"message": "Workflow not found."
}
}
Workflow data fields
The data object returned by GET /workflows/{id} contains:
| Field | Type | Description |
|---|---|---|
workflowId | string | Unique identifier for the workflow. |
runId | string | Identifier for the specific execution run. |
startedAt | string | ISO 8601 timestamp when execution began. |
userId | string | ID of the user who started the workflow. |
organizationId | string | ID of the organization that owns the workflow. |
status | string | Current execution status. See status values. |
executionState | object | Context, executed node list, and per-node results. See below. |
events | array | Temporal execution history. Only included when ?includeEvents=true. |
description | object | Temporal workflow metadata. Only included when ?includeEvents=true. |
Execution state fields
The executionState object is the core of every workflow response.
| Field | Type | Description |
|---|---|---|
context | Record<string, unknown> | The input context you passed at execution time, merged with any updates made during the run. |
executed | string[] | Ordered list of node IDs that have completed. |
results | Record<string, NodeResult> | Map of node ID to its result. See node result structure. |
executionState may be undefined for workflows that were terminated or timed out before state could be captured.
Node result structure
View full schema in API Reference →
Each entry in executionState.results has this shape:
| Field | Type | Description |
|---|---|---|
data | object | string | null | The parsed HTTP response body. JSON responses are parsed into objects. Text responses are returned as strings. Empty bodies are null. |
statusCode | number | HTTP status code returned by the endpoint (e.g. 200, 404). |
headers | Record<string, string> | Response headers as key-value pairs. |
Example for a single node:
Node result example
{
"get_order": {
"data": {
"id": "order_123",
"items": ["item_a", "item_b"],
"total": 59.99
},
"statusCode": 200,
"headers": {
"content-type": "application/json"
}
}
}
Reference these values in downstream nodes with template expressions:
{{ results.get_order.data.total }} → 59.99
{{ results.get_order.statusCode }} → 200
{{ results.get_order.data.items[0] }} → "item_a"
See Template Syntax for the full expression reference.
Workflow status values
| Status | Description |
|---|---|
RUNNING | The workflow is currently executing. |
COMPLETED | All nodes finished successfully. |
FAILED | One or more nodes failed and the workflow could not recover. |
CANCELLED | The workflow was cancelled before completion. |
TERMINATED | The workflow was terminated via the API. |
CONTINUED_AS_NEW | The workflow restarted with a new run. Used internally by long-running workflows. |
TIMED_OUT | The workflow exceeded its maximum allowed duration. |
Poll until complete
If you do not use webhooks, poll GET /workflows/{id} until the status is no longer RUNNING.
const workflowId = result.data.workflowId
let status = 'RUNNING'
while (status === 'RUNNING') {
await new Promise(resolve => setTimeout(resolve, 2000))
const check = await graph.getWorkflowStatus(workflowId)
if (check.success && check.data) {
status = check.data.status
}
}
// Fetch final results
const final = await graph.getWorkflowStatus(workflowId)
console.log('Final results:', final.data.executionState.results)
Terminate a workflow
View full schema in API Reference →
Call graph.terminateWorkflow(workflowId) to stop a running workflow. Pass an optional reason string for auditing. Terminated workflows cannot be resumed.
const result = await graph.terminateWorkflow('wf_abc123', {
reason: 'Order cancelled by customer'
})
console.log('Status:', result.data.status) // "TERMINATED"
Webhooks
Pass a webhookUrl when executing a workflow to receive a POST notification when the workflow completes. This removes the need to poll.
const result = await graph.execute({
context: { orderId: 'order_123' },
webhookUrl: 'https://api.example.com/webhooks/workflow-complete'
})
Graph Compose sends a POST request to your webhook URL with the workflow result in the body when execution finishes. Webhook failures are logged but do not affect the workflow outcome.
Synchronous execution
View full schema in API Reference →
POST /workflows/execute-sync blocks until the workflow completes and returns the final result inline. The response body has the same shape as GET /workflows/{id}.
Synchronous execution
curl -X POST https://api.graphcompose.io/api/v1/workflows/execute-sync \
-H "Authorization: Bearer $GRAPH_COMPOSE_TOKEN" \
-H "Content-Type: application/json" \
-d '{
"nodes": [
{
"id": "get_user",
"type": "http",
"http": {
"method": "GET",
"url": "https://api.example.com/users/{{ context.userId }}"
}
}
],
"context": { "userId": "user_123" }
}'
Use synchronous execution only for short workflows. Long-running workflows may exceed HTTP timeout limits. For most use cases, asynchronous execution with polling or webhooks is the better choice.