Skip to content

Scheduled workflow recipes

Run a workflow every minute

The simplest schedule — trigger a workflow on a fixed interval.

{
  "name": "every-minute-demo-schedule",
  "cronExpression": "0 * * * * *",
  "zoneId": "UTC",
  "startWorkflowRequest": {
    "name": "daily_report_workflow",
    "version": 1,
    "correlationId": "demo-${scheduledTime}"
  },
  "runCatchupScheduleInstances": false,
  "paused": false
}

The workflow:

{
  "name": "daily_report_workflow",
  "version": 1,
  "schemaVersion": 2,
  "tasks": [
    {
      "name": "fetch_report_data",
      "taskReferenceName": "fetch_report_data_ref",
      "type": "HTTP",
      "inputParameters": {
        "http_request": {
          "uri": "https://jsonplaceholder.typicode.com/todos?userId=1",
          "method": "GET",
          "connectionTimeOut": 3000,
          "readTimeOut": 3000
        }
      }
    }
  ],
  "outputParameters": {
    "statusCode": "${fetch_report_data_ref.output.response.statusCode}",
    "itemCount": "${fetch_report_data_ref.output.response.body.length()}"
  },
  "timeoutPolicy": "TIME_OUT_WF",
  "timeoutSeconds": 120
}

Register and schedule:

# Register workflow
curl -X PUT 'http://localhost:8080/api/metadata/workflow' \
  -H 'Content-Type: application/json' \
  -d @daily-report-workflow.json

# Create schedule
curl -X POST 'http://localhost:8080/api/scheduler/schedules' \
  -H 'Content-Type: application/json' \
  -d @every-minute-schedule.json

# Watch executions
curl 'http://localhost:8080/api/scheduler/search/executions?freeText=every-minute-demo-schedule&size=10'

Weekday business-hours schedule

Trigger a report workflow at 9 AM Eastern on weekdays only.

{
  "name": "daily-report-schedule",
  "cronExpression": "0 0 9 * * MON-FRI",
  "zoneId": "America/New_York",
  "startWorkflowRequest": {
    "name": "daily_report_workflow",
    "version": 1,
    "correlationId": "daily-report-${scheduledTime}"
  },
  "runCatchupScheduleInstances": false,
  "paused": false
}

The zoneId ensures the schedule respects daylight saving time transitions.


Catch up missed executions after downtime

When the scheduler restarts after being offline, runCatchupScheduleInstances: true fires all missed cron slots. Use this for workflows where every execution matters (billing, compliance, ETL).

{
  "name": "catchup-demo-schedule",
  "cronExpression": "0 * * * * *",
  "zoneId": "UTC",
  "runCatchupScheduleInstances": true,
  "paused": false,
  "startWorkflowRequest": {
    "name": "catchup_demo_workflow",
    "version": 1,
    "input": {}
  }
}

If the scheduler was down for 5 minutes, it will fire 5 workflow executions on restart — one per missed minute.

Warning

Catchup executions fire in rapid succession. Make sure your workflow and downstream systems can handle the burst.


Bounded schedule with a time window

Restrict a schedule to fire only within a time window using scheduleStartTime and scheduleEndTime (epoch milliseconds).

# Compute a 5-minute window starting now
START_MS=$(date +%s000)
END_MS=$(( $(date +%s) + 300 ))000

curl -X POST 'http://localhost:8080/api/scheduler/schedules' \
  -H 'Content-Type: application/json' \
  -d "{
    \"name\": \"bounded-demo-schedule\",
    \"cronExpression\": \"0 * * * * *\",
    \"zoneId\": \"UTC\",
    \"scheduleStartTime\": $START_MS,
    \"scheduleEndTime\": $END_MS,
    \"startWorkflowRequest\": {
      \"name\": \"bounded_demo_workflow\",
      \"version\": 1,
      \"input\": {}
    }
  }"

The schedule fires every minute but only within the 5-minute window, then stops automatically.


Pass input parameters to scheduled workflows

The scheduler automatically injects _scheduledTime and _executedTime into every execution. You can also provide static input that gets merged:

Schedule definition:

{
  "name": "input-param-demo-schedule",
  "cronExpression": "0 * * * * *",
  "zoneId": "UTC",
  "startWorkflowRequest": {
    "name": "input_param_demo_workflow",
    "version": 1,
    "input": {
      "reportOwner": "platform-team",
      "alertThreshold": 100
    }
  }
}

Workflow that uses the injected timestamps to compute a 24-hour report window:

