Workflows
A workflow is the graph of nodes an agent runs on every turn. Building one takes three calls with a dashboard JWT: createWorkflow (name only), updateWorkflow (the nodes and edges, sent as variables) and publishWorkflow. Then attach it to an agent with updateAgent. Node type values inside the graph are lowercase (start, llm, response); the NodeType enum in this API is the same list in capitals. Every node type and its fields are documented under Workflows, Node Types. Every publish that changes the graph is kept as a version you can list, compare and roll back to. With an API key you can also run a published workflow without a chat (runWorkflowTask) and read the outcome (workflowTaskOutcome).
Every operation is a POST of { "query", "variables" } to https://api.wetel.dev/graphql (sandbox) with the x-huat-platform: customer header and the credential shown. New here? Start with the Quickstart and the Authentication cheat sheet. Errors are listed in Errors; Postman and OpenAPI files are on Downloads.
| Operation | Kind | Credential | What it does |
|---|---|---|---|
createWorkflow | mutation | Dashboard JWT | Create an empty workflow (name and description only). |
updateWorkflow | mutation | Dashboard JWT | Save the workflow’s draft graph (nodes and edges) and optionally its name. |
workflowDiagnostics | query | Dashboard JWT | Lint a workflow graph. Lists findings that will probably misbehave at run time; never blocks. |
publishWorkflow | mutation | Dashboard JWT | Publish the current draft. New turns of every session on the agent run this version. |
workflowVersions | query | Dashboard JWT | List a workflow’s published versions, newest first (summaries only, no graph). |
workflowVersionDiff | query | Dashboard JWT | What changed between two versions, or between a version and the current draft. Ids and key names only, never values. |
rollbackWorkflow | mutation | Dashboard JWT | Publish an earlier version’s graph again, as a new version. The draft is not changed. |
restoreWorkflowVersionToDraft | mutation | Dashboard JWT | Copy a version’s graph into the draft, replacing it. Nothing is published. |
nodeConfigSchemas | query | API key | The JSON Schema of every node type’s config, for building or validating graphs in your own tools. |
workflowRuns | query | Dashboard JWT | List the runs of one session, newest first, to debug what a workflow did. |
workflowRun | query | Dashboard JWT | Read one run’s node trace and context, to see exactly which nodes ran, in order, and why. |
pendingAwaitReplies | query | Dashboard JWT | List the conversations waiting at an await_reply node for the user’s answer, newest first. |
runWorkflowTask | mutation | API key | Run an agent’s published workflow headlessly (no chat message). Asynchronous; returns once accepted. |
workflowTaskOutcome | query | API key | Read the outcome of a task started with runWorkflowTask, with the same API key. |
createWorkflow
Section titled “createWorkflow”Create an empty workflow (name and description only).
- Kind: mutation
- Credential: Dashboard JWT:
Authorization: Bearer <jwt>, fromlogin(a person or a one-off script) - Available in: sandbox and production
CreateWorkflowInputhas nonodesoredges: add the graph withupdateWorkflow.
Request
mutation CreateWorkflow($input: CreateWorkflowInput!) { createWorkflow(input: $input) { id name version isPublished }}{ "input": { "name": "Ticket routing", "description": "Classifies a ticket as L1, L2 or L3 and tells the user where it goes." }}Input fields
CreateWorkflowInput
| Field | Type | Required | Description |
|---|---|---|---|
description | String | no | Optional free-text description (at most 500 characters). |
name | String! | yes | Display name (at most 120 characters). |
Code samples
#!/usr/bin/env bash# createWorkflow: Create an empty workflow (name and description only).# Credential: Dashboard JWT (WETEL_JWT).API_URL="${WETEL_API_URL:-https://api.wetel.dev/graphql}"
curl -sS "$API_URL" \ -H "Content-Type: application/json" \ -H "x-huat-platform: customer" \ -H "Authorization: Bearer $WETEL_JWT" \ --data-binary @- <<'JSON'{ "query": "mutation CreateWorkflow($input:CreateWorkflowInput!){createWorkflow(input:$input){id name version isPublished}}", "variables": { "input": { "name": "Ticket routing", "description": "Classifies a ticket as L1, L2 or L3 and tells the user where it goes." } }}JSON// createWorkflow: Create an empty workflow (name and description only).// Credential: Dashboard JWT (WETEL_JWT). Node 18+ (built-in fetch).const API_URL = process.env.WETEL_API_URL ?? "https://api.wetel.dev/graphql";
const query = `mutation CreateWorkflow($input: CreateWorkflowInput!) { createWorkflow(input: $input) { id name version isPublished }}`;
const variables = { input: { name: "Ticket routing", description: "Classifies a ticket as L1, L2 or L3 and tells the user where it goes.", },};
type GraphQLResponse = { data?: Record<string, unknown>; errors?: { message: string; extensions?: { code?: string } }[];};
async function main(): Promise<void> { const res = await fetch(API_URL, { method: "POST", headers: { "Content-Type": "application/json", "x-huat-platform": "customer", Authorization: `Bearer ${process.env.WETEL_JWT ?? ""}`, }, body: JSON.stringify({ query, variables }), }); const body = (await res.json()) as GraphQLResponse; if (body.errors?.length) { const error = body.errors[0]; throw new Error(`${error.extensions?.code}: ${error.message}`); } console.log(JSON.stringify(body.data?.createWorkflow, null, 2));}
main().catch(err => { console.error(err); process.exit(1);});
export {};<?php// createWorkflow: Create an empty workflow (name and description only).// Credential: Dashboard JWT (WETEL_JWT). PHP 7.3+ with the curl extension.$apiUrl = getenv('WETEL_API_URL') ?: 'https://api.wetel.dev/graphql';
$query = <<<'GRAPHQL'mutation CreateWorkflow($input: CreateWorkflowInput!) { createWorkflow(input: $input) { id name version isPublished }}GRAPHQL;
$variables = json_decode(<<<'JSON'{ "input": { "name": "Ticket routing", "description": "Classifies a ticket as L1, L2 or L3 and tells the user where it goes." }}JSON);
$ch = curl_init($apiUrl);curl_setopt_array($ch, [ CURLOPT_POST => true, CURLOPT_RETURNTRANSFER => true, CURLOPT_HTTPHEADER => [ 'Content-Type: application/json', 'x-huat-platform: customer', 'Authorization: Bearer ' . getenv('WETEL_JWT'), ], CURLOPT_POSTFIELDS => json_encode(['query' => $query, 'variables' => $variables]),]);$body = json_decode(curl_exec($ch), true);curl_close($ch);
if (!empty($body['errors'])) { $error = $body['errors'][0]; fwrite(STDERR, ($error['extensions']['code'] ?? 'ERROR') . ': ' . $error['message'] . PHP_EOL); exit(1);}echo json_encode($body['data']['createWorkflow'], JSON_PRETTY_PRINT), PHP_EOL;// createWorkflow: Create an empty workflow (name and description only).// Credential: Dashboard JWT (WETEL_JWT). .NET 8: put this in Program.cs of a console app and run "dotnet run".using System.Net.Http.Headers;using System.Net.Http.Json;using System.Text.Json;using System.Text.Json.Nodes;
var apiUrl = Environment.GetEnvironmentVariable("WETEL_API_URL") ?? "https://api.wetel.dev/graphql";
const string query = """ mutation CreateWorkflow($input: CreateWorkflowInput!) { createWorkflow(input: $input) { id name version isPublished } } """;
var variables = JsonNode.Parse(""" { "input": { "name": "Ticket routing", "description": "Classifies a ticket as L1, L2 or L3 and tells the user where it goes." } } """);
using var http = new HttpClient();using var request = new HttpRequestMessage(HttpMethod.Post, apiUrl){ Content = JsonContent.Create(new { query, variables }),};request.Headers.Add("x-huat-platform", "customer");request.Headers.Authorization = new AuthenticationHeaderValue("Bearer", Environment.GetEnvironmentVariable("WETEL_JWT"));
using var response = await http.SendAsync(request);var body = JsonNode.Parse(await response.Content.ReadAsStringAsync())!;if (body["errors"] is JsonArray errors && errors.Count > 0){ Console.Error.WriteLine($"{errors[0]?["extensions"]?["code"]}: {errors[0]?["message"]}"); return 1;}Console.WriteLine(body["data"]?["createWorkflow"]?.ToJsonString(new JsonSerializerOptions { WriteIndented = true }));return 0;# createWorkflow: Create an empty workflow (name and description only).# Credential: Dashboard JWT (WETEL_JWT). Python 3.8+ with "requests".import jsonimport os
import requests
API_URL = os.environ.get("WETEL_API_URL", "https://api.wetel.dev/graphql")
QUERY = """mutation CreateWorkflow($input: CreateWorkflowInput!) { createWorkflow(input: $input) { id name version isPublished }}"""
VARIABLES = { "input": { "name": "Ticket routing", "description": "Classifies a ticket as L1, L2 or L3 and tells the user where it goes.", },}
response = requests.post( API_URL, json={"query": QUERY, "variables": VARIABLES}, headers={ "x-huat-platform": "customer", "Authorization": "Bearer " + os.environ["WETEL_JWT"], }, timeout=30,)body = response.json()if body.get("errors"): error = body["errors"][0] raise SystemExit(f"{error.get('extensions', {}).get('code')}: {error['message']}")print(json.dumps(body["data"]["createWorkflow"], indent=2))Responses
Success (HTTP 200)
{ "data": { "createWorkflow": { "id": 12, "name": "Ticket routing", "version": 1, "isPublished": false } }}UNAUTHENTICATED: Called with an API key instead of a JWT (HTTP 200)
{ "errors": [ { "message": "Unauthorized", "locations": [ { "line": 2, "column": 3 } ], "path": [ "createWorkflow" ], "extensions": { "code": "UNAUTHENTICATED", "originalError": { "message": "Unauthorized", "statusCode": 401 } } } ], "data": null}updateWorkflow
Section titled “updateWorkflow”Save the workflow’s draft graph (nodes and edges) and optionally its name.
- Kind: mutation
- Credential: Dashboard JWT:
Authorization: Bearer <jwt>, fromlogin(a person or a one-off script) - Available in: sandbox and production
nodesandedgesare JSON: always send them through variables, never inline in the query.- The draft is what you edit; sessions keep running the last published version until you call
publishWorkflow. - A node config the platform cannot accept (a missing field, an out-of-range number, an unknown value) is refused with
BAD_USER_INPUTand a message naming the node and the field. A draft may still be structurally incomplete;publishWorkflowchecks the whole graph. input.settings.failureResponsesets plain text (1 to 500 characters) sent instead of the standard error reply when a run fails;nullclears it. Effective on save, not on publish.
Request
mutation SaveGraph($input: UpdateWorkflowInput!) { updateWorkflow(input: $input) { id version isPublished updatedAt }}{ "input": { "id": 12, "nodes": [ { "id": "start", "type": "start", "label": "Start", "config": {}, "position": { "x": 0, "y": 0 } }, { "id": "classify", "type": "llm", "label": "Classify level", "config": { "promptTemplate": "You route IT support tickets. Read the ticket and answer with one word: L1 for password resets, account unlocks and how-to questions; L2 for a broken device, application or connection that needs a technician; L3 for an outage, a security incident or anything affecting many people.\n\nTicket: \"{{userMessage}}\"", "outputVar": "level", "matchAnyOf": [ "L1", "L2", "L3" ], "streamOutput": false }, "position": { "x": 0, "y": 100 } }, { "id": "reply", "type": "response", "label": "Tell the user", "config": { "messageTemplate": "Thanks. Your ticket goes to the {{level}} queue; we will reply here." }, "position": { "x": 0, "y": 200 } } ], "edges": [ { "id": "e1", "source": "start", "target": "classify" }, { "id": "e2", "source": "classify", "target": "reply" } ] }}Input fields
UpdateWorkflowInput
| Field | Type | Required | Description |
|---|---|---|---|
description | String | no | New description (at most 500 characters). |
edges | JSON | no | The complete edge list of the draft, replacing the stored one: a JSON array (send it through variables) of {id, source, target, label?}, at most 400 edges. source and target are node ids; label selects the branch for routing nodes (for example “true” and “false” after a condition node). |
id | Int! | yes | Integer id of the workflow to change. |
name | String | no | New display name (at most 120 characters). |
nodes | JSON | no | The complete node list of the draft, replacing the stored one: a JSON array (send it through variables) of {id, type, label, config, position: {x, y}}, at most 200 nodes. type is a NodeType value in lower case (for example “llm”); config follows that type’s schema from nodeConfigSchemas and is validated on save. |
settings | WorkflowSettingsInput | no | Workflow-level settings to change, merged into the stored ones (an omitted field inside keeps its value; omitting settings, or sending null, changes nothing). Effective on save, not on publish. |
WorkflowSettingsInput
| Field | Type | Required | Description |
|---|---|---|---|
failureResponse | String | no | Plain text (1 to 500 characters after trimming) sent instead of the standard error reply when a run fails. Refused (BAD_USER_INPUT) when it contains {{ }} templates, HTML tags, control characters or anything shaped like a credential or signed URL. null clears it (back to the standard reply); omit it to keep the stored value. |
notifyOnFailure | WorkflowFailureNotify | no | OWNERS turns on the in-app failure notice to the workspace owners; NONE (or null) turns it off. Omit it to keep the stored value. Effective on save, no publish needed. |
Allowed values
WorkflowFailureNotify:NONE,OWNERS
Code samples
#!/usr/bin/env bash# updateWorkflow: Save the workflow's draft graph (nodes and edges) and optionally its name.# Credential: Dashboard JWT (WETEL_JWT).API_URL="${WETEL_API_URL:-https://api.wetel.dev/graphql}"
curl -sS "$API_URL" \ -H "Content-Type: application/json" \ -H "x-huat-platform: customer" \ -H "Authorization: Bearer $WETEL_JWT" \ --data-binary @- <<'JSON'{ "query": "mutation SaveGraph($input:UpdateWorkflowInput!){updateWorkflow(input:$input){id version isPublished updatedAt}}", "variables": { "input": { "id": 12, "nodes": [ { "id": "start", "type": "start", "label": "Start", "config": {}, "position": { "x": 0, "y": 0 } }, { "id": "classify", "type": "llm", "label": "Classify level", "config": { "promptTemplate": "You route IT support tickets. Read the ticket and answer with one word: L1 for password resets, account unlocks and how-to questions; L2 for a broken device, application or connection that needs a technician; L3 for an outage, a security incident or anything affecting many people.\n\nTicket: \"{{userMessage}}\"", "outputVar": "level", "matchAnyOf": [ "L1", "L2", "L3" ], "streamOutput": false }, "position": { "x": 0, "y": 100 } }, { "id": "reply", "type": "response", "label": "Tell the user", "config": { "messageTemplate": "Thanks. Your ticket goes to the {{level}} queue; we will reply here." }, "position": { "x": 0, "y": 200 } } ], "edges": [ { "id": "e1", "source": "start", "target": "classify" }, { "id": "e2", "source": "classify", "target": "reply" } ] } }}JSON// updateWorkflow: Save the workflow's draft graph (nodes and edges) and optionally its name.// Credential: Dashboard JWT (WETEL_JWT). Node 18+ (built-in fetch).const API_URL = process.env.WETEL_API_URL ?? "https://api.wetel.dev/graphql";
const query = `mutation SaveGraph($input: UpdateWorkflowInput!) { updateWorkflow(input: $input) { id version isPublished updatedAt }}`;
const variables = { input: { id: 12, nodes: [ { id: "start", type: "start", label: "Start", config: {}, position: { x: 0, y: 0, }, }, { id: "classify", type: "llm", label: "Classify level", config: { promptTemplate: 'You route IT support tickets. Read the ticket and answer with one word: L1 for password resets, account unlocks and how-to questions; L2 for a broken device, application or connection that needs a technician; L3 for an outage, a security incident or anything affecting many people.\n\nTicket: "{{userMessage}}"', outputVar: "level", matchAnyOf: ["L1", "L2", "L3"], streamOutput: false, }, position: { x: 0, y: 100, }, }, { id: "reply", type: "response", label: "Tell the user", config: { messageTemplate: "Thanks. Your ticket goes to the {{level}} queue; we will reply here.", }, position: { x: 0, y: 200, }, }, ], edges: [ { id: "e1", source: "start", target: "classify", }, { id: "e2", source: "classify", target: "reply", }, ], },};
type GraphQLResponse = { data?: Record<string, unknown>; errors?: { message: string; extensions?: { code?: string } }[];};
async function main(): Promise<void> { const res = await fetch(API_URL, { method: "POST", headers: { "Content-Type": "application/json", "x-huat-platform": "customer", Authorization: `Bearer ${process.env.WETEL_JWT ?? ""}`, }, body: JSON.stringify({ query, variables }), }); const body = (await res.json()) as GraphQLResponse; if (body.errors?.length) { const error = body.errors[0]; throw new Error(`${error.extensions?.code}: ${error.message}`); } console.log(JSON.stringify(body.data?.updateWorkflow, null, 2));}
main().catch(err => { console.error(err); process.exit(1);});
export {};<?php// updateWorkflow: Save the workflow's draft graph (nodes and edges) and optionally its name.// Credential: Dashboard JWT (WETEL_JWT). PHP 7.3+ with the curl extension.$apiUrl = getenv('WETEL_API_URL') ?: 'https://api.wetel.dev/graphql';
$query = <<<'GRAPHQL'mutation SaveGraph($input: UpdateWorkflowInput!) { updateWorkflow(input: $input) { id version isPublished updatedAt }}GRAPHQL;
$variables = json_decode(<<<'JSON'{ "input": { "id": 12, "nodes": [ { "id": "start", "type": "start", "label": "Start", "config": {}, "position": { "x": 0, "y": 0 } }, { "id": "classify", "type": "llm", "label": "Classify level", "config": { "promptTemplate": "You route IT support tickets. Read the ticket and answer with one word: L1 for password resets, account unlocks and how-to questions; L2 for a broken device, application or connection that needs a technician; L3 for an outage, a security incident or anything affecting many people.\n\nTicket: \"{{userMessage}}\"", "outputVar": "level", "matchAnyOf": [ "L1", "L2", "L3" ], "streamOutput": false }, "position": { "x": 0, "y": 100 } }, { "id": "reply", "type": "response", "label": "Tell the user", "config": { "messageTemplate": "Thanks. Your ticket goes to the {{level}} queue; we will reply here." }, "position": { "x": 0, "y": 200 } } ], "edges": [ { "id": "e1", "source": "start", "target": "classify" }, { "id": "e2", "source": "classify", "target": "reply" } ] }}JSON);
$ch = curl_init($apiUrl);curl_setopt_array($ch, [ CURLOPT_POST => true, CURLOPT_RETURNTRANSFER => true, CURLOPT_HTTPHEADER => [ 'Content-Type: application/json', 'x-huat-platform: customer', 'Authorization: Bearer ' . getenv('WETEL_JWT'), ], CURLOPT_POSTFIELDS => json_encode(['query' => $query, 'variables' => $variables]),]);$body = json_decode(curl_exec($ch), true);curl_close($ch);
if (!empty($body['errors'])) { $error = $body['errors'][0]; fwrite(STDERR, ($error['extensions']['code'] ?? 'ERROR') . ': ' . $error['message'] . PHP_EOL); exit(1);}echo json_encode($body['data']['updateWorkflow'], JSON_PRETTY_PRINT), PHP_EOL;// updateWorkflow: Save the workflow's draft graph (nodes and edges) and optionally its name.// Credential: Dashboard JWT (WETEL_JWT). .NET 8: put this in Program.cs of a console app and run "dotnet run".using System.Net.Http.Headers;using System.Net.Http.Json;using System.Text.Json;using System.Text.Json.Nodes;
var apiUrl = Environment.GetEnvironmentVariable("WETEL_API_URL") ?? "https://api.wetel.dev/graphql";
const string query = """ mutation SaveGraph($input: UpdateWorkflowInput!) { updateWorkflow(input: $input) { id version isPublished updatedAt } } """;
var variables = JsonNode.Parse(""" { "input": { "id": 12, "nodes": [ { "id": "start", "type": "start", "label": "Start", "config": {}, "position": { "x": 0, "y": 0 } }, { "id": "classify", "type": "llm", "label": "Classify level", "config": { "promptTemplate": "You route IT support tickets. Read the ticket and answer with one word: L1 for password resets, account unlocks and how-to questions; L2 for a broken device, application or connection that needs a technician; L3 for an outage, a security incident or anything affecting many people.\n\nTicket: \"{{userMessage}}\"", "outputVar": "level", "matchAnyOf": [ "L1", "L2", "L3" ], "streamOutput": false }, "position": { "x": 0, "y": 100 } }, { "id": "reply", "type": "response", "label": "Tell the user", "config": { "messageTemplate": "Thanks. Your ticket goes to the {{level}} queue; we will reply here." }, "position": { "x": 0, "y": 200 } } ], "edges": [ { "id": "e1", "source": "start", "target": "classify" }, { "id": "e2", "source": "classify", "target": "reply" } ] } } """);
using var http = new HttpClient();using var request = new HttpRequestMessage(HttpMethod.Post, apiUrl){ Content = JsonContent.Create(new { query, variables }),};request.Headers.Add("x-huat-platform", "customer");request.Headers.Authorization = new AuthenticationHeaderValue("Bearer", Environment.GetEnvironmentVariable("WETEL_JWT"));
using var response = await http.SendAsync(request);var body = JsonNode.Parse(await response.Content.ReadAsStringAsync())!;if (body["errors"] is JsonArray errors && errors.Count > 0){ Console.Error.WriteLine($"{errors[0]?["extensions"]?["code"]}: {errors[0]?["message"]}"); return 1;}Console.WriteLine(body["data"]?["updateWorkflow"]?.ToJsonString(new JsonSerializerOptions { WriteIndented = true }));return 0;# updateWorkflow: Save the workflow's draft graph (nodes and edges) and optionally its name.# Credential: Dashboard JWT (WETEL_JWT). Python 3.8+ with "requests".import jsonimport os
import requests
API_URL = os.environ.get("WETEL_API_URL", "https://api.wetel.dev/graphql")
QUERY = """mutation SaveGraph($input: UpdateWorkflowInput!) { updateWorkflow(input: $input) { id version isPublished updatedAt }}"""
VARIABLES = { "input": { "id": 12, "nodes": [ { "id": "start", "type": "start", "label": "Start", "config": {}, "position": { "x": 0, "y": 0, }, }, { "id": "classify", "type": "llm", "label": "Classify level", "config": { "promptTemplate": "You route IT support tickets. Read the ticket and answer with one word: L1 for password resets, account unlocks and how-to questions; L2 for a broken device, application or connection that needs a technician; L3 for an outage, a security incident or anything affecting many people.\n\nTicket: \"{{userMessage}}\"", "outputVar": "level", "matchAnyOf": [ "L1", "L2", "L3", ], "streamOutput": False, }, "position": { "x": 0, "y": 100, }, }, { "id": "reply", "type": "response", "label": "Tell the user", "config": { "messageTemplate": "Thanks. Your ticket goes to the {{level}} queue; we will reply here.", }, "position": { "x": 0, "y": 200, }, }, ], "edges": [ { "id": "e1", "source": "start", "target": "classify", }, { "id": "e2", "source": "classify", "target": "reply", }, ], },}
response = requests.post( API_URL, json={"query": QUERY, "variables": VARIABLES}, headers={ "x-huat-platform": "customer", "Authorization": "Bearer " + os.environ["WETEL_JWT"], }, timeout=30,)body = response.json()if body.get("errors"): error = body["errors"][0] raise SystemExit(f"{error.get('extensions', {}).get('code')}: {error['message']}")print(json.dumps(body["data"]["updateWorkflow"], indent=2))Responses
Success (HTTP 200)
{ "data": { "updateWorkflow": { "id": 12, "version": 1, "isPublished": false, "updatedAt": "2026-10-12T04:05:41.020Z" } }}NOT_FOUND: Workflow not in your workspace (HTTP 200)
{ "errors": [ { "message": "Workflow 12 not found", "locations": [ { "line": 2, "column": 3 } ], "path": [ "updateWorkflow" ], "extensions": { "code": "NOT_FOUND", "status": 404, "originalError": { "message": "Workflow 12 not found", "error": "Not Found", "statusCode": 404 } } } ], "data": null}BAD_USER_INPUT: name sent as a number (HTTP 400)
Sent with variables:
{ "input": { "id": 12, "name": 42 }}{ "errors": [ { "message": "Variable \"$input\" got invalid value 42 at \"input.name\"; String cannot represent a non string value: 42", "locations": [ { "line": 1, "column": 20 } ], "extensions": { "code": "BAD_USER_INPUT" } } ]}workflowDiagnostics
Section titled “workflowDiagnostics”Lint a workflow graph. Lists findings that will probably misbehave at run time; never blocks.
- Kind: query
- Credential: Dashboard JWT:
Authorization: Bearer <jwt>, fromlogin(a person or a one-off script) - Available in: sandbox and production
published: falsechecks the draft (whatupdateWorkflowwrote);published: truechecks the version sessions run.- Switch on
code, not onmessage. An empty list means nothing was found. Publishing has its own blocking checks.
Request
query Lint($id: Int!, $published: Boolean!) { workflowDiagnostics(id: $id, published: $published) { code severity nodeId edgeId message }}{ "id": 12, "published": false}Allowed values
WorkflowDiagnosticSeverity:INFO,WARNING
Code samples
#!/usr/bin/env bash# workflowDiagnostics: Lint a workflow graph. Lists findings that will probably misbehave at run time; never blocks.# Credential: Dashboard JWT (WETEL_JWT).API_URL="${WETEL_API_URL:-https://api.wetel.dev/graphql}"
curl -sS "$API_URL" \ -H "Content-Type: application/json" \ -H "x-huat-platform: customer" \ -H "Authorization: Bearer $WETEL_JWT" \ --data-binary @- <<'JSON'{ "query": "query Lint($id:Int!$published:Boolean!){workflowDiagnostics(id:$id published:$published){code severity nodeId edgeId message}}", "variables": { "id": 12, "published": false }}JSON// workflowDiagnostics: Lint a workflow graph. Lists findings that will probably misbehave at run time; never blocks.// Credential: Dashboard JWT (WETEL_JWT). Node 18+ (built-in fetch).const API_URL = process.env.WETEL_API_URL ?? "https://api.wetel.dev/graphql";
const query = `query Lint($id: Int!, $published: Boolean!) { workflowDiagnostics(id: $id, published: $published) { code severity nodeId edgeId message }}`;
const variables = { id: 12, published: false,};
type GraphQLResponse = { data?: Record<string, unknown>; errors?: { message: string; extensions?: { code?: string } }[];};
async function main(): Promise<void> { const res = await fetch(API_URL, { method: "POST", headers: { "Content-Type": "application/json", "x-huat-platform": "customer", Authorization: `Bearer ${process.env.WETEL_JWT ?? ""}`, }, body: JSON.stringify({ query, variables }), }); const body = (await res.json()) as GraphQLResponse; if (body.errors?.length) { const error = body.errors[0]; throw new Error(`${error.extensions?.code}: ${error.message}`); } console.log(JSON.stringify(body.data?.workflowDiagnostics, null, 2));}
main().catch(err => { console.error(err); process.exit(1);});
export {};<?php// workflowDiagnostics: Lint a workflow graph. Lists findings that will probably misbehave at run time; never blocks.// Credential: Dashboard JWT (WETEL_JWT). PHP 7.3+ with the curl extension.$apiUrl = getenv('WETEL_API_URL') ?: 'https://api.wetel.dev/graphql';
$query = <<<'GRAPHQL'query Lint($id: Int!, $published: Boolean!) { workflowDiagnostics(id: $id, published: $published) { code severity nodeId edgeId message }}GRAPHQL;
$variables = json_decode(<<<'JSON'{ "id": 12, "published": false}JSON);
$ch = curl_init($apiUrl);curl_setopt_array($ch, [ CURLOPT_POST => true, CURLOPT_RETURNTRANSFER => true, CURLOPT_HTTPHEADER => [ 'Content-Type: application/json', 'x-huat-platform: customer', 'Authorization: Bearer ' . getenv('WETEL_JWT'), ], CURLOPT_POSTFIELDS => json_encode(['query' => $query, 'variables' => $variables]),]);$body = json_decode(curl_exec($ch), true);curl_close($ch);
if (!empty($body['errors'])) { $error = $body['errors'][0]; fwrite(STDERR, ($error['extensions']['code'] ?? 'ERROR') . ': ' . $error['message'] . PHP_EOL); exit(1);}echo json_encode($body['data']['workflowDiagnostics'], JSON_PRETTY_PRINT), PHP_EOL;// workflowDiagnostics: Lint a workflow graph. Lists findings that will probably misbehave at run time; never blocks.// Credential: Dashboard JWT (WETEL_JWT). .NET 8: put this in Program.cs of a console app and run "dotnet run".using System.Net.Http.Headers;using System.Net.Http.Json;using System.Text.Json;using System.Text.Json.Nodes;
var apiUrl = Environment.GetEnvironmentVariable("WETEL_API_URL") ?? "https://api.wetel.dev/graphql";
const string query = """ query Lint($id: Int!, $published: Boolean!) { workflowDiagnostics(id: $id, published: $published) { code severity nodeId edgeId message } } """;
var variables = JsonNode.Parse(""" { "id": 12, "published": false } """);
using var http = new HttpClient();using var request = new HttpRequestMessage(HttpMethod.Post, apiUrl){ Content = JsonContent.Create(new { query, variables }),};request.Headers.Add("x-huat-platform", "customer");request.Headers.Authorization = new AuthenticationHeaderValue("Bearer", Environment.GetEnvironmentVariable("WETEL_JWT"));
using var response = await http.SendAsync(request);var body = JsonNode.Parse(await response.Content.ReadAsStringAsync())!;if (body["errors"] is JsonArray errors && errors.Count > 0){ Console.Error.WriteLine($"{errors[0]?["extensions"]?["code"]}: {errors[0]?["message"]}"); return 1;}Console.WriteLine(body["data"]?["workflowDiagnostics"]?.ToJsonString(new JsonSerializerOptions { WriteIndented = true }));return 0;# workflowDiagnostics: Lint a workflow graph. Lists findings that will probably misbehave at run time; never blocks.# Credential: Dashboard JWT (WETEL_JWT). Python 3.8+ with "requests".import jsonimport os
import requests
API_URL = os.environ.get("WETEL_API_URL", "https://api.wetel.dev/graphql")
QUERY = """query Lint($id: Int!, $published: Boolean!) { workflowDiagnostics(id: $id, published: $published) { code severity nodeId edgeId message }}"""
VARIABLES = { "id": 12, "published": False,}
response = requests.post( API_URL, json={"query": QUERY, "variables": VARIABLES}, headers={ "x-huat-platform": "customer", "Authorization": "Bearer " + os.environ["WETEL_JWT"], }, timeout=30,)body = response.json()if body.get("errors"): error = body["errors"][0] raise SystemExit(f"{error.get('extensions', {}).get('code')}: {error['message']}")print(json.dumps(body["data"]["workflowDiagnostics"], indent=2))Responses
One finding (HTTP 200)
{ "data": { "workflowDiagnostics": [ { "code": "UNREACHABLE_NODE", "severity": "WARNING", "nodeId": "notes", "edgeId": null, "message": "Node \"notes\" (\"Internal notes\") cannot be reached from the start node, so it never runs, and publish will refuse the workflow. Connect it to the graph or delete it." } ] }}Nothing found (HTTP 200)
{ "data": { "workflowDiagnostics": [] }}NOT_FOUND: Never published, asked for the published version (HTTP 200)
{ "errors": [ { "message": "Workflow 12 has no published version; omit published to check the draft", "locations": [ { "line": 2, "column": 3 } ], "path": [ "workflowDiagnostics" ], "extensions": { "code": "NOT_FOUND", "status": 404, "originalError": { "message": "Workflow 12 has no published version; omit published to check the draft", "error": "Not Found", "statusCode": 404 } } } ], "data": null}publishWorkflow
Section titled “publishWorkflow”Publish the current draft. New turns of every session on the agent run this version.
- Kind: mutation
- Credential: Dashboard JWT:
Authorization: Bearer <jwt>, fromlogin(a person or a one-off script) - Available in: sandbox and production
- Refused when the graph is not runnable, for example with no
startnode or with a node that cannot be reached.
Request
mutation Publish($id: Int!) { publishWorkflow(id: $id) { id version isPublished }}{ "id": 12}Code samples
#!/usr/bin/env bash# publishWorkflow: Publish the current draft. New turns of every session on the agent run this version.# Credential: Dashboard JWT (WETEL_JWT).API_URL="${WETEL_API_URL:-https://api.wetel.dev/graphql}"
curl -sS "$API_URL" \ -H "Content-Type: application/json" \ -H "x-huat-platform: customer" \ -H "Authorization: Bearer $WETEL_JWT" \ --data-binary @- <<'JSON'{ "query": "mutation Publish($id:Int!){publishWorkflow(id:$id){id version isPublished}}", "variables": { "id": 12 }}JSON// publishWorkflow: Publish the current draft. New turns of every session on the agent run this version.// Credential: Dashboard JWT (WETEL_JWT). Node 18+ (built-in fetch).const API_URL = process.env.WETEL_API_URL ?? "https://api.wetel.dev/graphql";
const query = `mutation Publish($id: Int!) { publishWorkflow(id: $id) { id version isPublished }}`;
const variables = { id: 12,};
type GraphQLResponse = { data?: Record<string, unknown>; errors?: { message: string; extensions?: { code?: string } }[];};
async function main(): Promise<void> { const res = await fetch(API_URL, { method: "POST", headers: { "Content-Type": "application/json", "x-huat-platform": "customer", Authorization: `Bearer ${process.env.WETEL_JWT ?? ""}`, }, body: JSON.stringify({ query, variables }), }); const body = (await res.json()) as GraphQLResponse; if (body.errors?.length) { const error = body.errors[0]; throw new Error(`${error.extensions?.code}: ${error.message}`); } console.log(JSON.stringify(body.data?.publishWorkflow, null, 2));}
main().catch(err => { console.error(err); process.exit(1);});
export {};<?php// publishWorkflow: Publish the current draft. New turns of every session on the agent run this version.// Credential: Dashboard JWT (WETEL_JWT). PHP 7.3+ with the curl extension.$apiUrl = getenv('WETEL_API_URL') ?: 'https://api.wetel.dev/graphql';
$query = <<<'GRAPHQL'mutation Publish($id: Int!) { publishWorkflow(id: $id) { id version isPublished }}GRAPHQL;
$variables = json_decode(<<<'JSON'{ "id": 12}JSON);
$ch = curl_init($apiUrl);curl_setopt_array($ch, [ CURLOPT_POST => true, CURLOPT_RETURNTRANSFER => true, CURLOPT_HTTPHEADER => [ 'Content-Type: application/json', 'x-huat-platform: customer', 'Authorization: Bearer ' . getenv('WETEL_JWT'), ], CURLOPT_POSTFIELDS => json_encode(['query' => $query, 'variables' => $variables]),]);$body = json_decode(curl_exec($ch), true);curl_close($ch);
if (!empty($body['errors'])) { $error = $body['errors'][0]; fwrite(STDERR, ($error['extensions']['code'] ?? 'ERROR') . ': ' . $error['message'] . PHP_EOL); exit(1);}echo json_encode($body['data']['publishWorkflow'], JSON_PRETTY_PRINT), PHP_EOL;// publishWorkflow: Publish the current draft. New turns of every session on the agent run this version.// Credential: Dashboard JWT (WETEL_JWT). .NET 8: put this in Program.cs of a console app and run "dotnet run".using System.Net.Http.Headers;using System.Net.Http.Json;using System.Text.Json;using System.Text.Json.Nodes;
var apiUrl = Environment.GetEnvironmentVariable("WETEL_API_URL") ?? "https://api.wetel.dev/graphql";
const string query = """ mutation Publish($id: Int!) { publishWorkflow(id: $id) { id version isPublished } } """;
var variables = JsonNode.Parse(""" { "id": 12 } """);
using var http = new HttpClient();using var request = new HttpRequestMessage(HttpMethod.Post, apiUrl){ Content = JsonContent.Create(new { query, variables }),};request.Headers.Add("x-huat-platform", "customer");request.Headers.Authorization = new AuthenticationHeaderValue("Bearer", Environment.GetEnvironmentVariable("WETEL_JWT"));
using var response = await http.SendAsync(request);var body = JsonNode.Parse(await response.Content.ReadAsStringAsync())!;if (body["errors"] is JsonArray errors && errors.Count > 0){ Console.Error.WriteLine($"{errors[0]?["extensions"]?["code"]}: {errors[0]?["message"]}"); return 1;}Console.WriteLine(body["data"]?["publishWorkflow"]?.ToJsonString(new JsonSerializerOptions { WriteIndented = true }));return 0;# publishWorkflow: Publish the current draft. New turns of every session on the agent run this version.# Credential: Dashboard JWT (WETEL_JWT). Python 3.8+ with "requests".import jsonimport os
import requests
API_URL = os.environ.get("WETEL_API_URL", "https://api.wetel.dev/graphql")
QUERY = """mutation Publish($id: Int!) { publishWorkflow(id: $id) { id version isPublished }}"""
VARIABLES = { "id": 12,}
response = requests.post( API_URL, json={"query": QUERY, "variables": VARIABLES}, headers={ "x-huat-platform": "customer", "Authorization": "Bearer " + os.environ["WETEL_JWT"], }, timeout=30,)body = response.json()if body.get("errors"): error = body["errors"][0] raise SystemExit(f"{error.get('extensions', {}).get('code')}: {error['message']}")print(json.dumps(body["data"]["publishWorkflow"], indent=2))Responses
Success (HTTP 200)
{ "data": { "publishWorkflow": { "id": 12, "version": 2, "isPublished": true } }}BAD_USER_INPUT: Node type written in capitals (HTTP 200)
{ "errors": [ { "message": "node classify: type \"LLM\" must be lowercase: \"llm\"", "locations": [ { "line": 2, "column": 3 } ], "path": [ "publishWorkflow" ], "extensions": { "code": "BAD_USER_INPUT" } } ], "data": null}BAD_REQUEST: No start node (HTTP 200)
{ "errors": [ { "message": "Workflow must have a START node before publishing (node types seen: llm, response; the start type is \"start\", lowercase)", "locations": [ { "line": 2, "column": 3 } ], "path": [ "publishWorkflow" ], "extensions": { "code": "BAD_REQUEST", "originalError": { "message": "Workflow must have a START node before publishing (node types seen: llm, response; the start type is \"start\", lowercase)", "error": "Bad Request", "statusCode": 400 } } } ], "data": null}workflowVersions
Section titled “workflowVersions”List a workflow’s published versions, newest first (summaries only, no graph).
- Kind: query
- Credential: Dashboard JWT:
Authorization: Bearer <jwt>, fromlogin(a person or a one-off script) - Available in: sandbox and production
- One entry for every publish, rollback or template hire that changed the published graph; republishing an unchanged graph adds none.
versionNumber(1, 2, 3 …) is what the other version operations take.idis the valuecurrentVersionIdand a run’sworkflowVersionIdhold.isCurrentmarks the version new turns run now.- Page with
first(default 20, at most 100) andafter(a previous page’spageInfo.endCursor, older versions). The newest 100 versions are kept, so numbers can have gaps.
Request
query History($workflowId: Int!, $first: Int, $after: String) { workflowVersions(workflowId: $workflowId, first: $first, after: $after) { pageInfo { hasNextPage endCursor } edges { node { id versionNumber isCurrent source restoredFromVersionNumber note createdAt } } }}{ "workflowId": 12, "first": 20}Allowed values
WorkflowVersionSource:BACKFILL,HIRE,PUBLISH,ROLLBACK,SEED
Code samples
#!/usr/bin/env bash# workflowVersions: List a workflow's published versions, newest first (summaries only, no graph).# Credential: Dashboard JWT (WETEL_JWT).API_URL="${WETEL_API_URL:-https://api.wetel.dev/graphql}"
curl -sS "$API_URL" \ -H "Content-Type: application/json" \ -H "x-huat-platform: customer" \ -H "Authorization: Bearer $WETEL_JWT" \ --data-binary @- <<'JSON'{ "query": "query History($workflowId:Int!$first:Int$after:String){workflowVersions(workflowId:$workflowId first:$first after:$after){pageInfo{hasNextPage endCursor}edges{node{id versionNumber isCurrent source restoredFromVersionNumber note createdAt}}}}", "variables": { "workflowId": 12, "first": 20 }}JSON// workflowVersions: List a workflow's published versions, newest first (summaries only, no graph).// Credential: Dashboard JWT (WETEL_JWT). Node 18+ (built-in fetch).const API_URL = process.env.WETEL_API_URL ?? "https://api.wetel.dev/graphql";
const query = `query History($workflowId: Int!, $first: Int, $after: String) { workflowVersions(workflowId: $workflowId, first: $first, after: $after) { pageInfo { hasNextPage endCursor } edges { node { id versionNumber isCurrent source restoredFromVersionNumber note createdAt } } }}`;
const variables = { workflowId: 12, first: 20,};
type GraphQLResponse = { data?: Record<string, unknown>; errors?: { message: string; extensions?: { code?: string } }[];};
async function main(): Promise<void> { const res = await fetch(API_URL, { method: "POST", headers: { "Content-Type": "application/json", "x-huat-platform": "customer", Authorization: `Bearer ${process.env.WETEL_JWT ?? ""}`, }, body: JSON.stringify({ query, variables }), }); const body = (await res.json()) as GraphQLResponse; if (body.errors?.length) { const error = body.errors[0]; throw new Error(`${error.extensions?.code}: ${error.message}`); } console.log(JSON.stringify(body.data?.workflowVersions, null, 2));}
main().catch(err => { console.error(err); process.exit(1);});
export {};<?php// workflowVersions: List a workflow's published versions, newest first (summaries only, no graph).// Credential: Dashboard JWT (WETEL_JWT). PHP 7.3+ with the curl extension.$apiUrl = getenv('WETEL_API_URL') ?: 'https://api.wetel.dev/graphql';
$query = <<<'GRAPHQL'query History($workflowId: Int!, $first: Int, $after: String) { workflowVersions(workflowId: $workflowId, first: $first, after: $after) { pageInfo { hasNextPage endCursor } edges { node { id versionNumber isCurrent source restoredFromVersionNumber note createdAt } } }}GRAPHQL;
$variables = json_decode(<<<'JSON'{ "workflowId": 12, "first": 20}JSON);
$ch = curl_init($apiUrl);curl_setopt_array($ch, [ CURLOPT_POST => true, CURLOPT_RETURNTRANSFER => true, CURLOPT_HTTPHEADER => [ 'Content-Type: application/json', 'x-huat-platform: customer', 'Authorization: Bearer ' . getenv('WETEL_JWT'), ], CURLOPT_POSTFIELDS => json_encode(['query' => $query, 'variables' => $variables]),]);$body = json_decode(curl_exec($ch), true);curl_close($ch);
if (!empty($body['errors'])) { $error = $body['errors'][0]; fwrite(STDERR, ($error['extensions']['code'] ?? 'ERROR') . ': ' . $error['message'] . PHP_EOL); exit(1);}echo json_encode($body['data']['workflowVersions'], JSON_PRETTY_PRINT), PHP_EOL;// workflowVersions: List a workflow's published versions, newest first (summaries only, no graph).// Credential: Dashboard JWT (WETEL_JWT). .NET 8: put this in Program.cs of a console app and run "dotnet run".using System.Net.Http.Headers;using System.Net.Http.Json;using System.Text.Json;using System.Text.Json.Nodes;
var apiUrl = Environment.GetEnvironmentVariable("WETEL_API_URL") ?? "https://api.wetel.dev/graphql";
const string query = """ query History($workflowId: Int!, $first: Int, $after: String) { workflowVersions(workflowId: $workflowId, first: $first, after: $after) { pageInfo { hasNextPage endCursor } edges { node { id versionNumber isCurrent source restoredFromVersionNumber note createdAt } } } } """;
var variables = JsonNode.Parse(""" { "workflowId": 12, "first": 20 } """);
using var http = new HttpClient();using var request = new HttpRequestMessage(HttpMethod.Post, apiUrl){ Content = JsonContent.Create(new { query, variables }),};request.Headers.Add("x-huat-platform", "customer");request.Headers.Authorization = new AuthenticationHeaderValue("Bearer", Environment.GetEnvironmentVariable("WETEL_JWT"));
using var response = await http.SendAsync(request);var body = JsonNode.Parse(await response.Content.ReadAsStringAsync())!;if (body["errors"] is JsonArray errors && errors.Count > 0){ Console.Error.WriteLine($"{errors[0]?["extensions"]?["code"]}: {errors[0]?["message"]}"); return 1;}Console.WriteLine(body["data"]?["workflowVersions"]?.ToJsonString(new JsonSerializerOptions { WriteIndented = true }));return 0;# workflowVersions: List a workflow's published versions, newest first (summaries only, no graph).# Credential: Dashboard JWT (WETEL_JWT). Python 3.8+ with "requests".import jsonimport os
import requests
API_URL = os.environ.get("WETEL_API_URL", "https://api.wetel.dev/graphql")
QUERY = """query History($workflowId: Int!, $first: Int, $after: String) { workflowVersions(workflowId: $workflowId, first: $first, after: $after) { pageInfo { hasNextPage endCursor } edges { node { id versionNumber isCurrent source restoredFromVersionNumber note createdAt } } }}"""
VARIABLES = { "workflowId": 12, "first": 20,}
response = requests.post( API_URL, json={"query": QUERY, "variables": VARIABLES}, headers={ "x-huat-platform": "customer", "Authorization": "Bearer " + os.environ["WETEL_JWT"], }, timeout=30,)body = response.json()if body.get("errors"): error = body["errors"][0] raise SystemExit(f"{error.get('extensions', {}).get('code')}: {error['message']}")print(json.dumps(body["data"]["workflowVersions"], indent=2))Responses
Success (HTTP 200)
{ "data": { "workflowVersions": { "pageInfo": { "hasNextPage": false, "endCursor": "djox" }, "edges": [ { "node": { "id": 305, "versionNumber": 2, "isCurrent": true, "source": "PUBLISH", "restoredFromVersionNumber": null, "note": "Stricter classifier prompt", "createdAt": "2026-10-15T08:40:10.000Z" } }, { "node": { "id": 291, "versionNumber": 1, "isCurrent": false, "source": "PUBLISH", "restoredFromVersionNumber": null, "note": null, "createdAt": "2026-10-14T09:02:31.000Z" } } ] } }}NOT_FOUND: Workflow not in your workspace (HTTP 200)
{ "errors": [ { "message": "Workflow 12 not found", "locations": [ { "line": 2, "column": 3 } ], "path": [ "workflowVersions" ], "extensions": { "code": "NOT_FOUND", "status": 404, "originalError": { "message": "Workflow 12 not found", "error": "Not Found", "statusCode": 404 } } } ], "data": null}BAD_REQUEST: Cursor not issued by this query (HTTP 200)
{ "errors": [ { "message": "Invalid `after` cursor", "locations": [ { "line": 2, "column": 3 } ], "path": [ "workflowVersions" ], "extensions": { "code": "BAD_REQUEST", "originalError": { "message": "Invalid `after` cursor", "error": "Bad Request", "statusCode": 400 } } } ], "data": null}workflowVersionDiff
Section titled “workflowVersionDiff”What changed between two versions, or between a version and the current draft. Ids and key names only, never values.
- Kind: query
- Credential: Dashboard JWT:
Authorization: Bearer <jwt>, fromlogin(a person or a one-off script) - Available in: sandbox and production
- Omit
toVersionto compare against the draft. WithfromVersionset to the current version, that is exactly what the nextpublishWorkflowwould change. changedKeysnames top-level keys (label,position,type) and config keys asconfig.<key>. Read both graphs withworkflowVersionto see the values.
Request
query Diff($workflowId: Int!, $fromVersion: Int!, $toVersion: Int) { workflowVersionDiff(workflowId: $workflowId, fromVersion: $fromVersion, toVersion: $toVersion) { addedNodeIds removedNodeIds changedNodes { id type changedKeys } addedEdgeIds removedEdgeIds changedEdgeIds }}{ "workflowId": 12, "fromVersion": 2}Code samples
#!/usr/bin/env bash# workflowVersionDiff: What changed between two versions, or between a version and the current draft. Ids and key names only, never values.# Credential: Dashboard JWT (WETEL_JWT).API_URL="${WETEL_API_URL:-https://api.wetel.dev/graphql}"
curl -sS "$API_URL" \ -H "Content-Type: application/json" \ -H "x-huat-platform: customer" \ -H "Authorization: Bearer $WETEL_JWT" \ --data-binary @- <<'JSON'{ "query": "query Diff($workflowId:Int!$fromVersion:Int!$toVersion:Int){workflowVersionDiff(workflowId:$workflowId fromVersion:$fromVersion toVersion:$toVersion){addedNodeIds removedNodeIds changedNodes{id type changedKeys}addedEdgeIds removedEdgeIds changedEdgeIds}}", "variables": { "workflowId": 12, "fromVersion": 2 }}JSON// workflowVersionDiff: What changed between two versions, or between a version and the current draft. Ids and key names only, never values.// Credential: Dashboard JWT (WETEL_JWT). Node 18+ (built-in fetch).const API_URL = process.env.WETEL_API_URL ?? "https://api.wetel.dev/graphql";
const query = `query Diff($workflowId: Int!, $fromVersion: Int!, $toVersion: Int) { workflowVersionDiff(workflowId: $workflowId, fromVersion: $fromVersion, toVersion: $toVersion) { addedNodeIds removedNodeIds changedNodes { id type changedKeys } addedEdgeIds removedEdgeIds changedEdgeIds }}`;
const variables = { workflowId: 12, fromVersion: 2,};
type GraphQLResponse = { data?: Record<string, unknown>; errors?: { message: string; extensions?: { code?: string } }[];};
async function main(): Promise<void> { const res = await fetch(API_URL, { method: "POST", headers: { "Content-Type": "application/json", "x-huat-platform": "customer", Authorization: `Bearer ${process.env.WETEL_JWT ?? ""}`, }, body: JSON.stringify({ query, variables }), }); const body = (await res.json()) as GraphQLResponse; if (body.errors?.length) { const error = body.errors[0]; throw new Error(`${error.extensions?.code}: ${error.message}`); } console.log(JSON.stringify(body.data?.workflowVersionDiff, null, 2));}
main().catch(err => { console.error(err); process.exit(1);});
export {};<?php// workflowVersionDiff: What changed between two versions, or between a version and the current draft. Ids and key names only, never values.// Credential: Dashboard JWT (WETEL_JWT). PHP 7.3+ with the curl extension.$apiUrl = getenv('WETEL_API_URL') ?: 'https://api.wetel.dev/graphql';
$query = <<<'GRAPHQL'query Diff($workflowId: Int!, $fromVersion: Int!, $toVersion: Int) { workflowVersionDiff(workflowId: $workflowId, fromVersion: $fromVersion, toVersion: $toVersion) { addedNodeIds removedNodeIds changedNodes { id type changedKeys } addedEdgeIds removedEdgeIds changedEdgeIds }}GRAPHQL;
$variables = json_decode(<<<'JSON'{ "workflowId": 12, "fromVersion": 2}JSON);
$ch = curl_init($apiUrl);curl_setopt_array($ch, [ CURLOPT_POST => true, CURLOPT_RETURNTRANSFER => true, CURLOPT_HTTPHEADER => [ 'Content-Type: application/json', 'x-huat-platform: customer', 'Authorization: Bearer ' . getenv('WETEL_JWT'), ], CURLOPT_POSTFIELDS => json_encode(['query' => $query, 'variables' => $variables]),]);$body = json_decode(curl_exec($ch), true);curl_close($ch);
if (!empty($body['errors'])) { $error = $body['errors'][0]; fwrite(STDERR, ($error['extensions']['code'] ?? 'ERROR') . ': ' . $error['message'] . PHP_EOL); exit(1);}echo json_encode($body['data']['workflowVersionDiff'], JSON_PRETTY_PRINT), PHP_EOL;// workflowVersionDiff: What changed between two versions, or between a version and the current draft. Ids and key names only, never values.// Credential: Dashboard JWT (WETEL_JWT). .NET 8: put this in Program.cs of a console app and run "dotnet run".using System.Net.Http.Headers;using System.Net.Http.Json;using System.Text.Json;using System.Text.Json.Nodes;
var apiUrl = Environment.GetEnvironmentVariable("WETEL_API_URL") ?? "https://api.wetel.dev/graphql";
const string query = """ query Diff($workflowId: Int!, $fromVersion: Int!, $toVersion: Int) { workflowVersionDiff(workflowId: $workflowId, fromVersion: $fromVersion, toVersion: $toVersion) { addedNodeIds removedNodeIds changedNodes { id type changedKeys } addedEdgeIds removedEdgeIds changedEdgeIds } } """;
var variables = JsonNode.Parse(""" { "workflowId": 12, "fromVersion": 2 } """);
using var http = new HttpClient();using var request = new HttpRequestMessage(HttpMethod.Post, apiUrl){ Content = JsonContent.Create(new { query, variables }),};request.Headers.Add("x-huat-platform", "customer");request.Headers.Authorization = new AuthenticationHeaderValue("Bearer", Environment.GetEnvironmentVariable("WETEL_JWT"));
using var response = await http.SendAsync(request);var body = JsonNode.Parse(await response.Content.ReadAsStringAsync())!;if (body["errors"] is JsonArray errors && errors.Count > 0){ Console.Error.WriteLine($"{errors[0]?["extensions"]?["code"]}: {errors[0]?["message"]}"); return 1;}Console.WriteLine(body["data"]?["workflowVersionDiff"]?.ToJsonString(new JsonSerializerOptions { WriteIndented = true }));return 0;# workflowVersionDiff: What changed between two versions, or between a version and the current draft. Ids and key names only, never values.# Credential: Dashboard JWT (WETEL_JWT). Python 3.8+ with "requests".import jsonimport os
import requests
API_URL = os.environ.get("WETEL_API_URL", "https://api.wetel.dev/graphql")
QUERY = """query Diff($workflowId: Int!, $fromVersion: Int!, $toVersion: Int) { workflowVersionDiff(workflowId: $workflowId, fromVersion: $fromVersion, toVersion: $toVersion) { addedNodeIds removedNodeIds changedNodes { id type changedKeys } addedEdgeIds removedEdgeIds changedEdgeIds }}"""
VARIABLES = { "workflowId": 12, "fromVersion": 2,}
response = requests.post( API_URL, json={"query": QUERY, "variables": VARIABLES}, headers={ "x-huat-platform": "customer", "Authorization": "Bearer " + os.environ["WETEL_JWT"], }, timeout=30,)body = response.json()if body.get("errors"): error = body["errors"][0] raise SystemExit(f"{error.get('extensions', {}).get('code')}: {error['message']}")print(json.dumps(body["data"]["workflowVersionDiff"], indent=2))Responses
Draft compared with version 2 (HTTP 200)
{ "data": { "workflowVersionDiff": { "addedNodeIds": [], "removedNodeIds": [], "changedNodes": [ { "id": "classify", "type": "llm", "changedKeys": [ "config.promptTemplate" ] } ], "addedEdgeIds": [], "removedEdgeIds": [], "changedEdgeIds": [] } }}NOT_FOUND: Version does not exist or was pruned (HTTP 200)
{ "errors": [ { "message": "Version 2 of workflow 12 not found (it may have been pruned)", "locations": [ { "line": 2, "column": 3 } ], "path": [ "workflowVersionDiff" ], "extensions": { "code": "NOT_FOUND", "status": 404, "originalError": { "message": "Version 2 of workflow 12 not found (it may have been pruned)", "error": "Not Found", "statusCode": 404 } } } ], "data": null}rollbackWorkflow
Section titled “rollbackWorkflow”Publish an earlier version’s graph again, as a new version. The draft is not changed.
- Kind: mutation
- Credential: Dashboard JWT:
Authorization: Bearer <jwt>, fromlogin(a person or a one-off script) - Available in: sandbox and production
- The old graph is checked against today’s rules, as a publish is, and refused with the same errors as
publishWorkflowwhen it no longer passes. - Refused when that graph is already the one published now.
noteis optional, at most 500 characters. - Takes effect for the next session turn; a conversation paused at
await_replyorapprovalkeeps the version it paused on.
Request
mutation Rollback($workflowId: Int!, $versionNumber: Int!, $note: String) { rollbackWorkflow(workflowId: $workflowId, versionNumber: $versionNumber, note: $note) { id currentVersionId isPublished }}{ "workflowId": 12, "versionNumber": 1, "note": "Back to the first prompt while the classifier is fixed"}Code samples
#!/usr/bin/env bash# rollbackWorkflow: Publish an earlier version's graph again, as a new version. The draft is not changed.# Credential: Dashboard JWT (WETEL_JWT).API_URL="${WETEL_API_URL:-https://api.wetel.dev/graphql}"
curl -sS "$API_URL" \ -H "Content-Type: application/json" \ -H "x-huat-platform: customer" \ -H "Authorization: Bearer $WETEL_JWT" \ --data-binary @- <<'JSON'{ "query": "mutation Rollback($workflowId:Int!$versionNumber:Int!$note:String){rollbackWorkflow(workflowId:$workflowId versionNumber:$versionNumber note:$note){id currentVersionId isPublished}}", "variables": { "workflowId": 12, "versionNumber": 1, "note": "Back to the first prompt while the classifier is fixed" }}JSON// rollbackWorkflow: Publish an earlier version's graph again, as a new version. The draft is not changed.// Credential: Dashboard JWT (WETEL_JWT). Node 18+ (built-in fetch).const API_URL = process.env.WETEL_API_URL ?? "https://api.wetel.dev/graphql";
const query = `mutation Rollback($workflowId: Int!, $versionNumber: Int!, $note: String) { rollbackWorkflow(workflowId: $workflowId, versionNumber: $versionNumber, note: $note) { id currentVersionId isPublished }}`;
const variables = { workflowId: 12, versionNumber: 1, note: "Back to the first prompt while the classifier is fixed",};
type GraphQLResponse = { data?: Record<string, unknown>; errors?: { message: string; extensions?: { code?: string } }[];};
async function main(): Promise<void> { const res = await fetch(API_URL, { method: "POST", headers: { "Content-Type": "application/json", "x-huat-platform": "customer", Authorization: `Bearer ${process.env.WETEL_JWT ?? ""}`, }, body: JSON.stringify({ query, variables }), }); const body = (await res.json()) as GraphQLResponse; if (body.errors?.length) { const error = body.errors[0]; throw new Error(`${error.extensions?.code}: ${error.message}`); } console.log(JSON.stringify(body.data?.rollbackWorkflow, null, 2));}
main().catch(err => { console.error(err); process.exit(1);});
export {};<?php// rollbackWorkflow: Publish an earlier version's graph again, as a new version. The draft is not changed.// Credential: Dashboard JWT (WETEL_JWT). PHP 7.3+ with the curl extension.$apiUrl = getenv('WETEL_API_URL') ?: 'https://api.wetel.dev/graphql';
$query = <<<'GRAPHQL'mutation Rollback($workflowId: Int!, $versionNumber: Int!, $note: String) { rollbackWorkflow(workflowId: $workflowId, versionNumber: $versionNumber, note: $note) { id currentVersionId isPublished }}GRAPHQL;
$variables = json_decode(<<<'JSON'{ "workflowId": 12, "versionNumber": 1, "note": "Back to the first prompt while the classifier is fixed"}JSON);
$ch = curl_init($apiUrl);curl_setopt_array($ch, [ CURLOPT_POST => true, CURLOPT_RETURNTRANSFER => true, CURLOPT_HTTPHEADER => [ 'Content-Type: application/json', 'x-huat-platform: customer', 'Authorization: Bearer ' . getenv('WETEL_JWT'), ], CURLOPT_POSTFIELDS => json_encode(['query' => $query, 'variables' => $variables]),]);$body = json_decode(curl_exec($ch), true);curl_close($ch);
if (!empty($body['errors'])) { $error = $body['errors'][0]; fwrite(STDERR, ($error['extensions']['code'] ?? 'ERROR') . ': ' . $error['message'] . PHP_EOL); exit(1);}echo json_encode($body['data']['rollbackWorkflow'], JSON_PRETTY_PRINT), PHP_EOL;// rollbackWorkflow: Publish an earlier version's graph again, as a new version. The draft is not changed.// Credential: Dashboard JWT (WETEL_JWT). .NET 8: put this in Program.cs of a console app and run "dotnet run".using System.Net.Http.Headers;using System.Net.Http.Json;using System.Text.Json;using System.Text.Json.Nodes;
var apiUrl = Environment.GetEnvironmentVariable("WETEL_API_URL") ?? "https://api.wetel.dev/graphql";
const string query = """ mutation Rollback($workflowId: Int!, $versionNumber: Int!, $note: String) { rollbackWorkflow(workflowId: $workflowId, versionNumber: $versionNumber, note: $note) { id currentVersionId isPublished } } """;
var variables = JsonNode.Parse(""" { "workflowId": 12, "versionNumber": 1, "note": "Back to the first prompt while the classifier is fixed" } """);
using var http = new HttpClient();using var request = new HttpRequestMessage(HttpMethod.Post, apiUrl){ Content = JsonContent.Create(new { query, variables }),};request.Headers.Add("x-huat-platform", "customer");request.Headers.Authorization = new AuthenticationHeaderValue("Bearer", Environment.GetEnvironmentVariable("WETEL_JWT"));
using var response = await http.SendAsync(request);var body = JsonNode.Parse(await response.Content.ReadAsStringAsync())!;if (body["errors"] is JsonArray errors && errors.Count > 0){ Console.Error.WriteLine($"{errors[0]?["extensions"]?["code"]}: {errors[0]?["message"]}"); return 1;}Console.WriteLine(body["data"]?["rollbackWorkflow"]?.ToJsonString(new JsonSerializerOptions { WriteIndented = true }));return 0;# rollbackWorkflow: Publish an earlier version's graph again, as a new version. The draft is not changed.# Credential: Dashboard JWT (WETEL_JWT). Python 3.8+ with "requests".import jsonimport os
import requests
API_URL = os.environ.get("WETEL_API_URL", "https://api.wetel.dev/graphql")
QUERY = """mutation Rollback($workflowId: Int!, $versionNumber: Int!, $note: String) { rollbackWorkflow(workflowId: $workflowId, versionNumber: $versionNumber, note: $note) { id currentVersionId isPublished }}"""
VARIABLES = { "workflowId": 12, "versionNumber": 1, "note": "Back to the first prompt while the classifier is fixed",}
response = requests.post( API_URL, json={"query": QUERY, "variables": VARIABLES}, headers={ "x-huat-platform": "customer", "Authorization": "Bearer " + os.environ["WETEL_JWT"], }, timeout=30,)body = response.json()if body.get("errors"): error = body["errors"][0] raise SystemExit(f"{error.get('extensions', {}).get('code')}: {error['message']}")print(json.dumps(body["data"]["rollbackWorkflow"], indent=2))Responses
Success (HTTP 200)
{ "data": { "rollbackWorkflow": { "id": 12, "currentVersionId": 318, "isPublished": true } }}BAD_USER_INPUT: That graph is already published (HTTP 200)
{ "errors": [ { "message": "Version 1 has the same graph as the version published now; nothing to roll back.", "locations": [ { "line": 2, "column": 3 } ], "path": [ "rollbackWorkflow" ], "extensions": { "code": "BAD_USER_INPUT" } } ], "data": null}NOT_FOUND: Version does not exist or was pruned (HTTP 200)
{ "errors": [ { "message": "Version 1 of workflow 12 not found (it may have been pruned)", "locations": [ { "line": 2, "column": 3 } ], "path": [ "rollbackWorkflow" ], "extensions": { "code": "NOT_FOUND", "status": 404, "originalError": { "message": "Version 1 of workflow 12 not found (it may have been pruned)", "error": "Not Found", "statusCode": 404 } } } ], "data": null}restoreWorkflowVersionToDraft
Section titled “restoreWorkflowVersionToDraft”Copy a version’s graph into the draft, replacing it. Nothing is published.
- Kind: mutation
- Credential: Dashboard JWT:
Authorization: Bearer <jwt>, fromlogin(a person or a one-off script) - Available in: sandbox and production
- Unpublished edits in the draft are replaced. Edit the restored draft, then call
publishWorkflow. - The graph is checked like an
updateWorkflowsave, so a node config that no longer passes today’s rules is refused withBAD_USER_INPUT.
Request
mutation RestoreToDraft($workflowId: Int!, $versionNumber: Int!) { restoreWorkflowVersionToDraft(workflowId: $workflowId, versionNumber: $versionNumber) { id version updatedAt }}{ "workflowId": 12, "versionNumber": 1}Code samples
#!/usr/bin/env bash# restoreWorkflowVersionToDraft: Copy a version's graph into the draft, replacing it. Nothing is published.# Credential: Dashboard JWT (WETEL_JWT).API_URL="${WETEL_API_URL:-https://api.wetel.dev/graphql}"
curl -sS "$API_URL" \ -H "Content-Type: application/json" \ -H "x-huat-platform: customer" \ -H "Authorization: Bearer $WETEL_JWT" \ --data-binary @- <<'JSON'{ "query": "mutation RestoreToDraft($workflowId:Int!$versionNumber:Int!){restoreWorkflowVersionToDraft(workflowId:$workflowId versionNumber:$versionNumber){id version updatedAt}}", "variables": { "workflowId": 12, "versionNumber": 1 }}JSON// restoreWorkflowVersionToDraft: Copy a version's graph into the draft, replacing it. Nothing is published.// Credential: Dashboard JWT (WETEL_JWT). Node 18+ (built-in fetch).const API_URL = process.env.WETEL_API_URL ?? "https://api.wetel.dev/graphql";
const query = `mutation RestoreToDraft($workflowId: Int!, $versionNumber: Int!) { restoreWorkflowVersionToDraft(workflowId: $workflowId, versionNumber: $versionNumber) { id version updatedAt }}`;
const variables = { workflowId: 12, versionNumber: 1,};
type GraphQLResponse = { data?: Record<string, unknown>; errors?: { message: string; extensions?: { code?: string } }[];};
async function main(): Promise<void> { const res = await fetch(API_URL, { method: "POST", headers: { "Content-Type": "application/json", "x-huat-platform": "customer", Authorization: `Bearer ${process.env.WETEL_JWT ?? ""}`, }, body: JSON.stringify({ query, variables }), }); const body = (await res.json()) as GraphQLResponse; if (body.errors?.length) { const error = body.errors[0]; throw new Error(`${error.extensions?.code}: ${error.message}`); } console.log( JSON.stringify(body.data?.restoreWorkflowVersionToDraft, null, 2) );}
main().catch(err => { console.error(err); process.exit(1);});
export {};<?php// restoreWorkflowVersionToDraft: Copy a version's graph into the draft, replacing it. Nothing is published.// Credential: Dashboard JWT (WETEL_JWT). PHP 7.3+ with the curl extension.$apiUrl = getenv('WETEL_API_URL') ?: 'https://api.wetel.dev/graphql';
$query = <<<'GRAPHQL'mutation RestoreToDraft($workflowId: Int!, $versionNumber: Int!) { restoreWorkflowVersionToDraft(workflowId: $workflowId, versionNumber: $versionNumber) { id version updatedAt }}GRAPHQL;
$variables = json_decode(<<<'JSON'{ "workflowId": 12, "versionNumber": 1}JSON);
$ch = curl_init($apiUrl);curl_setopt_array($ch, [ CURLOPT_POST => true, CURLOPT_RETURNTRANSFER => true, CURLOPT_HTTPHEADER => [ 'Content-Type: application/json', 'x-huat-platform: customer', 'Authorization: Bearer ' . getenv('WETEL_JWT'), ], CURLOPT_POSTFIELDS => json_encode(['query' => $query, 'variables' => $variables]),]);$body = json_decode(curl_exec($ch), true);curl_close($ch);
if (!empty($body['errors'])) { $error = $body['errors'][0]; fwrite(STDERR, ($error['extensions']['code'] ?? 'ERROR') . ': ' . $error['message'] . PHP_EOL); exit(1);}echo json_encode($body['data']['restoreWorkflowVersionToDraft'], JSON_PRETTY_PRINT), PHP_EOL;// restoreWorkflowVersionToDraft: Copy a version's graph into the draft, replacing it. Nothing is published.// Credential: Dashboard JWT (WETEL_JWT). .NET 8: put this in Program.cs of a console app and run "dotnet run".using System.Net.Http.Headers;using System.Net.Http.Json;using System.Text.Json;using System.Text.Json.Nodes;
var apiUrl = Environment.GetEnvironmentVariable("WETEL_API_URL") ?? "https://api.wetel.dev/graphql";
const string query = """ mutation RestoreToDraft($workflowId: Int!, $versionNumber: Int!) { restoreWorkflowVersionToDraft(workflowId: $workflowId, versionNumber: $versionNumber) { id version updatedAt } } """;
var variables = JsonNode.Parse(""" { "workflowId": 12, "versionNumber": 1 } """);
using var http = new HttpClient();using var request = new HttpRequestMessage(HttpMethod.Post, apiUrl){ Content = JsonContent.Create(new { query, variables }),};request.Headers.Add("x-huat-platform", "customer");request.Headers.Authorization = new AuthenticationHeaderValue("Bearer", Environment.GetEnvironmentVariable("WETEL_JWT"));
using var response = await http.SendAsync(request);var body = JsonNode.Parse(await response.Content.ReadAsStringAsync())!;if (body["errors"] is JsonArray errors && errors.Count > 0){ Console.Error.WriteLine($"{errors[0]?["extensions"]?["code"]}: {errors[0]?["message"]}"); return 1;}Console.WriteLine(body["data"]?["restoreWorkflowVersionToDraft"]?.ToJsonString(new JsonSerializerOptions { WriteIndented = true }));return 0;# restoreWorkflowVersionToDraft: Copy a version's graph into the draft, replacing it. Nothing is published.# Credential: Dashboard JWT (WETEL_JWT). Python 3.8+ with "requests".import jsonimport os
import requests
API_URL = os.environ.get("WETEL_API_URL", "https://api.wetel.dev/graphql")
QUERY = """mutation RestoreToDraft($workflowId: Int!, $versionNumber: Int!) { restoreWorkflowVersionToDraft(workflowId: $workflowId, versionNumber: $versionNumber) { id version updatedAt }}"""
VARIABLES = { "workflowId": 12, "versionNumber": 1,}
response = requests.post( API_URL, json={"query": QUERY, "variables": VARIABLES}, headers={ "x-huat-platform": "customer", "Authorization": "Bearer " + os.environ["WETEL_JWT"], }, timeout=30,)body = response.json()if body.get("errors"): error = body["errors"][0] raise SystemExit(f"{error.get('extensions', {}).get('code')}: {error['message']}")print(json.dumps(body["data"]["restoreWorkflowVersionToDraft"], indent=2))Responses
Success (HTTP 200)
{ "data": { "restoreWorkflowVersionToDraft": { "id": 12, "version": 5, "updatedAt": "2026-10-16T03:20:11.400Z" } }}NOT_FOUND: Version does not exist or was pruned (HTTP 200)
{ "errors": [ { "message": "Version 1 of workflow 12 not found (it may have been pruned)", "locations": [ { "line": 2, "column": 3 } ], "path": [ "restoreWorkflowVersionToDraft" ], "extensions": { "code": "NOT_FOUND", "status": 404, "originalError": { "message": "Version 1 of workflow 12 not found (it may have been pruned)", "error": "Not Found", "statusCode": 404 } } } ], "data": null}BAD_USER_INPUT: versionNumber sent as a string (HTTP 400)
Sent with variables:
{ "workflowId": 12, "versionNumber": "one"}{ "errors": [ { "message": "Variable \"$versionNumber\" got invalid value \"one\"; Int cannot represent non-integer value: \"one\"", "locations": [ { "line": 1, "column": 44 } ], "extensions": { "code": "BAD_USER_INPUT" } } ]}nodeConfigSchemas
Section titled “nodeConfigSchemas”The JSON Schema of every node type’s config, for building or validating graphs in your own tools.
- Kind: query
- Credential: API key:
X-Api-Key: sk_..., kept on your server only - Available in: sandbox and production
- Accepts an API key or a dashboard JWT. The answer is the same for every workspace.
schemais a standard JSON Schema (draft 2020-12). One entry per node type that has a config;STARThas none.
Request
query NodeSchemas { nodeConfigSchemas { type schema }}{}Allowed values
NodeType:ACTION,APPROVAL,AWAIT_REPLY,CONDITION,END_SESSION,GENERATE_ARTIFACT,KNOWLEDGE_SEARCH,LLM,PDF_EXPORT,RESPONSE,ROUTER,SET_VARIABLE,START,SUB_AGENT,SWITCH,TOOL,WEBHOOK
Code samples
#!/usr/bin/env bash# nodeConfigSchemas: The JSON Schema of every node type's config, for building or validating graphs in your own tools.# Credential: API key (WETEL_API_KEY).API_URL="${WETEL_API_URL:-https://api.wetel.dev/graphql}"
curl -sS "$API_URL" \ -H "Content-Type: application/json" \ -H "x-huat-platform: customer" \ -H "X-Api-Key: $WETEL_API_KEY" \ --data-binary @- <<'JSON'{ "query": "query NodeSchemas{nodeConfigSchemas{type schema}}", "variables": {}}JSON// nodeConfigSchemas: The JSON Schema of every node type's config, for building or validating graphs in your own tools.// Credential: API key (WETEL_API_KEY). Node 18+ (built-in fetch).const API_URL = process.env.WETEL_API_URL ?? "https://api.wetel.dev/graphql";
const query = `query NodeSchemas { nodeConfigSchemas { type schema }}`;
const variables = {};
type GraphQLResponse = { data?: Record<string, unknown>; errors?: { message: string; extensions?: { code?: string } }[];};
async function main(): Promise<void> { const res = await fetch(API_URL, { method: "POST", headers: { "Content-Type": "application/json", "x-huat-platform": "customer", "X-Api-Key": process.env.WETEL_API_KEY ?? "", }, body: JSON.stringify({ query, variables }), }); const body = (await res.json()) as GraphQLResponse; if (body.errors?.length) { const error = body.errors[0]; throw new Error(`${error.extensions?.code}: ${error.message}`); } console.log(JSON.stringify(body.data?.nodeConfigSchemas, null, 2));}
main().catch(err => { console.error(err); process.exit(1);});
export {};<?php// nodeConfigSchemas: The JSON Schema of every node type's config, for building or validating graphs in your own tools.// Credential: API key (WETEL_API_KEY). PHP 7.3+ with the curl extension.$apiUrl = getenv('WETEL_API_URL') ?: 'https://api.wetel.dev/graphql';
$query = <<<'GRAPHQL'query NodeSchemas { nodeConfigSchemas { type schema }}GRAPHQL;
$variables = json_decode(<<<'JSON'{}JSON);
$ch = curl_init($apiUrl);curl_setopt_array($ch, [ CURLOPT_POST => true, CURLOPT_RETURNTRANSFER => true, CURLOPT_HTTPHEADER => [ 'Content-Type: application/json', 'x-huat-platform: customer', 'X-Api-Key: ' . getenv('WETEL_API_KEY'), ], CURLOPT_POSTFIELDS => json_encode(['query' => $query, 'variables' => $variables]),]);$body = json_decode(curl_exec($ch), true);curl_close($ch);
if (!empty($body['errors'])) { $error = $body['errors'][0]; fwrite(STDERR, ($error['extensions']['code'] ?? 'ERROR') . ': ' . $error['message'] . PHP_EOL); exit(1);}echo json_encode($body['data']['nodeConfigSchemas'], JSON_PRETTY_PRINT), PHP_EOL;// nodeConfigSchemas: The JSON Schema of every node type's config, for building or validating graphs in your own tools.// Credential: API key (WETEL_API_KEY). .NET 8: put this in Program.cs of a console app and run "dotnet run".using System.Net.Http.Headers;using System.Net.Http.Json;using System.Text.Json;using System.Text.Json.Nodes;
var apiUrl = Environment.GetEnvironmentVariable("WETEL_API_URL") ?? "https://api.wetel.dev/graphql";
const string query = """ query NodeSchemas { nodeConfigSchemas { type schema } } """;
var variables = JsonNode.Parse(""" {} """);
using var http = new HttpClient();using var request = new HttpRequestMessage(HttpMethod.Post, apiUrl){ Content = JsonContent.Create(new { query, variables }),};request.Headers.Add("x-huat-platform", "customer");request.Headers.Add("X-Api-Key", Environment.GetEnvironmentVariable("WETEL_API_KEY"));
using var response = await http.SendAsync(request);var body = JsonNode.Parse(await response.Content.ReadAsStringAsync())!;if (body["errors"] is JsonArray errors && errors.Count > 0){ Console.Error.WriteLine($"{errors[0]?["extensions"]?["code"]}: {errors[0]?["message"]}"); return 1;}Console.WriteLine(body["data"]?["nodeConfigSchemas"]?.ToJsonString(new JsonSerializerOptions { WriteIndented = true }));return 0;# nodeConfigSchemas: The JSON Schema of every node type's config, for building or validating graphs in your own tools.# Credential: API key (WETEL_API_KEY). Python 3.8+ with "requests".import jsonimport os
import requests
API_URL = os.environ.get("WETEL_API_URL", "https://api.wetel.dev/graphql")
QUERY = """query NodeSchemas { nodeConfigSchemas { type schema }}"""
VARIABLES = {}
response = requests.post( API_URL, json={"query": QUERY, "variables": VARIABLES}, headers={ "x-huat-platform": "customer", "X-Api-Key": os.environ["WETEL_API_KEY"], }, timeout=30,)body = response.json()if body.get("errors"): error = body["errors"][0] raise SystemExit(f"{error.get('extensions', {}).get('code')}: {error['message']}")print(json.dumps(body["data"]["nodeConfigSchemas"], indent=2))Responses
Success (one entry of the list shown) (HTTP 200)
{ "data": { "nodeConfigSchemas": [ { "type": "RESPONSE", "schema": { "$schema": "https://json-schema.org/draft/2020-12/schema", "type": "object", "properties": { "maxVisits": { "type": "number" }, "messageTemplate": { "type": "string" }, "mood": { "type": "string" }, "structuredContentTemplate": { "type": "string" } }, "additionalProperties": {} } } ] }}UNAUTHENTICATED: No credential (HTTP 200)
{ "errors": [ { "message": "Unauthorized", "locations": [ { "line": 2, "column": 3 } ], "path": [ "nodeConfigSchemas" ], "extensions": { "code": "UNAUTHENTICATED", "originalError": { "message": "Unauthorized", "statusCode": 401 } } } ], "data": null}workflowRuns
Section titled “workflowRuns”List the runs of one session, newest first, to debug what a workflow did.
- Kind: query
- Credential: Dashboard JWT:
Authorization: Bearer <jwt>, fromlogin(a person or a one-off script) - Available in: sandbox and production
- A run is one execution of the graph for one turn. Pass a run’s id to
workflowRun(id)for the full node trace.
Request
query Runs($sessionId: Int!) { workflowRuns(sessionId: $sessionId) { id status workflowId errorMessage totalTokensUsed createdAt completedAt }}{ "sessionId": 4211}Allowed values
WorkflowRunStatus:AWAITING_INPUT,COMPLETED,FAILED,INTERRUPTED,PENDING,RUNNING
Code samples
#!/usr/bin/env bash# workflowRuns: List the runs of one session, newest first, to debug what a workflow did.# Credential: Dashboard JWT (WETEL_JWT).API_URL="${WETEL_API_URL:-https://api.wetel.dev/graphql}"
curl -sS "$API_URL" \ -H "Content-Type: application/json" \ -H "x-huat-platform: customer" \ -H "Authorization: Bearer $WETEL_JWT" \ --data-binary @- <<'JSON'{ "query": "query Runs($sessionId:Int!){workflowRuns(sessionId:$sessionId){id status workflowId errorMessage totalTokensUsed createdAt completedAt}}", "variables": { "sessionId": 4211 }}JSON// workflowRuns: List the runs of one session, newest first, to debug what a workflow did.// Credential: Dashboard JWT (WETEL_JWT). Node 18+ (built-in fetch).const API_URL = process.env.WETEL_API_URL ?? "https://api.wetel.dev/graphql";
const query = `query Runs($sessionId: Int!) { workflowRuns(sessionId: $sessionId) { id status workflowId errorMessage totalTokensUsed createdAt completedAt }}`;
const variables = { sessionId: 4211,};
type GraphQLResponse = { data?: Record<string, unknown>; errors?: { message: string; extensions?: { code?: string } }[];};
async function main(): Promise<void> { const res = await fetch(API_URL, { method: "POST", headers: { "Content-Type": "application/json", "x-huat-platform": "customer", Authorization: `Bearer ${process.env.WETEL_JWT ?? ""}`, }, body: JSON.stringify({ query, variables }), }); const body = (await res.json()) as GraphQLResponse; if (body.errors?.length) { const error = body.errors[0]; throw new Error(`${error.extensions?.code}: ${error.message}`); } console.log(JSON.stringify(body.data?.workflowRuns, null, 2));}
main().catch(err => { console.error(err); process.exit(1);});
export {};<?php// workflowRuns: List the runs of one session, newest first, to debug what a workflow did.// Credential: Dashboard JWT (WETEL_JWT). PHP 7.3+ with the curl extension.$apiUrl = getenv('WETEL_API_URL') ?: 'https://api.wetel.dev/graphql';
$query = <<<'GRAPHQL'query Runs($sessionId: Int!) { workflowRuns(sessionId: $sessionId) { id status workflowId errorMessage totalTokensUsed createdAt completedAt }}GRAPHQL;
$variables = json_decode(<<<'JSON'{ "sessionId": 4211}JSON);
$ch = curl_init($apiUrl);curl_setopt_array($ch, [ CURLOPT_POST => true, CURLOPT_RETURNTRANSFER => true, CURLOPT_HTTPHEADER => [ 'Content-Type: application/json', 'x-huat-platform: customer', 'Authorization: Bearer ' . getenv('WETEL_JWT'), ], CURLOPT_POSTFIELDS => json_encode(['query' => $query, 'variables' => $variables]),]);$body = json_decode(curl_exec($ch), true);curl_close($ch);
if (!empty($body['errors'])) { $error = $body['errors'][0]; fwrite(STDERR, ($error['extensions']['code'] ?? 'ERROR') . ': ' . $error['message'] . PHP_EOL); exit(1);}echo json_encode($body['data']['workflowRuns'], JSON_PRETTY_PRINT), PHP_EOL;// workflowRuns: List the runs of one session, newest first, to debug what a workflow did.// Credential: Dashboard JWT (WETEL_JWT). .NET 8: put this in Program.cs of a console app and run "dotnet run".using System.Net.Http.Headers;using System.Net.Http.Json;using System.Text.Json;using System.Text.Json.Nodes;
var apiUrl = Environment.GetEnvironmentVariable("WETEL_API_URL") ?? "https://api.wetel.dev/graphql";
const string query = """ query Runs($sessionId: Int!) { workflowRuns(sessionId: $sessionId) { id status workflowId errorMessage totalTokensUsed createdAt completedAt } } """;
var variables = JsonNode.Parse(""" { "sessionId": 4211 } """);
using var http = new HttpClient();using var request = new HttpRequestMessage(HttpMethod.Post, apiUrl){ Content = JsonContent.Create(new { query, variables }),};request.Headers.Add("x-huat-platform", "customer");request.Headers.Authorization = new AuthenticationHeaderValue("Bearer", Environment.GetEnvironmentVariable("WETEL_JWT"));
using var response = await http.SendAsync(request);var body = JsonNode.Parse(await response.Content.ReadAsStringAsync())!;if (body["errors"] is JsonArray errors && errors.Count > 0){ Console.Error.WriteLine($"{errors[0]?["extensions"]?["code"]}: {errors[0]?["message"]}"); return 1;}Console.WriteLine(body["data"]?["workflowRuns"]?.ToJsonString(new JsonSerializerOptions { WriteIndented = true }));return 0;# workflowRuns: List the runs of one session, newest first, to debug what a workflow did.# Credential: Dashboard JWT (WETEL_JWT). Python 3.8+ with "requests".import jsonimport os
import requests
API_URL = os.environ.get("WETEL_API_URL", "https://api.wetel.dev/graphql")
QUERY = """query Runs($sessionId: Int!) { workflowRuns(sessionId: $sessionId) { id status workflowId errorMessage totalTokensUsed createdAt completedAt }}"""
VARIABLES = { "sessionId": 4211,}
response = requests.post( API_URL, json={"query": QUERY, "variables": VARIABLES}, headers={ "x-huat-platform": "customer", "Authorization": "Bearer " + os.environ["WETEL_JWT"], }, timeout=30,)body = response.json()if body.get("errors"): error = body["errors"][0] raise SystemExit(f"{error.get('extensions', {}).get('code')}: {error['message']}")print(json.dumps(body["data"]["workflowRuns"], indent=2))Responses
Success (HTTP 200)
{ "data": { "workflowRuns": [ { "id": 5531, "status": "COMPLETED", "workflowId": 12, "errorMessage": null, "totalTokensUsed": 412, "createdAt": "2026-10-12T03:15:02.300Z", "completedAt": "2026-10-12T03:15:05.902Z" } ] }}UNAUTHENTICATED: Called with an API key instead of a JWT (HTTP 200)
{ "errors": [ { "message": "Unauthorized", "locations": [ { "line": 2, "column": 3 } ], "path": [ "workflowRuns" ], "extensions": { "code": "UNAUTHENTICATED", "originalError": { "message": "Unauthorized", "statusCode": 401 } } } ], "data": null}workflowRun
Section titled “workflowRun”Read one run’s node trace and context, to see exactly which nodes ran, in order, and why.
- Kind: query
- Credential: Dashboard JWT:
Authorization: Bearer <jwt>, fromlogin(a person or a one-off script) - Available in: sandbox and production
nodeTraceis a JSON array with one entry per finished node (nodeId,nodeType,startedAt,finishedAtand, depending on the node,outputVar,outputValue,tokensUsed, a status and error details).contextis the run’s variables. Secrets and signed URLs are masked on read.- A run that stopped at an
await_replyorapprovalnode isAWAITING_INPUT; the run that continued it carries its id inresumedFromRunId, and the paused run points forward withcontinuedByRunId. - Returns
null(not an error) for an id that does not exist in your workspace.
Request
query Run($id: Int!) { workflowRun(id: $id) { id status sessionId workflowId errorMessage nodeTrace context resumedFromRunId continuedByRunId totalTokensUsed createdAt completedAt }}{ "id": 5531}Allowed values
WorkflowRunStatus:AWAITING_INPUT,COMPLETED,FAILED,INTERRUPTED,PENDING,RUNNING
Code samples
#!/usr/bin/env bash# workflowRun: Read one run's node trace and context, to see exactly which nodes ran, in order, and why.# Credential: Dashboard JWT (WETEL_JWT).API_URL="${WETEL_API_URL:-https://api.wetel.dev/graphql}"
curl -sS "$API_URL" \ -H "Content-Type: application/json" \ -H "x-huat-platform: customer" \ -H "Authorization: Bearer $WETEL_JWT" \ --data-binary @- <<'JSON'{ "query": "query Run($id:Int!){workflowRun(id:$id){id status sessionId workflowId errorMessage nodeTrace context resumedFromRunId continuedByRunId totalTokensUsed createdAt completedAt}}", "variables": { "id": 5531 }}JSON// workflowRun: Read one run's node trace and context, to see exactly which nodes ran, in order, and why.// Credential: Dashboard JWT (WETEL_JWT). Node 18+ (built-in fetch).const API_URL = process.env.WETEL_API_URL ?? "https://api.wetel.dev/graphql";
const query = `query Run($id: Int!) { workflowRun(id: $id) { id status sessionId workflowId errorMessage nodeTrace context resumedFromRunId continuedByRunId totalTokensUsed createdAt completedAt }}`;
const variables = { id: 5531,};
type GraphQLResponse = { data?: Record<string, unknown>; errors?: { message: string; extensions?: { code?: string } }[];};
async function main(): Promise<void> { const res = await fetch(API_URL, { method: "POST", headers: { "Content-Type": "application/json", "x-huat-platform": "customer", Authorization: `Bearer ${process.env.WETEL_JWT ?? ""}`, }, body: JSON.stringify({ query, variables }), }); const body = (await res.json()) as GraphQLResponse; if (body.errors?.length) { const error = body.errors[0]; throw new Error(`${error.extensions?.code}: ${error.message}`); } console.log(JSON.stringify(body.data?.workflowRun, null, 2));}
main().catch(err => { console.error(err); process.exit(1);});
export {};<?php// workflowRun: Read one run's node trace and context, to see exactly which nodes ran, in order, and why.// Credential: Dashboard JWT (WETEL_JWT). PHP 7.3+ with the curl extension.$apiUrl = getenv('WETEL_API_URL') ?: 'https://api.wetel.dev/graphql';
$query = <<<'GRAPHQL'query Run($id: Int!) { workflowRun(id: $id) { id status sessionId workflowId errorMessage nodeTrace context resumedFromRunId continuedByRunId totalTokensUsed createdAt completedAt }}GRAPHQL;
$variables = json_decode(<<<'JSON'{ "id": 5531}JSON);
$ch = curl_init($apiUrl);curl_setopt_array($ch, [ CURLOPT_POST => true, CURLOPT_RETURNTRANSFER => true, CURLOPT_HTTPHEADER => [ 'Content-Type: application/json', 'x-huat-platform: customer', 'Authorization: Bearer ' . getenv('WETEL_JWT'), ], CURLOPT_POSTFIELDS => json_encode(['query' => $query, 'variables' => $variables]),]);$body = json_decode(curl_exec($ch), true);curl_close($ch);
if (!empty($body['errors'])) { $error = $body['errors'][0]; fwrite(STDERR, ($error['extensions']['code'] ?? 'ERROR') . ': ' . $error['message'] . PHP_EOL); exit(1);}echo json_encode($body['data']['workflowRun'], JSON_PRETTY_PRINT), PHP_EOL;// workflowRun: Read one run's node trace and context, to see exactly which nodes ran, in order, and why.// Credential: Dashboard JWT (WETEL_JWT). .NET 8: put this in Program.cs of a console app and run "dotnet run".using System.Net.Http.Headers;using System.Net.Http.Json;using System.Text.Json;using System.Text.Json.Nodes;
var apiUrl = Environment.GetEnvironmentVariable("WETEL_API_URL") ?? "https://api.wetel.dev/graphql";
const string query = """ query Run($id: Int!) { workflowRun(id: $id) { id status sessionId workflowId errorMessage nodeTrace context resumedFromRunId continuedByRunId totalTokensUsed createdAt completedAt } } """;
var variables = JsonNode.Parse(""" { "id": 5531 } """);
using var http = new HttpClient();using var request = new HttpRequestMessage(HttpMethod.Post, apiUrl){ Content = JsonContent.Create(new { query, variables }),};request.Headers.Add("x-huat-platform", "customer");request.Headers.Authorization = new AuthenticationHeaderValue("Bearer", Environment.GetEnvironmentVariable("WETEL_JWT"));
using var response = await http.SendAsync(request);var body = JsonNode.Parse(await response.Content.ReadAsStringAsync())!;if (body["errors"] is JsonArray errors && errors.Count > 0){ Console.Error.WriteLine($"{errors[0]?["extensions"]?["code"]}: {errors[0]?["message"]}"); return 1;}Console.WriteLine(body["data"]?["workflowRun"]?.ToJsonString(new JsonSerializerOptions { WriteIndented = true }));return 0;# workflowRun: Read one run's node trace and context, to see exactly which nodes ran, in order, and why.# Credential: Dashboard JWT (WETEL_JWT). Python 3.8+ with "requests".import jsonimport os
import requests
API_URL = os.environ.get("WETEL_API_URL", "https://api.wetel.dev/graphql")
QUERY = """query Run($id: Int!) { workflowRun(id: $id) { id status sessionId workflowId errorMessage nodeTrace context resumedFromRunId continuedByRunId totalTokensUsed createdAt completedAt }}"""
VARIABLES = { "id": 5531,}
response = requests.post( API_URL, json={"query": QUERY, "variables": VARIABLES}, headers={ "x-huat-platform": "customer", "Authorization": "Bearer " + os.environ["WETEL_JWT"], }, timeout=30,)body = response.json()if body.get("errors"): error = body["errors"][0] raise SystemExit(f"{error.get('extensions', {}).get('code')}: {error['message']}")print(json.dumps(body["data"]["workflowRun"], indent=2))Responses
Success (HTTP 200)
{ "data": { "workflowRun": { "id": 5531, "status": "COMPLETED", "sessionId": 4211, "workflowId": 12, "errorMessage": null, "nodeTrace": [ { "nodeId": "start", "nodeType": "start", "startedAt": "2026-10-12T03:15:02.310Z", "finishedAt": "2026-10-12T03:15:02.311Z" }, { "nodeId": "classify", "nodeType": "llm", "startedAt": "2026-10-12T03:15:02.312Z", "finishedAt": "2026-10-12T03:15:04.870Z", "outputVar": "category", "outputValue": "network", "tokensUsed": 412 }, { "nodeId": "reply", "nodeType": "response", "startedAt": "2026-10-12T03:15:04.871Z", "finishedAt": "2026-10-12T03:15:05.902Z" } ], "context": { "userMessage": "My laptop cannot connect to the office VPN since this morning.", "category": "network" }, "resumedFromRunId": null, "continuedByRunId": null, "totalTokensUsed": 412, "createdAt": "2026-10-12T03:15:02.300Z", "completedAt": "2026-10-12T03:15:05.902Z" } }}Not in your workspace (HTTP 200)
{ "data": { "workflowRun": null }}UNAUTHENTICATED: Called with an API key instead of a JWT (HTTP 200)
{ "errors": [ { "message": "Unauthorized", "locations": [ { "line": 2, "column": 3 } ], "path": [ "workflowRun" ], "extensions": { "code": "UNAUTHENTICATED", "originalError": { "message": "Unauthorized", "statusCode": 401 } } } ], "data": { "workflowRun": null }}pendingAwaitReplies
Section titled “pendingAwaitReplies”List the conversations waiting at an await_reply node for the user’s answer, newest first.
- Kind: query
- Credential: Dashboard JWT:
Authorization: Bearer <jwt>, fromlogin(a person or a one-off script) - Available in: sandbox and production
- Pass
sessionIdto check one conversation (is it stuck, or waiting for the user?); omit it for every open wait in the workspace. OnlyWAITINGrows are listed. - [object Object]
optionsis the numbered list the user was shown (empty inFREE_TEXTmode). The stored run context is never returned here.
Request
query Waiting($sessionId: Int) { pendingAwaitReplies(sessionId: $sessionId) { id sessionId workflowRunId nodeId status matchMode saveAs options { slot id label } expiresAt }}{ "sessionId": 4211}Allowed values
WorkflowRunWaitStatus:CANCELLED,EXPIRED,RESUMED,WAITINGAwaitReplyMatchMode:FREE_TEXT,OPTIONS
Code samples
#!/usr/bin/env bash# pendingAwaitReplies: List the conversations waiting at an await_reply node for the user's answer, newest first.# Credential: Dashboard JWT (WETEL_JWT).API_URL="${WETEL_API_URL:-https://api.wetel.dev/graphql}"
curl -sS "$API_URL" \ -H "Content-Type: application/json" \ -H "x-huat-platform: customer" \ -H "Authorization: Bearer $WETEL_JWT" \ --data-binary @- <<'JSON'{ "query": "query Waiting($sessionId:Int){pendingAwaitReplies(sessionId:$sessionId){id sessionId workflowRunId nodeId status matchMode saveAs options{slot id label}expiresAt}}", "variables": { "sessionId": 4211 }}JSON// pendingAwaitReplies: List the conversations waiting at an await_reply node for the user's answer, newest first.// Credential: Dashboard JWT (WETEL_JWT). Node 18+ (built-in fetch).const API_URL = process.env.WETEL_API_URL ?? "https://api.wetel.dev/graphql";
const query = `query Waiting($sessionId: Int) { pendingAwaitReplies(sessionId: $sessionId) { id sessionId workflowRunId nodeId status matchMode saveAs options { slot id label } expiresAt }}`;
const variables = { sessionId: 4211,};
type GraphQLResponse = { data?: Record<string, unknown>; errors?: { message: string; extensions?: { code?: string } }[];};
async function main(): Promise<void> { const res = await fetch(API_URL, { method: "POST", headers: { "Content-Type": "application/json", "x-huat-platform": "customer", Authorization: `Bearer ${process.env.WETEL_JWT ?? ""}`, }, body: JSON.stringify({ query, variables }), }); const body = (await res.json()) as GraphQLResponse; if (body.errors?.length) { const error = body.errors[0]; throw new Error(`${error.extensions?.code}: ${error.message}`); } console.log(JSON.stringify(body.data?.pendingAwaitReplies, null, 2));}
main().catch(err => { console.error(err); process.exit(1);});
export {};<?php// pendingAwaitReplies: List the conversations waiting at an await_reply node for the user's answer, newest first.// Credential: Dashboard JWT (WETEL_JWT). PHP 7.3+ with the curl extension.$apiUrl = getenv('WETEL_API_URL') ?: 'https://api.wetel.dev/graphql';
$query = <<<'GRAPHQL'query Waiting($sessionId: Int) { pendingAwaitReplies(sessionId: $sessionId) { id sessionId workflowRunId nodeId status matchMode saveAs options { slot id label } expiresAt }}GRAPHQL;
$variables = json_decode(<<<'JSON'{ "sessionId": 4211}JSON);
$ch = curl_init($apiUrl);curl_setopt_array($ch, [ CURLOPT_POST => true, CURLOPT_RETURNTRANSFER => true, CURLOPT_HTTPHEADER => [ 'Content-Type: application/json', 'x-huat-platform: customer', 'Authorization: Bearer ' . getenv('WETEL_JWT'), ], CURLOPT_POSTFIELDS => json_encode(['query' => $query, 'variables' => $variables]),]);$body = json_decode(curl_exec($ch), true);curl_close($ch);
if (!empty($body['errors'])) { $error = $body['errors'][0]; fwrite(STDERR, ($error['extensions']['code'] ?? 'ERROR') . ': ' . $error['message'] . PHP_EOL); exit(1);}echo json_encode($body['data']['pendingAwaitReplies'], JSON_PRETTY_PRINT), PHP_EOL;// pendingAwaitReplies: List the conversations waiting at an await_reply node for the user's answer, newest first.// Credential: Dashboard JWT (WETEL_JWT). .NET 8: put this in Program.cs of a console app and run "dotnet run".using System.Net.Http.Headers;using System.Net.Http.Json;using System.Text.Json;using System.Text.Json.Nodes;
var apiUrl = Environment.GetEnvironmentVariable("WETEL_API_URL") ?? "https://api.wetel.dev/graphql";
const string query = """ query Waiting($sessionId: Int) { pendingAwaitReplies(sessionId: $sessionId) { id sessionId workflowRunId nodeId status matchMode saveAs options { slot id label } expiresAt } } """;
var variables = JsonNode.Parse(""" { "sessionId": 4211 } """);
using var http = new HttpClient();using var request = new HttpRequestMessage(HttpMethod.Post, apiUrl){ Content = JsonContent.Create(new { query, variables }),};request.Headers.Add("x-huat-platform", "customer");request.Headers.Authorization = new AuthenticationHeaderValue("Bearer", Environment.GetEnvironmentVariable("WETEL_JWT"));
using var response = await http.SendAsync(request);var body = JsonNode.Parse(await response.Content.ReadAsStringAsync())!;if (body["errors"] is JsonArray errors && errors.Count > 0){ Console.Error.WriteLine($"{errors[0]?["extensions"]?["code"]}: {errors[0]?["message"]}"); return 1;}Console.WriteLine(body["data"]?["pendingAwaitReplies"]?.ToJsonString(new JsonSerializerOptions { WriteIndented = true }));return 0;# pendingAwaitReplies: List the conversations waiting at an await_reply node for the user's answer, newest first.# Credential: Dashboard JWT (WETEL_JWT). Python 3.8+ with "requests".import jsonimport os
import requests
API_URL = os.environ.get("WETEL_API_URL", "https://api.wetel.dev/graphql")
QUERY = """query Waiting($sessionId: Int) { pendingAwaitReplies(sessionId: $sessionId) { id sessionId workflowRunId nodeId status matchMode saveAs options { slot id label } expiresAt }}"""
VARIABLES = { "sessionId": 4211,}
response = requests.post( API_URL, json={"query": QUERY, "variables": VARIABLES}, headers={ "x-huat-platform": "customer", "Authorization": "Bearer " + os.environ["WETEL_JWT"], }, timeout=30,)body = response.json()if body.get("errors"): error = body["errors"][0] raise SystemExit(f"{error.get('extensions', {}).get('code')}: {error['message']}")print(json.dumps(body["data"]["pendingAwaitReplies"], indent=2))Responses
Success (HTTP 200)
{ "data": { "pendingAwaitReplies": [ { "id": 207, "sessionId": 4211, "workflowRunId": 5534, "nodeId": "ask_device", "status": "WAITING", "matchMode": "OPTIONS", "saveAs": "device", "options": [ { "slot": 1, "id": "laptop", "label": "Laptop" }, { "slot": 2, "id": "desktop", "label": "Desktop" } ], "expiresAt": "2026-10-13T03:16:10.000Z" } ] }}BAD_USER_INPUT: Session id sent as a string (HTTP 400)
Sent with variables:
{ "sessionId": "four-two-one-one"}{ "errors": [ { "message": "Variable \"$sessionId\" got invalid value \"four-two-one-one\"; Int cannot represent non-integer value: \"four-two-one-one\"", "locations": [ { "line": 1, "column": 15 } ], "extensions": { "code": "BAD_USER_INPUT" } } ]}runWorkflowTask
Section titled “runWorkflowTask”Run an agent’s published workflow headlessly (no chat message). Asynchronous; returns once accepted.
- Kind: mutation
- Credential: API key:
X-Api-Key: sk_..., kept on your server only - Available in: sandbox and production
- A successful answer means the task was accepted and queued, never that it succeeded. Read the result with
workflowTaskOutcomeand the returnedsessionId. variablesis a flat JSON object merged into the run’s context;externalConversationIdgroups repeat calls for one conversation into one session.- The agent must be
ACTIVEand have a workflow attached. Limited to 60 calls per minute per client IP by default.
Request
mutation RunTask($input: RunWorkflowTaskInput!) { runWorkflowTask(input: $input) { accepted sessionId workflowId reusedSession turnCount }}{ "input": { "agentId": 7, "externalConversationId": "ticket-1002", "variables": { "ticketId": "T-1002", "subject": "Cannot print to the 3rd floor printer", "reporterRole": "staff" } }}Input fields
RunWorkflowTaskInput
| Field | Type | Required | Description |
|---|---|---|---|
agentId | Int! | yes | Id of an ACTIVE agent of the API key workspace with a workflow attached; NotFound for an unknown agent, BadRequest when it is inactive or has no workflow. |
attachmentFilename | String | no | Filename for attachmentUrl (at most 255 characters). |
attachmentMime | String | no | MIME type for attachmentUrl (at most 255 characters); when omitted it is taken from the filename extension, then from the URL path. |
attachmentUrl | String | no | Single-file shortcut: absolute http(s) URL of one file. Equivalent to attachments: [{url, mime, filename}] with the attachmentMime and attachmentFilename fields below. Send EITHER this OR attachments, never both (both is refused with a 400). |
attachments | [MessageAttachmentInput!] | no | Optional files for this task (at most 10), the same structured shape and validation as attachments on sdkSendMessage: absolute http(s) URL, no embedded credentials, sanitised filename, MIME and kind inferred when omitted. Not fetched at accept time; a workflow node downloads them when it needs them. In workflow context they appear as ctx.attachments (list of {url, mime, filename, kind}), ctx.attachmentImageUrls (the IMAGE urls, usable as imageUrlsVar), and the first entry fills ctx.attachment, ctx.attachmentUrl, ctx.attachmentMime and ctx.attachmentFilename. Do not combine with the single attachmentUrl field (400). |
clientTurnId | String | no | Optional opaque correlation token of your own (at most 255 characters), echoed on every AiResponseEvent this run publishes; not stored. |
externalConversationId | String! | yes | Your own id for the conversation or job (1 to 255 characters). It becomes the session clientExternalId: calls with the same id reuse the open session for that agent, so they share history. |
variables | JSON | no | Facts for the run as a flat JSON object (send it through variables): at most 32 keys matching [A-Za-z][A-Za-z0-9_]* (64 characters at most), values string (at most 4096 characters), number, boolean or null. Keys starting with ”_” and runner names such as sessionId, userMessage or channel are refused. Merged into the run variables unchanged, never coerced. |
MessageAttachmentInput
| Field | Type | Required | Description |
|---|---|---|---|
filename | String | no | Original filename, at most 255 characters. Quotes, square brackets and control characters are removed. |
kind | AttachmentKind | no | Kind of file. When omitted it is inferred from the MIME type (image/*, audio/*, video/*, PDF and Office or text types are DOCUMENT, anything else OTHER). |
mime | String | no | MIME type, at most 100 characters (for example image/png). When omitted it is inferred from the filename, then from the URL path extension. |
url | String! | yes | Absolute http(s) URL of the file, at most 2048 characters, without user:password credentials. Treat a signed URL as a credential: it is redacted wherever messages are read back. |
Allowed values
AttachmentKind:AUDIO,DOCUMENT,IMAGE,OTHER,VIDEO
Code samples
#!/usr/bin/env bash# runWorkflowTask: Run an agent's published workflow headlessly (no chat message). Asynchronous; returns once accepted.# Credential: API key (WETEL_API_KEY).API_URL="${WETEL_API_URL:-https://api.wetel.dev/graphql}"
curl -sS "$API_URL" \ -H "Content-Type: application/json" \ -H "x-huat-platform: customer" \ -H "X-Api-Key: $WETEL_API_KEY" \ --data-binary @- <<'JSON'{ "query": "mutation RunTask($input:RunWorkflowTaskInput!){runWorkflowTask(input:$input){accepted sessionId workflowId reusedSession turnCount}}", "variables": { "input": { "agentId": 7, "externalConversationId": "ticket-1002", "variables": { "ticketId": "T-1002", "subject": "Cannot print to the 3rd floor printer", "reporterRole": "staff" } } }}JSON// runWorkflowTask: Run an agent's published workflow headlessly (no chat message). Asynchronous; returns once accepted.// Credential: API key (WETEL_API_KEY). Node 18+ (built-in fetch).const API_URL = process.env.WETEL_API_URL ?? "https://api.wetel.dev/graphql";
const query = `mutation RunTask($input: RunWorkflowTaskInput!) { runWorkflowTask(input: $input) { accepted sessionId workflowId reusedSession turnCount }}`;
const variables = { input: { agentId: 7, externalConversationId: "ticket-1002", variables: { ticketId: "T-1002", subject: "Cannot print to the 3rd floor printer", reporterRole: "staff", }, },};
type GraphQLResponse = { data?: Record<string, unknown>; errors?: { message: string; extensions?: { code?: string } }[];};
async function main(): Promise<void> { const res = await fetch(API_URL, { method: "POST", headers: { "Content-Type": "application/json", "x-huat-platform": "customer", "X-Api-Key": process.env.WETEL_API_KEY ?? "", }, body: JSON.stringify({ query, variables }), }); const body = (await res.json()) as GraphQLResponse; if (body.errors?.length) { const error = body.errors[0]; throw new Error(`${error.extensions?.code}: ${error.message}`); } console.log(JSON.stringify(body.data?.runWorkflowTask, null, 2));}
main().catch(err => { console.error(err); process.exit(1);});
export {};<?php// runWorkflowTask: Run an agent's published workflow headlessly (no chat message). Asynchronous; returns once accepted.// Credential: API key (WETEL_API_KEY). PHP 7.3+ with the curl extension.$apiUrl = getenv('WETEL_API_URL') ?: 'https://api.wetel.dev/graphql';
$query = <<<'GRAPHQL'mutation RunTask($input: RunWorkflowTaskInput!) { runWorkflowTask(input: $input) { accepted sessionId workflowId reusedSession turnCount }}GRAPHQL;
$variables = json_decode(<<<'JSON'{ "input": { "agentId": 7, "externalConversationId": "ticket-1002", "variables": { "ticketId": "T-1002", "subject": "Cannot print to the 3rd floor printer", "reporterRole": "staff" } }}JSON);
$ch = curl_init($apiUrl);curl_setopt_array($ch, [ CURLOPT_POST => true, CURLOPT_RETURNTRANSFER => true, CURLOPT_HTTPHEADER => [ 'Content-Type: application/json', 'x-huat-platform: customer', 'X-Api-Key: ' . getenv('WETEL_API_KEY'), ], CURLOPT_POSTFIELDS => json_encode(['query' => $query, 'variables' => $variables]),]);$body = json_decode(curl_exec($ch), true);curl_close($ch);
if (!empty($body['errors'])) { $error = $body['errors'][0]; fwrite(STDERR, ($error['extensions']['code'] ?? 'ERROR') . ': ' . $error['message'] . PHP_EOL); exit(1);}echo json_encode($body['data']['runWorkflowTask'], JSON_PRETTY_PRINT), PHP_EOL;// runWorkflowTask: Run an agent's published workflow headlessly (no chat message). Asynchronous; returns once accepted.// Credential: API key (WETEL_API_KEY). .NET 8: put this in Program.cs of a console app and run "dotnet run".using System.Net.Http.Headers;using System.Net.Http.Json;using System.Text.Json;using System.Text.Json.Nodes;
var apiUrl = Environment.GetEnvironmentVariable("WETEL_API_URL") ?? "https://api.wetel.dev/graphql";
const string query = """ mutation RunTask($input: RunWorkflowTaskInput!) { runWorkflowTask(input: $input) { accepted sessionId workflowId reusedSession turnCount } } """;
var variables = JsonNode.Parse(""" { "input": { "agentId": 7, "externalConversationId": "ticket-1002", "variables": { "ticketId": "T-1002", "subject": "Cannot print to the 3rd floor printer", "reporterRole": "staff" } } } """);
using var http = new HttpClient();using var request = new HttpRequestMessage(HttpMethod.Post, apiUrl){ Content = JsonContent.Create(new { query, variables }),};request.Headers.Add("x-huat-platform", "customer");request.Headers.Add("X-Api-Key", Environment.GetEnvironmentVariable("WETEL_API_KEY"));
using var response = await http.SendAsync(request);var body = JsonNode.Parse(await response.Content.ReadAsStringAsync())!;if (body["errors"] is JsonArray errors && errors.Count > 0){ Console.Error.WriteLine($"{errors[0]?["extensions"]?["code"]}: {errors[0]?["message"]}"); return 1;}Console.WriteLine(body["data"]?["runWorkflowTask"]?.ToJsonString(new JsonSerializerOptions { WriteIndented = true }));return 0;# runWorkflowTask: Run an agent's published workflow headlessly (no chat message). Asynchronous; returns once accepted.# Credential: API key (WETEL_API_KEY). Python 3.8+ with "requests".import jsonimport os
import requests
API_URL = os.environ.get("WETEL_API_URL", "https://api.wetel.dev/graphql")
QUERY = """mutation RunTask($input: RunWorkflowTaskInput!) { runWorkflowTask(input: $input) { accepted sessionId workflowId reusedSession turnCount }}"""
VARIABLES = { "input": { "agentId": 7, "externalConversationId": "ticket-1002", "variables": { "ticketId": "T-1002", "subject": "Cannot print to the 3rd floor printer", "reporterRole": "staff", }, },}
response = requests.post( API_URL, json={"query": QUERY, "variables": VARIABLES}, headers={ "x-huat-platform": "customer", "X-Api-Key": os.environ["WETEL_API_KEY"], }, timeout=30,)body = response.json()if body.get("errors"): error = body["errors"][0] raise SystemExit(f"{error.get('extensions', {}).get('code')}: {error['message']}")print(json.dumps(body["data"]["runWorkflowTask"], indent=2))Responses
Accepted (HTTP 200)
{ "data": { "runWorkflowTask": { "accepted": true, "sessionId": 4290, "workflowId": 12, "reusedSession": false, "turnCount": 1 } }}BAD_REQUEST: Agent has no workflow (HTTP 200)
{ "errors": [ { "message": "Agent 7 has no workflow attached — runWorkflowTask requires one", "locations": [ { "line": 2, "column": 3 } ], "path": [ "runWorkflowTask" ], "extensions": { "code": "BAD_REQUEST", "originalError": { "message": "Agent 7 has no workflow attached — runWorkflowTask requires one", "error": "Bad Request", "statusCode": 400 } } } ], "data": null}NOT_FOUND: Agent not in the key’s workspace (HTTP 200)
{ "errors": [ { "message": "Agent 7 not found", "locations": [ { "line": 2, "column": 3 } ], "path": [ "runWorkflowTask" ], "extensions": { "code": "NOT_FOUND", "status": 404, "originalError": { "message": "Agent 7 not found", "error": "Not Found", "statusCode": 404 } } } ], "data": null}workflowTaskOutcome
Section titled “workflowTaskOutcome”Read the outcome of a task started with runWorkflowTask, with the same API key.
- Kind: query
- Credential: API key:
X-Api-Key: sk_..., kept on your server only - Available in: sandbox and production
statusis the newest run’s status:PENDINGandRUNNINGmean poll again (with a back-off);COMPLETED,FAILED,INTERRUPTEDandAWAITING_INPUTare final for now.nullmeans the task is still queued.repliesare what the agent said, oldest first. The run context and node trace are never returned here.
Request
query TaskOutcome($sessionId: Int!) { workflowTaskOutcome(sessionId: $sessionId) { sessionId status runs { id status errorMessage } replies { text createdAt } }}{ "sessionId": 4290}Allowed values
WorkflowRunStatus:AWAITING_INPUT,COMPLETED,FAILED,INTERRUPTED,PENDING,RUNNING
Code samples
#!/usr/bin/env bash# workflowTaskOutcome: Read the outcome of a task started with runWorkflowTask, with the same API key.# Credential: API key (WETEL_API_KEY).API_URL="${WETEL_API_URL:-https://api.wetel.dev/graphql}"
curl -sS "$API_URL" \ -H "Content-Type: application/json" \ -H "x-huat-platform: customer" \ -H "X-Api-Key: $WETEL_API_KEY" \ --data-binary @- <<'JSON'{ "query": "query TaskOutcome($sessionId:Int!){workflowTaskOutcome(sessionId:$sessionId){sessionId status runs{id status errorMessage}replies{text createdAt}}}", "variables": { "sessionId": 4290 }}JSON// workflowTaskOutcome: Read the outcome of a task started with runWorkflowTask, with the same API key.// Credential: API key (WETEL_API_KEY). Node 18+ (built-in fetch).const API_URL = process.env.WETEL_API_URL ?? "https://api.wetel.dev/graphql";
const query = `query TaskOutcome($sessionId: Int!) { workflowTaskOutcome(sessionId: $sessionId) { sessionId status runs { id status errorMessage } replies { text createdAt } }}`;
const variables = { sessionId: 4290,};
type GraphQLResponse = { data?: Record<string, unknown>; errors?: { message: string; extensions?: { code?: string } }[];};
async function main(): Promise<void> { const res = await fetch(API_URL, { method: "POST", headers: { "Content-Type": "application/json", "x-huat-platform": "customer", "X-Api-Key": process.env.WETEL_API_KEY ?? "", }, body: JSON.stringify({ query, variables }), }); const body = (await res.json()) as GraphQLResponse; if (body.errors?.length) { const error = body.errors[0]; throw new Error(`${error.extensions?.code}: ${error.message}`); } console.log(JSON.stringify(body.data?.workflowTaskOutcome, null, 2));}
main().catch(err => { console.error(err); process.exit(1);});
export {};<?php// workflowTaskOutcome: Read the outcome of a task started with runWorkflowTask, with the same API key.// Credential: API key (WETEL_API_KEY). PHP 7.3+ with the curl extension.$apiUrl = getenv('WETEL_API_URL') ?: 'https://api.wetel.dev/graphql';
$query = <<<'GRAPHQL'query TaskOutcome($sessionId: Int!) { workflowTaskOutcome(sessionId: $sessionId) { sessionId status runs { id status errorMessage } replies { text createdAt } }}GRAPHQL;
$variables = json_decode(<<<'JSON'{ "sessionId": 4290}JSON);
$ch = curl_init($apiUrl);curl_setopt_array($ch, [ CURLOPT_POST => true, CURLOPT_RETURNTRANSFER => true, CURLOPT_HTTPHEADER => [ 'Content-Type: application/json', 'x-huat-platform: customer', 'X-Api-Key: ' . getenv('WETEL_API_KEY'), ], CURLOPT_POSTFIELDS => json_encode(['query' => $query, 'variables' => $variables]),]);$body = json_decode(curl_exec($ch), true);curl_close($ch);
if (!empty($body['errors'])) { $error = $body['errors'][0]; fwrite(STDERR, ($error['extensions']['code'] ?? 'ERROR') . ': ' . $error['message'] . PHP_EOL); exit(1);}echo json_encode($body['data']['workflowTaskOutcome'], JSON_PRETTY_PRINT), PHP_EOL;// workflowTaskOutcome: Read the outcome of a task started with runWorkflowTask, with the same API key.// Credential: API key (WETEL_API_KEY). .NET 8: put this in Program.cs of a console app and run "dotnet run".using System.Net.Http.Headers;using System.Net.Http.Json;using System.Text.Json;using System.Text.Json.Nodes;
var apiUrl = Environment.GetEnvironmentVariable("WETEL_API_URL") ?? "https://api.wetel.dev/graphql";
const string query = """ query TaskOutcome($sessionId: Int!) { workflowTaskOutcome(sessionId: $sessionId) { sessionId status runs { id status errorMessage } replies { text createdAt } } } """;
var variables = JsonNode.Parse(""" { "sessionId": 4290 } """);
using var http = new HttpClient();using var request = new HttpRequestMessage(HttpMethod.Post, apiUrl){ Content = JsonContent.Create(new { query, variables }),};request.Headers.Add("x-huat-platform", "customer");request.Headers.Add("X-Api-Key", Environment.GetEnvironmentVariable("WETEL_API_KEY"));
using var response = await http.SendAsync(request);var body = JsonNode.Parse(await response.Content.ReadAsStringAsync())!;if (body["errors"] is JsonArray errors && errors.Count > 0){ Console.Error.WriteLine($"{errors[0]?["extensions"]?["code"]}: {errors[0]?["message"]}"); return 1;}Console.WriteLine(body["data"]?["workflowTaskOutcome"]?.ToJsonString(new JsonSerializerOptions { WriteIndented = true }));return 0;# workflowTaskOutcome: Read the outcome of a task started with runWorkflowTask, with the same API key.# Credential: API key (WETEL_API_KEY). Python 3.8+ with "requests".import jsonimport os
import requests
API_URL = os.environ.get("WETEL_API_URL", "https://api.wetel.dev/graphql")
QUERY = """query TaskOutcome($sessionId: Int!) { workflowTaskOutcome(sessionId: $sessionId) { sessionId status runs { id status errorMessage } replies { text createdAt } }}"""
VARIABLES = { "sessionId": 4290,}
response = requests.post( API_URL, json={"query": QUERY, "variables": VARIABLES}, headers={ "x-huat-platform": "customer", "X-Api-Key": os.environ["WETEL_API_KEY"], }, timeout=30,)body = response.json()if body.get("errors"): error = body["errors"][0] raise SystemExit(f"{error.get('extensions', {}).get('code')}: {error['message']}")print(json.dumps(body["data"]["workflowTaskOutcome"], indent=2))Responses
Completed (HTTP 200)
{ "data": { "workflowTaskOutcome": { "sessionId": 4290, "status": "COMPLETED", "runs": [ { "id": 5602, "status": "COMPLETED", "errorMessage": null } ], "replies": [ { "text": "Thanks. Your ticket goes to the L2 queue; we will reply here.", "createdAt": "2026-10-12T04:10:13.540Z" } ] } }}NOT_FOUND: Not a task session of this key’s workspace (HTTP 200)
{ "errors": [ { "message": "No workflow task found for session 4290", "locations": [ { "line": 2, "column": 3 } ], "path": [ "workflowTaskOutcome" ], "extensions": { "code": "NOT_FOUND", "status": 404, "originalError": { "message": "No workflow task found for session 4290", "error": "Not Found", "statusCode": 404 } } } ], "data": null}