Skip to content

Pause, resume, cancel & reschedule

Once a schedule exists you control it with four side-effectful calls. Each one is a POST to a sub-resource of the schedule and returns the updated schedule so you can read the new state from the response.

pause cancel
┌────────┐ ──────────► ┌────────┐ ──────────► ┌──────────┐
│ active │ │ paused │ │ canceled │ (terminal)
└────────┘ ◄────────── └────────┘ ──────────► └──────────┘
resume cancel

A newly created schedule starts in state: "active". The states are active, paused, and canceled — that’s the whole machine. Cancel is valid from both active and paused — the diagram shows the cancel-from-paused path, but you can cancel an active schedule directly too.

POST /v1/schedules/{id}/pause moves an active schedule to paused. Its pending deliveries become non-claimable, so nothing fires while it’s paused. Any node already holding an occurrence is notified to drop it.

Terminal window
curl -sS -X POST https://api.schedstack.com/v1/schedules/sch_01J9.../pause \
-H "Authorization: Bearer sk_test_…"
{
"id": "sch_01J9...",
"object": "schedule",
"mode": "test",
"kind": "recurring",
"state": "paused",
"endpoint": "https://example.com/hooks/billing",
"method": "POST",
"cron": "0 9 * * *",
"timezone": "America/New_York",
"next_fire_at": "2026-07-01T13:00:00Z",
"next_runs": ["2026-07-01T13:00:00Z", "2026-07-02T13:00:00Z"],
"updated_at": "2026-06-27T18:04:11Z"
}

Pause is durable: a recurring schedule stops minting new occurrences and an undelivered one-shot holds its slot until you resume.

POST /v1/schedules/{id}/resume moves a paused schedule back to active. Deliveries that were paused become claimable again and fire at their scheduled instant.

Terminal window
curl -sS -X POST https://api.schedstack.com/v1/schedules/sch_01J9.../resume \
-H "Authorization: Bearer sk_test_…"
{
"id": "sch_01J9...",
"object": "schedule",
"state": "active",
"kind": "recurring",
"next_fire_at": "2026-07-01T13:00:00Z",
"updated_at": "2026-06-27T18:09:55Z"
}

POST /v1/schedules/{id}/cancel is terminal. The schedule moves to canceled, every pending delivery is finalized as canceled (it shows up in the schedule’s delivery history with that status), and no future occurrence is ever minted. A delivery that has already landed a 2xx finalizes as succeeded (success wins); all other pending deliveries finalize as canceled.

Terminal window
curl -sS -X POST https://api.schedstack.com/v1/schedules/sch_01J9.../cancel \
-H "Authorization: Bearer sk_test_…"
{
"id": "sch_01J9...",
"object": "schedule",
"state": "canceled",
"updated_at": "2026-06-27T18:12:30Z"
}

POST /v1/schedules/{id}/reschedule is the only way to change when a schedule fires. The body carries exactly one timing source — the same fields you used at creation:

Field Use
delay Fire after a relative duration from now, e.g. "30m", "24h". Minimum 1s.
fire_at An absolute RFC 3339 instant, e.g. "2026-07-01T09:00:00Z".
local_fire_at + timezone A wall-clock time in an IANA zone, e.g. "2026-07-01T09:00:00" + "America/New_York" (DST-correct).
cron + timezone For a recurring schedule: replace the recurrence and re-time the next run.

Provide exactly one of delay / fire_at / local_fire_at / cron. Sending zero or more than one returns a 400/422 with a missing_timing or multiple_timing error.

Terminal window
curl -sS -X POST https://api.schedstack.com/v1/schedules/sch_01J9.../reschedule \
-H "Authorization: Bearer sk_test_…" \
-H "Content-Type: application/json" \
-d '{ "fire_at": "2026-07-01T09:00:00Z" }'
{
"id": "sch_01J9...",
"object": "schedule",
"kind": "one_shot",
"state": "active",
"fire_at": "2026-07-01T09:00:00Z",
"next_runs": ["2026-07-01T09:00:00Z"],
"updated_at": "2026-06-27T18:20:02Z"
}

Reschedule re-times the pending delivery in place: its scheduled_for moves to the new instant and its TTL deadline shifts by the same delta, so the time-to-live you set is preserved. A node already holding the old occurrence is told to drop and re-claim it at the new time.

reschedule accepts an Idempotency-Key header, so a network retry of the same reschedule is safe. See Idempotency keys.

PATCH /v1/schedules/{id} updates the target (endpoint, method), policy (ttl, retry_policy), and metadata — and nothing else. Timing fields are not part of a PATCH; sending fire_at or delay in a PATCH body does not move the fire time. Use reschedule for that. This split keeps “edit the config” and “move the clock” as separate, explicit operations.

Terminal window
# Update target + retries (timing untouched)
curl -sS -X PATCH https://api.schedstack.com/v1/schedules/sch_01J9... \
-H "Authorization: Bearer sk_test_…" \
-H "Content-Type: application/json" \
-d '{
"endpoint": "https://example.com/hooks/billing-v2",
"retry_policy": { "max_attempts": 5 },
"metadata": { "owner": "billing" }
}'

pause, resume, and cancel are forgiving: they return 200 with the current schedule even when the transition is a no-op. Pausing an already-paused schedule, or pausing a canceled one, doesn’t error — it just returns the schedule unchanged. The call only fails with 404 not_found when the schedule ID doesn’t exist in your (project, mode).

Read the current state at any time:

Terminal window
curl -sS https://api.schedstack.com/v1/schedules/sch_01J9... \
-H "Authorization: Bearer sk_test_…"

List the deliveries this schedule has produced — pending and historical — newest first (cursor-paginated, see API conventions):

Terminal window
curl -sS https://api.schedstack.com/v1/schedules/sch_01J9.../deliveries \
-H "Authorization: Bearer sk_test_…"
{
"object": "list",
"data": [
{
"id": "dlv_01J9...",
"object": "delivery",
"schedule_id": "sch_01J9...",
"status": "canceled",
"scheduled_for": "2026-07-01T13:00:00Z",
"attempt_count": 0
}
],
"has_more": false,
"next_cursor": null
}

This is how you confirm a cancel took effect (the occurrence shows status: "canceled") or check whether a paused schedule’s next occurrence is still pending.