---
title: Schedules
description: The identity rule, the tick states, overlap, catch-up, pausing, and the revision fence a delete carries.
type: explanation
product: jobs
summary: How a Schedule fires, what a fire records, and the fences that keep a delete honest.
updated: 2026-09-28
order: 0
---

Two shapes describe this product, and it is worth knowing which one a fact
comes from. The declared `schedules` collection is the Schedule Resource the
one API documents — the name pattern, the spec, `pause` and `resume`, written
down in [the collection](/docs/api/schedules). Schedules run on the Compute
API today, whose wire is camel case (`identity.revision`, `everySeconds`,
`catchUp`).

## One instant, one Run

`(schedule, intended time)` is the Run identity. A fire names the instant it
was due for, and one instant starts at most one Run — a wake that arrives
twice, as a retry or a duplicate, starts the same instant's Run once. That is
what makes a schedule safe to run on infrastructure that can deliver a wake
more than once: the duplicate has nowhere to land.

A fire is not a Run result. The fire starts the Run; whether the Run
succeeded, failed, or is still going is the Run's own outcome, read from the
Run rather than from the schedule's fire. The two are separate facts because
they answer different questions — the fire says the work was started on time,
and the Run says what the work did.

## What a fire records

A fire is recorded as a tick, and a tick holds one of three states:

<KeyValue
	items={[
		{ key: 'open', value: 'The fire has started and nothing has settled yet.', mono: true },
		{ key: 'succeeded', value: 'A 2xx answer came back from the target.', mono: true },
		{ key: 'dead_lettered', value: 'The target refused, so the fire is recorded as a dead letter.', mono: true },
	]}
/>

A refusal is recorded, not swallowed: a dead letter sits beside the tick that
produced it, so the schedule's own state carries the evidence of what was
refused instead of a gap where a fire should have been. The schedule read
shows both — `lastTick` for the most recent fire, `deadLetters` for the
refusals it collected.

## Overlap: a fire that arrives while the last Run is still going

A rate can come due again while the Run its last fire started is still
running, and `overlap` is the policy for that moment. The Compute API takes
`skip`, `buffer_one` and `allow`; the declared collection lists a wider set
(`skip`, `buffer_one`, `buffer_all`, `cancel_other`, `allow`).

The question the policy answers is about the handler, not the schedule.
`allow` starts the new Run regardless, which is right for a handler that is
safe to run twice at once. `skip` starts nothing while the previous Run is
still open, which is the safe default for a handler that is not. `buffer_one`
holds one fire back rather than dropping it outright.

## Catch-up and the missed-fire window

A fire that nobody delivered — the schedule was paused, or the platform was
not there to fire it — is a missed fire, and the catch-up policy decides
whether it is still worth starting. Starting an old fire late is only useful
inside a window: a window bounds how stale a missed fire may be and still
start.

On the declared collection that window is `catchup_window`, a duration from 10
seconds to 365 days, and a missed fire older than it is skipped. On the
Compute API the field is `catchUp`, where `skip` drops the missed fires and
the bounded form states how many, or how old, a missed fire may be and still
start.

Catching up is the expensive direction: a schedule that was down for a while
starts every missed fire the window kept, so a window that is larger than the
work justifies turns a short outage into a burst.

## Pausing

A Schedule carries `pause`, and a paused Schedule starts no Run until it is
resumed. The declared collection spells that as `pause` and `resume` calls,
with an optional `pause_note` that says why the Schedule is paused; `resume`
follows the catch-up window for whatever was missed while it was paused, which
is the same rule that governs a missed fire any other way.

Pausing is a state of the schedule rather than a change to it, which is what
makes it the right tool for a stop that is meant to end: a paused schedule
keeps its calendar, its spec and its revisions, and nothing else about it
changes while it waits.

## Revisions, and the delete fence

A schedule is edited in place, and every change moves its
`identity.revision`. A delete states which revision it is deleting —
`expectedRevision`, taken from the last read — and that is a fence rather than
a formality: a tick between your read and your delete moves the revision, and
the delete is refused with a conflict instead of removing a schedule you did
not read. Read it again, and delete with the revision you just read.

The same call carries an `idempotencyKey`, so a delete that is retried is one
delete and not two.

<RelatedDocs
	links={[
		{
			href: '/docs/jobs/targets',
			label: 'HTTP targets',
			description: 'Where a fire lands, and what the receiver answers.',
		},
		{
			href: '/docs/jobs/quickstart',
			label: 'Quickstart',
			description: 'Create a schedule, read it back and delete it.',
		},
		{
			href: '/docs/api/schedules',
			label: 'The schedules collection',
			description: 'The Schedule Resource as declared, with every method and example.',
		},
	]}
/>
