Recipe - workflow with a cron trigger
Goal: a workflow that runs every day at 02:30 UTC. Fields and rules are in the Workflow reference and the Trigger reference.
Three things must all be on before anything runs: the trigger is active, it has a workflow, and the workflow is on in the environment (devActive for development, on by default; active for production, off by default). Create them in this order so the schedule never starts a half-built workflow.
1. Create the workflow
Call save_workflow:
{
"projectId": "<project id>",
"name": "Nightly report",
"description": "Builds the daily report",
"timeoutSeconds": 600
}It answers { "id": "<workflow id>" }. The default timeout is 300 seconds. The most you can set is 3600.
2. Build its canvas
Call get_canvas:
{ "target": { "kind": "workflow", "id": "<workflow id>" } }A new workflow has an entrypoint and an error_handler and no edges. Note the entrypoint id and the revision.
Call edit_canvas:
{
"target": { "kind": "workflow", "id": "<workflow id>" },
"revision": 0,
"validate": true,
"ops": [
{ "op": "add_block", "ref": "report", "type": "jsrunner",
"data": { "value": "return { ranAt: new Date().toISOString() };" },
"connect_from": { "from": "entrypoint_1" } },
{ "op": "add_block", "ref": "log", "type": "consolelog",
"data": { "message": "Nightly report ran", "level": "info" },
"connect_from": { "from": "report" } }
]
}A workflow has no caller, so it needs no response block. For the shape of other blocks use get_block_schemas with blockTypes.
3. Switch the workflow on
Call save_workflow:
{ "workflowId": "<workflow id>", "active": true }A workflow that is off in an environment is not given to that environment's workers, so no trigger can start it there. This step switches production on; development is on already.
4. Create the schedule trigger
Call save_trigger:
{
"projectId": "<project id>",
"name": "Nightly report at 02:30",
"type": "schedule",
"schedule": "0 30 2 * * *",
"timezone": "UTC",
"workflowId": "<workflow id>",
"active": true
}It answers { "id": "<trigger id>", "warnings": [] }.
The schedule has six fields: seconds, minutes, hours, day of month, month, day of week. 0 30 2 * * * is second 0, minute 30, hour 2, every day. Five fields are refused.
| Want | Write |
|---|---|
| Every 15 minutes | 0 */15 * * * * or @every 15m |
| Weekdays at 09:00 | 0 0 9 * * 1-5 |
| Every day at midnight | 0 0 0 * * * |
| Once | @at 2026-12-31T23:00:00Z |
Runs are spread over the minute unless you name the second. @daily can run at 00:00:37. Use UTC for a run that must happen exactly once a day, since a timezone with daylight saving can skip or repeat a run twice a year.
5. Check it
Call list_triggers:
{ "projectId": "<project id>", "workflowId": "<workflow id>" }Expect active: true, the workflow listed, and disabledReason: null.
6. Watch the first run
To avoid waiting until 02:30, point the trigger at a short interval and record the runs:
Call save_workflow:
{ "workflowId": "<workflow id>", "recordExecution": true }Call save_trigger:
{ "triggerId": "<trigger id>", "schedule": "@every 1m" }After two minutes:
Call list_recordings:
{ "projectId": "<project id>", "kind": "workflow", "targetId": "<workflow id>" }Read a run with get_recording (see Recipe, debug and fix a route or workflow). When it works, put the real schedule back and turn recording off:
Call save_trigger:
{ "triggerId": "<trigger id>", "schedule": "0 30 2 * * *" }Call save_workflow:
{ "workflowId": "<workflow id>", "recordExecution": false }7. Test the workflow itself
A test suite runs the workflow directly, with an input you choose. The schedule is not used. See Recipe, write and run a test suite.
Good to know
- A schedule is one run each time, never a batch. Set
payloadon the trigger to hand every run the same input. Otherwise the run has none. - A run that fails is retried a few times. Make the work safe to run twice.
- After downtime, missed runs are dropped, except
@at. - To pause the schedule use
save_triggerwithactive: false. Turning it on again resumes from the next scheduled time. delete_triggerremoves the schedule. The workflow stays.- Two workflows on the same clock need two triggers.
Common problems
| What you see | What to do |
|---|---|
Invalid input: schedule: A cron expression needs six fields... | Add the leading seconds field. |
Invalid input: timezone: Use an IANA timezone name... | Use a name such as Asia/Kolkata, not +05:30. |
Fluxify API error 409: trigger with that name already exists | Pick another trigger name. |
| Nothing runs | Trigger off, no workflow attached, or the workflow is switched off in that environment. Check list_triggers and get_workflow. |
disabledReason is set | The system switched the trigger off. Fix the cause, then save with active: true. |
| Runs fail at once | Look at get_system_logs for compile errors, then at the recording. |
