Schedule triggers: cron with a time zone
Run a workflow on a cron schedule bound to an IANA zone. What happens on the DST switch days, after downtime, and how the firings log explains a quiet night.
Last updated:
Wall-clock time, meant literally
A schedule is a cron expression plus an IANA zone — Europe/Berlin, never a UTC offset, because an offset is wrong for half the year. Wall-clock semantics are taken literally on the days that test them. On the night the clocks go back, a daily job fires once, not twice: on a portal permitting one session per login, a second sign-in would evict the first. On the night they go forward, a job scheduled into the missing hour fires the moment the gap ends rather than not at all.
Downtime follows the same discipline. However long the platform was down, there is exactly one catch-up decision — catchUp either skips the missed occurrences or runs one catch-up firing. Never a burst of back-to-back runs.
Create a nightly trigger
Requires: api-key
BASE="https://browserberg.com"
AUTH="Authorization: Bearer $BROWSERBERG_API_KEY"
JSON="Content-Type: application/json"
WFID=$(curl -s -X POST "$BASE/v1/workflows" -H "$AUTH" -H "$JSON" -d '{
"title": "Nightly example check",
"blocks": [ { "blockType": "navigation", "label": "Open_page", "url": "https://example.com" } ]
}' | python3 -c 'import json,sys; print(json.load(sys.stdin)["workflow"]["workflowId"])')
# A schedule is a cron expression plus an IANA zone -- never a UTC offset.
# Wall-clock time is meant literally, including on the DST switch days.
TRIGGER=$(curl -s -X POST "$BASE/v1/workflows/$WFID/triggers" -H "$AUTH" -H "$JSON" -d '{
"kind": "schedule",
"name": "Nightly at 02:30 Berlin time",
"cron": "30 2 * * *",
"timezone": "Europe/Berlin",
"catchUp": "run_once",
"session": { "ttlSeconds": 900 }
}')
echo "$TRIGGER" | python3 -c 'import json,sys; t = json.load(sys.stdin)["trigger"]; print(json.dumps({
"id": t["id"], "status": t["status"], "nextFireAt": t["nextFireAt"],
"catchUp": t["catchUp"], "overlap": t["overlap"]}, indent=2))'
TID=$(echo "$TRIGGER" | python3 -c 'import json,sys; print(json.load(sys.stdin)["trigger"]["id"])')
curl -s -X DELETE "$BASE/v1/triggers/$TID" -H "$AUTH" > /dev/null
{
"id": "trg_xxxxxxxx",
"status": "active",
"nextFireAt": "2026-01-01T00:00:00.000Z",
"catchUp": "run_xxxxxxxx",
"overlap": "skip"
}
Schedule options
The minimum interval between occurrences is 5 minutes.
| Name | Type | Description |
|---|---|---|
cron
required
|
string | Standard five-field cron expression. |
timezone
required
|
string | An IANA zone name such as Europe/Berlin. Offsets are rejected. |
catchUp
|
skip | run_once | skip drops occurrences missed during downtime; run_once fires a single catch-up. |
misfireGraceSeconds
|
integer |
How late an occurrence may still fire and count as on time (60 to 21600).
Default: 300
|
overlap
|
skip | allow | skip refuses a new firing while the previous run is still going; allow starts it anyway. |
session
|
object | The session each firing gets: poolId, profileId, locale, timezone, ttlSeconds. |
version
|
integer | Pins the workflow version to run; without it, each firing runs the latest. |
The firings log answers the morning question
GET /v1/triggers/:id/firings records every occasion the trigger came due — including the ones where nothing ran, with the outcome started, skipped or refused and the reason. When you arrive at your desk asking why nothing ran last night, the log is the answer, not a shrug.
A failed run does not pause a trigger; failure is what retries and the next occurrence are for. Only a fault that can never fix itself on its own — a deleted workflow, a revoked credential — pauses one.