Scheduled workflows
A schedule fires an agent’s published workflow headlessly on a 5-field cron expression in an IANA timezone, for example a morning digest of open tickets or a nightly check of stale ones. Managing schedules takes a dashboard JWT. Each fire runs asynchronously, exactly like runWorkflowTask: read its outcome from the schedule’s lastRunAt and lastRunStatus, or from the run with workflowRuns. Your plan limits the number of schedules (1 on DEV, 25 on PRO, 100 on ENTERPRISE), and a paused or auto-disabled schedule still counts.
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 |
|---|---|---|---|
createScheduledWorkflow | mutation | Dashboard JWT | Create an ACTIVE schedule that runs an agent’s published workflow on a cron expression. |
scheduledWorkflows | query | Dashboard JWT | List the workspace’s schedules, every status included, ordered by id. |
createScheduledWorkflow
Section titled “createScheduledWorkflow”Create an ACTIVE schedule that runs an agent’s published workflow on a cron expression.
- Kind: mutation
- Credential: Dashboard JWT:
Authorization: Bearer <jwt>, fromlogin(a person or a one-off script) - Available in: sandbox and production
cronExpressionhas exactly 5 fields (minute hour day-of-month month day-of-week); seconds and@daily-style aliases are refused.timezonedefaults toUTC.variablesis a flat JSON object of scalars merged into every run’s context, with the same rules asrunWorkflowTask. Send it through variables, never as an inline literal.externalConversationIdTemplatenames each fire’s session. The defaultscheduled:{{scheduleId}}:{{firedAt}}gives every fire a fresh session; drop{{firedAt}}to reuse one session. Only{{scheduleId}},{{date}}and{{firedAt}}are allowed.- After
maxConsecutiveFailuresfailed runs in a row (1 to 20, default 3) the schedule turnsAUTO_DISABLED.
Request
mutation CreateSchedule($input: CreateScheduledWorkflowInput!) { createScheduledWorkflow(input: $input) { id name agentId cronExpression timezone status nextRunAt variables }}{ "input": { "agentId": 7, "name": "Weekday stale-ticket check", "cronExpression": "0 9 * * 1-5", "timezone": "Asia/Kuala_Lumpur", "variables": { "queue": "l2", "staleAfterHours": 48 } }}Input fields
CreateScheduledWorkflowInput
| Field | Type | Required | Description |
|---|---|---|---|
agentId | Int! | yes | The agent whose published workflow runs on each fire. Must belong to the caller’s tenant. The agent (not the schedule) decides which workflow runs. |
cronExpression | String! | yes | Standard 5-field cron expression (minute hour day-of-month month day-of-week), e.g. “0 9 * * 1-5”. Seconds-level (6-field) and @alias forms are rejected. |
externalConversationIdTemplate | String | no | Template for each run’s session clientExternalId. Supported placeholders: {{scheduleId}}, {{date}}, {{firedAt}}. Default “scheduled:{{scheduleId}}:{{firedAt}}” gives every fire a fresh session; drop {{firedAt}} (e.g. “scheduled:{{scheduleId}}”) to reuse one session across fires for cross-run memory. |
maxConsecutiveFailures | Int | no | Consecutive failed runs before the schedule auto-disables (1-20, default 3). |
name | String! | yes | Operator-facing label (max 100 chars). |
timezone | String | no | IANA timezone the cron expression is evaluated in, e.g. “Asia/Kuala_Lumpur”. Defaults to “UTC”. |
variables | JSON | no | Flat map of scalar facts merged into every run’s initial context — identical rules to runWorkflowTask’s variables (reserved runner keys and nested values rejected). Pass via GraphQL variables, not an inline literal. For a CONDITION gate write someId > 0, never someId != 0. |
Allowed values
ScheduledWorkflowStatus:ACTIVE,AUTO_DISABLED,PAUSED
Code samples
#!/usr/bin/env bash# createScheduledWorkflow: Create an ACTIVE schedule that runs an agent's published workflow on a cron expression.# 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 CreateSchedule($input:CreateScheduledWorkflowInput!){createScheduledWorkflow(input:$input){id name agentId cronExpression timezone status nextRunAt variables}}", "variables": { "input": { "agentId": 7, "name": "Weekday stale-ticket check", "cronExpression": "0 9 * * 1-5", "timezone": "Asia/Kuala_Lumpur", "variables": { "queue": "l2", "staleAfterHours": 48 } } }}JSON// createScheduledWorkflow: Create an ACTIVE schedule that runs an agent's published workflow on a cron expression.// 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 CreateSchedule($input: CreateScheduledWorkflowInput!) { createScheduledWorkflow(input: $input) { id name agentId cronExpression timezone status nextRunAt variables }}`;
const variables = { input: { agentId: 7, name: "Weekday stale-ticket check", cronExpression: "0 9 * * 1-5", timezone: "Asia/Kuala_Lumpur", variables: { queue: "l2", staleAfterHours: 48, }, },};
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?.createScheduledWorkflow, null, 2));}
main().catch(err => { console.error(err); process.exit(1);});
export {};<?php// createScheduledWorkflow: Create an ACTIVE schedule that runs an agent's published workflow on a cron expression.// 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 CreateSchedule($input: CreateScheduledWorkflowInput!) { createScheduledWorkflow(input: $input) { id name agentId cronExpression timezone status nextRunAt variables }}GRAPHQL;
$variables = json_decode(<<<'JSON'{ "input": { "agentId": 7, "name": "Weekday stale-ticket check", "cronExpression": "0 9 * * 1-5", "timezone": "Asia/Kuala_Lumpur", "variables": { "queue": "l2", "staleAfterHours": 48 } }}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']['createScheduledWorkflow'], JSON_PRETTY_PRINT), PHP_EOL;// createScheduledWorkflow: Create an ACTIVE schedule that runs an agent's published workflow on a cron expression.// 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 CreateSchedule($input: CreateScheduledWorkflowInput!) { createScheduledWorkflow(input: $input) { id name agentId cronExpression timezone status nextRunAt variables } } """;
var variables = JsonNode.Parse(""" { "input": { "agentId": 7, "name": "Weekday stale-ticket check", "cronExpression": "0 9 * * 1-5", "timezone": "Asia/Kuala_Lumpur", "variables": { "queue": "l2", "staleAfterHours": 48 } } } """);
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"]?["createScheduledWorkflow"]?.ToJsonString(new JsonSerializerOptions { WriteIndented = true }));return 0;# createScheduledWorkflow: Create an ACTIVE schedule that runs an agent's published workflow on a cron expression.# 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 CreateSchedule($input: CreateScheduledWorkflowInput!) { createScheduledWorkflow(input: $input) { id name agentId cronExpression timezone status nextRunAt variables }}"""
VARIABLES = { "input": { "agentId": 7, "name": "Weekday stale-ticket check", "cronExpression": "0 9 * * 1-5", "timezone": "Asia/Kuala_Lumpur", "variables": { "queue": "l2", "staleAfterHours": 48, }, },}
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"]["createScheduledWorkflow"], indent=2))Responses
Success (HTTP 200)
{ "data": { "createScheduledWorkflow": { "id": 14, "name": "Weekday stale-ticket check", "agentId": 7, "cronExpression": "0 9 * * 1-5", "timezone": "Asia/Kuala_Lumpur", "status": "ACTIVE", "nextRunAt": "2026-10-19T01:00:00.000Z", "variables": { "queue": "l2", "staleAfterHours": 48 } } }}BAD_REQUEST: Cron expression with six fields (HTTP 200)
{ "errors": [ { "message": "cronExpression must have exactly 5 fields (minute hour day-of-month month day-of-week) — received 6. Seconds-level and @alias schedules are not supported.", "locations": [ { "line": 2, "column": 3 } ], "path": [ "createScheduledWorkflow" ], "extensions": { "code": "BAD_REQUEST", "originalError": { "message": "cronExpression must have exactly 5 fields (minute hour day-of-month month day-of-week) — received 6. Seconds-level and @alias schedules are not supported.", "error": "Bad Request", "statusCode": 400 } } } ], "data": null}BAD_REQUEST: Unknown timezone (HTTP 200)
{ "errors": [ { "message": "timezone \"Asia/Kuala Lumpur\" is not a valid IANA timezone (e.g. \"UTC\", \"Asia/Kuala_Lumpur\")", "locations": [ { "line": 2, "column": 3 } ], "path": [ "createScheduledWorkflow" ], "extensions": { "code": "BAD_REQUEST", "originalError": { "message": "timezone \"Asia/Kuala Lumpur\" is not a valid IANA timezone (e.g. \"UTC\", \"Asia/Kuala_Lumpur\")", "error": "Bad Request", "statusCode": 400 } } } ], "data": null}BAD_REQUEST: Plan limit reached (HTTP 200)
{ "errors": [ { "message": "Creating another scheduled workflow would exceed your plan's limit of 25. Upgrade your plan or delete an existing scheduled workflow.", "locations": [ { "line": 2, "column": 3 } ], "path": [ "createScheduledWorkflow" ], "extensions": { "code": "BAD_REQUEST", "originalError": { "message": "Creating another scheduled workflow would exceed your plan's limit of 25. Upgrade your plan or delete an existing scheduled workflow.", "error": "Bad Request", "statusCode": 400 } } } ], "data": null}NOT_FOUND: Agent not in your workspace (HTTP 200)
{ "errors": [ { "message": "Agent 7 not found", "locations": [ { "line": 2, "column": 3 } ], "path": [ "createScheduledWorkflow" ], "extensions": { "code": "NOT_FOUND", "status": 404, "originalError": { "message": "Agent 7 not found", "error": "Not Found", "statusCode": 404 } } } ], "data": null}scheduledWorkflows
Section titled “scheduledWorkflows”List the workspace’s schedules, every status included, ordered by id.
- Kind: query
- Credential: Dashboard JWT:
Authorization: Bearer <jwt>, fromlogin(a person or a one-off script) - Available in: sandbox and production
- Forward pagination:
first(1 to 100, default 20) andafter(a previous page’spageInfo.endCursor). lastRunStatusis the last fire’s run status in lower case (running,completed,failedand so on), ornullbefore the first fire.nextRunAtisnullunless the schedule isACTIVE.
Request
query Schedules($first: Int, $after: String) { scheduledWorkflows(first: $first, after: $after) { totalCount edges { node { id name agentId cronExpression timezone status lastRunAt lastRunStatus consecutiveFailureCount nextRunAt } } pageInfo { hasNextPage endCursor } }}{ "first": 20}Allowed values
ScheduledWorkflowStatus:ACTIVE,AUTO_DISABLED,PAUSED
Code samples
#!/usr/bin/env bash# scheduledWorkflows: List the workspace's schedules, every status included, ordered by id.# 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 Schedules($first:Int$after:String){scheduledWorkflows(first:$first after:$after){totalCount edges{node{id name agentId cronExpression timezone status lastRunAt lastRunStatus consecutiveFailureCount nextRunAt}}pageInfo{hasNextPage endCursor}}}", "variables": { "first": 20 }}JSON// scheduledWorkflows: List the workspace's schedules, every status included, ordered by id.// 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 Schedules($first: Int, $after: String) { scheduledWorkflows(first: $first, after: $after) { totalCount edges { node { id name agentId cronExpression timezone status lastRunAt lastRunStatus consecutiveFailureCount nextRunAt } } pageInfo { hasNextPage endCursor } }}`;
const variables = { 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?.scheduledWorkflows, null, 2));}
main().catch(err => { console.error(err); process.exit(1);});
export {};<?php// scheduledWorkflows: List the workspace's schedules, every status included, ordered by id.// 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 Schedules($first: Int, $after: String) { scheduledWorkflows(first: $first, after: $after) { totalCount edges { node { id name agentId cronExpression timezone status lastRunAt lastRunStatus consecutiveFailureCount nextRunAt } } pageInfo { hasNextPage endCursor } }}GRAPHQL;
$variables = json_decode(<<<'JSON'{ "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']['scheduledWorkflows'], JSON_PRETTY_PRINT), PHP_EOL;// scheduledWorkflows: List the workspace's schedules, every status included, ordered by id.// 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 Schedules($first: Int, $after: String) { scheduledWorkflows(first: $first, after: $after) { totalCount edges { node { id name agentId cronExpression timezone status lastRunAt lastRunStatus consecutiveFailureCount nextRunAt } } pageInfo { hasNextPage endCursor } } } """;
var variables = JsonNode.Parse(""" { "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"]?["scheduledWorkflows"]?.ToJsonString(new JsonSerializerOptions { WriteIndented = true }));return 0;# scheduledWorkflows: List the workspace's schedules, every status included, ordered by id.# 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 Schedules($first: Int, $after: String) { scheduledWorkflows(first: $first, after: $after) { totalCount edges { node { id name agentId cronExpression timezone status lastRunAt lastRunStatus consecutiveFailureCount nextRunAt } } pageInfo { hasNextPage endCursor } }}"""
VARIABLES = { "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"]["scheduledWorkflows"], indent=2))Responses
Success (HTTP 200)
{ "data": { "scheduledWorkflows": { "totalCount": 1, "edges": [ { "node": { "id": 14, "name": "Weekday stale-ticket check", "agentId": 7, "cronExpression": "0 9 * * 1-5", "timezone": "Asia/Kuala_Lumpur", "status": "ACTIVE", "lastRunAt": "2026-10-16T01:00:00.412Z", "lastRunStatus": "completed", "consecutiveFailureCount": 0, "nextRunAt": "2026-10-19T01:00:00.000Z" } } ], "pageInfo": { "hasNextPage": false, "endCursor": "MTQ=" } } }}BAD_REQUEST: Page size out of range (HTTP 200)
{ "errors": [ { "message": "first must be between 1 and 100", "locations": [ { "line": 2, "column": 3 } ], "path": [ "scheduledWorkflows" ], "extensions": { "code": "BAD_REQUEST", "originalError": { "message": "first must be between 1 and 100", "error": "Bad Request", "statusCode": 400 } } } ], "data": null}UNAUTHENTICATED: Called with an API key instead of a JWT (HTTP 200)
{ "errors": [ { "message": "Unauthorized", "locations": [ { "line": 2, "column": 3 } ], "path": [ "scheduledWorkflows" ], "extensions": { "code": "UNAUTHENTICATED", "originalError": { "message": "Unauthorized", "statusCode": 401 } } } ], "data": null}