Skip to content

Scheduled workflows

This content is not available in your language yet.

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.

OperationKindCredentialWhat it does
createScheduledWorkflowmutationDashboard JWTCreate an ACTIVE schedule that runs an agent’s published workflow on a cron expression.
scheduledWorkflowsqueryDashboard JWTList the workspace’s schedules, every status included, ordered by id.

Create an ACTIVE schedule that runs an agent’s published workflow on a cron expression.

  • Kind: mutation
  • Credential: Dashboard JWT: Authorization: Bearer <jwt>, from login (a person or a one-off script)
  • Available in: sandbox and production
  • cronExpression has exactly 5 fields (minute hour day-of-month month day-of-week); seconds and @daily-style aliases are refused. timezone defaults to UTC.
  • variables is a flat JSON object of scalars merged into every run’s context, with the same rules as runWorkflowTask. Send it through variables, never as an inline literal.
  • externalConversationIdTemplate names each fire’s session. The default scheduled:{{scheduleId}}:{{firedAt}} gives every fire a fresh session; drop {{firedAt}} to reuse one session. Only {{scheduleId}}, {{date}} and {{firedAt}} are allowed.
  • After maxConsecutiveFailures failed runs in a row (1 to 20, default 3) the schedule turns AUTO_DISABLED.

Request

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
}
}
}

Input fields

CreateScheduledWorkflowInput

FieldTypeRequiredDescription
agentIdInt!yesThe agent whose published workflow runs on each fire. Must belong to the caller’s tenant. The agent (not the schedule) decides which workflow runs.
cronExpressionString!yesStandard 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.
externalConversationIdTemplateStringnoTemplate 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.
maxConsecutiveFailuresIntnoConsecutive failed runs before the schedule auto-disables (1-20, default 3).
nameString!yesOperator-facing label (max 100 chars).
timezoneStringnoIANA timezone the cron expression is evaluated in, e.g. “Asia/Kuala_Lumpur”. Defaults to “UTC”.
variablesJSONnoFlat 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

createScheduledWorkflow.sh
#!/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

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
}

List the workspace’s schedules, every status included, ordered by id.

  • Kind: query
  • Credential: Dashboard JWT: Authorization: Bearer <jwt>, from login (a person or a one-off script)
  • Available in: sandbox and production
  • Forward pagination: first (1 to 100, default 20) and after (a previous page’s pageInfo.endCursor).
  • lastRunStatus is the last fire’s run status in lower case (running, completed, failed and so on), or null before the first fire. nextRunAt is null unless the schedule is ACTIVE.

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
}
}
}
variables
{
"first": 20
}

Allowed values

  • ScheduledWorkflowStatus: ACTIVE, AUTO_DISABLED, PAUSED

Code samples

scheduledWorkflows.sh
#!/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

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
}