Table of Contents
API : Tools: Scheduled jobs : Recurrence and run handling
Recurrence
A job without a recurrence runs once and is then archived. A job with one repeats until it is deleted or paused.
| Field | Type | Description |
|---|---|---|
| interval | String | Cadence: MINUTE, HOURLY, DAILY, WEEKLY, MONTHLY_FIRST, MONTHLY_LAST |
| atMinute | Number | Minute past the hour, 0-59. Ignored for MINUTE. |
| atHour | Number | Hour of day, 0-23. Required for DAILY, WEEKLY, MONTHLY_FIRST and MONTHLY_LAST. |
| weekdays | Array of Number | Days of week the job runs on, 0 = Sunday .. 6 = Saturday, at least one entry. Required for WEEKLY. Deduplicated and sorted on save. Jobs stored before 2026-08 may carry a legacy single weekday Number instead, which is still honoured. |
| enabled | Boolean | When false the job stays in the list but is never picked up (paused). |
Timezone
atHour and atMinute are wall-clock times in Europe/Copenhagen, so a job scheduled for 03:05 stays at 03:05 all year and the stored when shifts by an hour across daylight saving.
- A time that does not exist on the spring transition (02:00-02:59 on the last Sunday in March) runs at 03:00.
- A time that occurs twice on the autumn transition (last Sunday in October) runs on the first pass only.
MINUTE and HOURLY are computed in UTC, which is exact because Copenhagen is always a whole number of hours from UTC.
Missed occurrences
An occurrence missed while the scheduler was down is skipped, never replayed. When the scheduler comes back the job runs once and then jumps to the next occurrence after now. A job that was due 60 times during an hour of downtime therefore fires once, not 60 times.
What happens after a run
| Outcome | One-shot job | Recurring job |
|---|---|---|
| Success | Run archived to ScheduledJobsCompleted, job deleted | Run archived, when advanced to the next occurrence, runCount incremented, failure counter reset. The job is never deleted. |
| Transient error (no response, 5xx, 408, 425, 429) | Failure counter incremented, when pushed out by an exponential backoff (30s doubling, capped at 900s) | Same, except the backoff can never push past the next occurrence |
| Irrecoverable error (any other answered status) | Run archived to ScheduledJobsFailed, job deleted | Run archived, when advanced to the next occurrence. The schedule is never deleted. |
| Retries exhausted (5 consecutive failures) | Run archived to ScheduledJobsFailed, job deleted | Run archived and the schedule is paused (recurrence.enabled = false), so a broken schedule stops rather than failing forever |
failed counts consecutive failures and is reset to 0 on the next success. Resuming a paused job clears it.
Run records
Every attempt writes its own record to ScheduledJobsCompleted or ScheduledJobsFailed with a new _id and a sourceJobId pointing back at the job it came from, so a recurring job builds a history instead of overwriting a single document.
| Field | Type | Description |
|---|---|---|
| _id | Id | Unique id of this run |
| sourceJobId | Id | _id of the ScheduledJobs document this run came from |
| recurring | Boolean | Was this a run of a recurring schedule? |
| startedAt | Date | When the attempt started |
| completedAt | Date | When the attempt finished - the sort key for “newest first” |
| durationMs | Number | How long the attempt took |
| httpStatus | Number | Status the server answered with, when it answered at all |
| expiresAt | Date | When the record may be removed by the TTL monitor |
Retention: successful runs are kept for 30 days and failed runs for 90 days, enforced by a TTL index on expiresAt. Records archived before this field existed carry no expiresAt and are kept indefinitely.
Concurrency
- Two jobs sharing an accountId are never run concurrently, so a job that depends on an earlier one for the same account cannot overtake it. Jobs with no accountId are not serialised.
- A job being run carries runningSince; it cannot be started again until that is cleared. A claim older than the job's timeout plus a minute is treated as stale and taken over.
- Each request has a timeout, timeoutMs, defaulting to 120000 ms.