{
  "name": "input_param_demo_workflow",
  "version": 1,
  "schemaVersion": 2,
  "tasks": [
    {
      "name": "compute_report_window",
      "taskReferenceName": "compute_report_window",
      "type": "INLINE",
      "inputParameters": {
        "scheduledTime": "${workflow.input._scheduledTime}",
        "executionTime": "${workflow.input._executedTime}",
        "evaluatorType": "javascript",
        "expression": "function toISO(ms) { return new Date(ms).toISOString(); } ({ reportWindowStart: toISO($.scheduledTime - 86400000), reportWindowEnd: toISO($.scheduledTime), scheduledAt: toISO($.scheduledTime), triggeredAt: toISO($.executionTime) })"
      }
    }
  ],
  "outputParameters": {
    "reportWindowStart": "${compute_report_window.output.result.reportWindowStart}",
    "reportWindowEnd": "${compute_report_window.output.result.reportWindowEnd}",
    "scheduledAt": "${compute_report_window.output.result.scheduledAt}",
    "triggeredAt": "${compute_report_window.output.result.triggeredAt}"
  },
  "timeoutPolicy": "ALERT_ONLY",
  "timeoutSeconds": 30
}

Schedule a parallel (FORK/JOIN) workflow

A scheduled workflow can use any Conductor construct. This example fetches two timezones in parallel using FORK_JOIN:

{
  "name": "multistep_demo_workflow",
  "version": 3,
  "schemaVersion": 2,
  "tasks": [
    {
      "name": "fork_parallel_calls",
      "taskReferenceName": "fork_parallel_calls",
      "type": "FORK_JOIN",
      "forkTasks": [
        [
          {
            "name": "fetch_utc_time",
            "taskReferenceName": "fetch_utc_time",
            "type": "HTTP",
            "inputParameters": {
              "http_request": {
                "uri": "https://timeapi.io/api/time/current/zone?timeZone=UTC",
                "method": "GET"
              }
            }
          }
        ],
        [
          {
            "name": "fetch_ny_time",
            "taskReferenceName": "fetch_ny_time",
            "type": "HTTP",
            "inputParameters": {
              "http_request": {
                "uri": "https://timeapi.io/api/time/current/zone?timeZone=America/New_York",
                "method": "GET"
              }
            }
          }
        ]
      ]
    },
    {
      "name": "join_results",
      "taskReferenceName": "join_results",
      "type": "JOIN",
      "joinOn": ["fetch_utc_time", "fetch_ny_time"]
    }
  ],
  "outputParameters": {
    "utcTime": "${fetch_utc_time.output.response.body.dateTime}",
    "newYorkTime": "${fetch_ny_time.output.response.body.dateTime}"
  },
  "timeoutPolicy": "ALERT_ONLY",
  "timeoutSeconds": 60
}

Schedule it:

{
  "name": "multistep-demo-schedule",
  "cronExpression": "0 * * * * *",
  "zoneId": "UTC",
  "startWorkflowRequest": {
    "name": "multistep_demo_workflow",
    "version": 3
  }
}

Handle concurrent executions

The scheduler fires on every cron tick regardless of whether the previous execution has completed. If a workflow takes 90 seconds and the schedule fires every 60 seconds, executions will overlap:

{
  "name": "concurrent_demo_workflow",
  "version": 1,
  "schemaVersion": 2,
  "tasks": [
    {
      "name": "fetch_start_time",
      "taskReferenceName": "fetch_start_time",
      "type": "HTTP",
      "inputParameters": {
        "http_request": {
          "uri": "https://timeapi.io/api/time/current/zone?timeZone=UTC",
          "method": "GET"
        }
      }
    },
    {
      "name": "wait_90s",
      "taskReferenceName": "wait_90s",
      "type": "WAIT",
      "inputParameters": { "duration": "90s" }
    },
    {
      "name": "fetch_end_time",
      "taskReferenceName": "fetch_end_time",
      "type": "HTTP",
      "inputParameters": {
        "http_request": {
          "uri": "https://timeapi.io/api/time/current/zone?timeZone=UTC",
          "method": "GET"
        }
      }
    }
  ],
  "outputParameters": {
    "startedAt": "${fetch_start_time.output.response.body.dateTime}",
    "finishedAt": "${fetch_end_time.output.response.body.dateTime}"
  },
  "timeoutPolicy": "ALERT_ONLY",
  "timeoutSeconds": 300
}

Design for overlap

If concurrent runs are a problem, either increase the cron interval so it exceeds the workflow duration, or make your workflow idempotent so overlapping runs don't produce duplicate side effects.


Manage a schedule lifecycle

Complete lifecycle in one session — create, verify, pause, resume, delete:

# Create
curl -X POST 'http://localhost:8080/api/scheduler/schedules' \
  -H 'Content-Type: application/json' \
  -d @daily-report-schedule.json

# Preview next 5 execution times
curl 'http://localhost:8080/api/scheduler/nextFewSchedules?cronExpression=0+0+9+*+*+MON-FRI&limit=5'

# Check execution history
curl 'http://localhost:8080/api/scheduler/search/executions?freeText=daily-report-schedule&size=10'

# Pause
curl -X PUT 'http://localhost:8080/api/scheduler/schedules/daily-report-schedule/pause?reason=maintenance'

# Verify paused state
curl 'http://localhost:8080/api/scheduler/schedules/daily-report-schedule'

# Resume
curl -X PUT 'http://localhost:8080/api/scheduler/schedules/daily-report-schedule/resume'

# Delete
curl -X DELETE 'http://localhost:8080/api/scheduler/schedules/daily-report-schedule'