# Pause, resume, cancel & reschedule

> Manage a schedule after creation — pause and resume delivery, cancel permanently, or move the fire time. With curl and response examples for each call.

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.

These calls change *timing and delivery*, not the schedule's target or retry policy. To
edit the endpoint, method, TTL, retry policy, or metadata, use `PATCH /v1/schedules/{id}`
— but note **PATCH never moves the fire time** (see [Reschedule](#reschedule-move-the-fire-time)).

## Pause

`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.

```bash
curl -sS -X POST https://api.schedstack.com/v1/schedules/sch_01J9.../pause \
  -H "Authorization: Bearer sk_test_…"
```

```json
{
  "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.

## 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.

```bash
curl -sS -X POST https://api.schedstack.com/v1/schedules/sch_01J9.../resume \
  -H "Authorization: Bearer sk_test_…"
```

```json
{
  "id": "sch_01J9...",
  "object": "schedule",
  "state": "active",
  "kind": "recurring",
  "next_fire_at": "2026-07-01T13:00:00Z",
  "updated_at": "2026-06-27T18:09:55Z"
}
```

A pending occurrence whose `scheduled_for` already passed while the schedule was paused
fires as soon as you resume — pausing holds work, it doesn't discard it. This only holds if
its TTL deadline hasn't also passed: an occurrence whose `ttl` window elapsed while paused
finalizes as `expired` on resume instead of firing. If you want missed occurrences to stay
missed, cancel and create a new schedule instead.

## Cancel

`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`.

```bash
curl -sS -X POST https://api.schedstack.com/v1/schedules/sch_01J9.../cancel \
  -H "Authorization: Bearer sk_test_…"
```

```json
{
  "id": "sch_01J9...",
  "object": "schedule",
  "state": "canceled",
  "updated_at": "2026-06-27T18:12:30Z"
}
```

There is no un-cancel. `resume` only works on a `paused` schedule, so it won't revive a
canceled one. If you need it back, create a new schedule.

## Reschedule (move the fire time)

`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.

```bash
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" }'
```

```json
{
  "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](/docs/guides/idempotency-keys/).

### PATCH does not move timing

`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.

```bash
# 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" }
  }'
```

## A note on idempotent transitions

`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).

Because these endpoints don't reject no-op transitions, treat the **`state` in the
response** as the source of truth rather than assuming the transition happened. Don't pause
twice and infer anything from the second call — read `state`.

## Inspect a schedule and its deliveries

Read the current state at any time:

```bash
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](/docs/conventions/)):

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

```json
{
  "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.

## Related

- [One-shot scheduling](/docs/guides/one-shot/) — create a single durable delivery.
- [Recurring schedules & DST](/docs/guides/recurring/) — `cron` + timezone recurrence,
  which `reschedule` can also replace.
- [Retries & dead-letter](/docs/concepts/retries-and-dead-letter/) — what happens to a
  delivery's attempts independent of the schedule's lifecycle.
- [Dead-letter & replay](/docs/guides/dead-letter-and-replay/) — re-fire a delivery that
  ran out of attempts.
- [Idempotency keys](/docs/guides/idempotency-keys/) — make `reschedule` retries safe.
