# Schedules

> Start runs of a workflow on a recurrence — daily, weekly, monthly or yearly at a set time — with a run-name pattern, preset field values and per-task assignees; pause, resume, run now and preview.

Source: https://developers.processcart.com/guides/schedules

Operations: [Schedules endpoints](https://developers.processcart.com/reference/schedules) · scope `schedules:manage`.

A schedule starts runs of one published workflow on a recurrence. Each run gets a name from the schedule's pattern, the schedule's preset field values, and — for the tasks you name — the schedule's assignees instead of the task's own assignment rule. Schedules are managed in the app (Automations → Schedules, Builders and Admins) and through the API with the `schedules:manage` scope.

| Operation | HTTP | SDK method |
| --- | --- | --- |
| [List schedules](https://developers.processcart.com/reference/schedules#listSchedules) | <code class="pc-docs-wrap">GET /v1<wbr>/schedules</code> | `listSchedules(…)` |
| [Create a schedule](https://developers.processcart.com/reference/schedules#createSchedule) | <code class="pc-docs-wrap">POST /v1<wbr>/schedules</code> | `createSchedule(…)` |
| [Get a schedule](https://developers.processcart.com/reference/schedules#getSchedule) | <code class="pc-docs-wrap">GET /v1<wbr>/schedules<wbr>/{id}</code> | `getSchedule(…)` |
| [Update a schedule](https://developers.processcart.com/reference/schedules#updateSchedule) | <code class="pc-docs-wrap">PATCH /v1<wbr>/schedules<wbr>/{id}</code> | `updateSchedule(…)` |
| [Delete a schedule](https://developers.processcart.com/reference/schedules#deleteSchedule) | <code class="pc-docs-wrap">DELETE /v1<wbr>/schedules<wbr>/{id}</code> | `deleteSchedule(…)` |
| [Pause a schedule](https://developers.processcart.com/reference/schedules#pauseSchedule) | <code class="pc-docs-wrap">POST /v1<wbr>/schedules<wbr>/{id}<wbr>/pause</code> | `pauseSchedule(…)` |
| [Resume a schedule](https://developers.processcart.com/reference/schedules#resumeSchedule) | <code class="pc-docs-wrap">POST /v1<wbr>/schedules<wbr>/{id}<wbr>/resume</code> | `resumeSchedule(…)` |
| [Start a run from a schedule now](https://developers.processcart.com/reference/schedules#runScheduleNow) | <code class="pc-docs-wrap">POST /v1<wbr>/schedules<wbr>/{id}<wbr>/run</code> | `runScheduleNow(…)` |
| [Preview a schedule's next occurrences](https://developers.processcart.com/reference/schedules#previewSchedule) | <code class="pc-docs-wrap">GET /v1<wbr>/schedules<wbr>/{id}<wbr>/preview</code> | `previewSchedule(…)` |

## Create a schedule

```bash
curl -X POST https://app.processcart.com/api/v1/schedules \
  -H "Authorization: Bearer pc_live_…" \
  -H "Content-Type: application/json" \
  -d '{ "workflowId": "3c1d…", "name": "Weekly order review",
        "recurrence": { "freq": "weekly", "interval": 1, "time": "09:00",
                        "startDate": "2026-10-12", "weekly": { "days": ["mon"] } },
        "namePattern": "{{workflow}} — {{date:YYYY-MM-DD}}",
        "presetValues": { "customer": "Acme Corp", "amount": 250 },
        "assignmentOverrides": { "review": [{ "kind": "group", "groupId": "7f0e…" }] } }'
```

```typescript
const schedule = await client.createSchedule({
  workflowId: "3c1d…",
  name: "Weekly order review",
  recurrence: {
    freq: "weekly",
    interval: 1,
    time: "09:00",
    startDate: "2026-10-12",
    weekly: { days: ["mon"] },
  },
  presetValues: { customer: "Acme Corp", amount: 250 },
});
console.log(schedule.description, schedule.nextOccurrences);
```

The answer is the schedule (201, with an absolute `Location`): its recurrence in words (`description`), `nextRunAt` and the next three occurrences. A recurrence with no future occurrence is refused (422).

- **`timezone`** is an IANA zone such as `America/New_York`; leave it out to use the workspace's zone.
- **`presetValues`** are canonical values keyed by field key, checked against the published version like `POST /v1/runs` fields ([Field values & typing](https://developers.processcart.com/guides/field-values)). A preset its field's format rules refuse — outside a minimum or maximum, or failing a pattern — is refused when the schedule is saved (422, `errors[].code` `invalid_value` at `presetValues.<key>`), so a schedule never starts runs with it. Every required kickoff field needs a preset (422, `errors[].code` `required`; a required checkbox needs `true`) — except a File field, a field the API cannot write (a formula or other computed field), and a field whose default value fills it.
- **`assignmentOverrides`** maps a task key to the members and groups that get that task in every run — `{ "kind": "user", "userId" }` or `{ "kind": "group", "groupId" }` (1–20 per task), the same spelling the Run resource uses for those two kinds. Only these two kinds are accepted: a Run's `role` or `guest` assignee cannot be an override, and an entry carrying any other member is refused (422). An override replaces the task's own assignment rule for those runs; an override whose members have all left the workspace, or whose group was deleted, falls back to that rule.
- A schedule stays on its workflow; create another schedule for a different one. The workflow must be published, not archived and not in Trash.

## Recurrence

| Member                  | Meaning                                                                                                                                                                                                                                                                                                                                          |
| ----------------------- | ------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------ |
| `freq`                  | `daily`, `weekly`, `monthly` or `yearly`.                                                                                                                                                                                                                                                                                                        |
| `interval`              | Every N days (1–365), weeks (1–52), months (1–24) or years (1–10), counted from `startDate`.                                                                                                                                                                                                                                                     |
| `time`                  | `HH:mm`, 24-hour, minutes on 5-minute steps (`00`, `05`, …, `55`), in the schedule's zone.                                                                                                                                                                                                                                                       |
| `startDate` / `endDate` | The first and last local dates an occurrence may fall on (`endDate` inclusive; omit it or send `null` for no end).                                                                                                                                                                                                                               |
| `weekly`                | `{ "days": ["mon", "wed", "fri"] }` — weeks start on Monday; the week holding `startDate` is the first.                                                                                                                                                                                                                                          |
| `monthly`               | `{ "kind": "day", "day": 31 }` (a day past the end of a shorter month runs on its last day) or `{ "kind": "ordinal", "ordinal": "last", "target": "weekday" }` — `ordinal` is `first`, `second`, `third`, `fourth` or `last`; `target` is a weekday (`mon` … `sun`), `day`, `weekday` or `weekend_day` (“the last weekday”, “the first Monday”). |
| `yearly`                | `{ "month": 3, "day": 15 }`; February 29 runs on February 28 in other years.                                                                                                                                                                                                                                                                     |

Only the member that matches `freq` may be present. `GET /v1/schedules/{id}/preview?count=5` answers the next occurrences (1–10, default 3) as UTC instants; for a paused schedule, the ones a resume would produce.

## When runs are created

- Runs are created at the scheduled time, usually within a minute: the scheduler checks for due schedules every 5 minutes, and schedule times sit on 5-minute steps.
- **Daylight saving time.** A time that does not exist on a spring-forward day (02:30 when clocks jump from 02:00 to 03:00) runs at the same offset into the new hour (03:30). A time that happens twice on a fall-back day runs once, at the first.
- **Catch-up.** If runs could not be created while the schedule was on (an outage), the next check creates ONE run, for the latest missed occurrence, and records how many it skipped in `lastError`. It never creates one run per missed occurrence.
- **Resuming never catches up.** Occurrences that fall while a schedule is paused are skipped; after a resume, the next run is the first occurrence after now.
- **Time zones.** The schedule's zone decides when a run is created and what `{{date}}` and `{{time}}` read. Inside the run, `TODAY()`, date-field defaults and due rules use the workspace's zone, as for any run.
- **Outages.** A start that fails for a passing reason — the database did not answer, or the start failed unexpectedly — is not a refusal: `lastError` records it, the next check retries it, and the schedule is never paused for it. **Run now** answers 503 `unavailable` with `Retry-After` instead (500 `internal_error` for an unexpected failure). Any other error while creating a run counts toward a pause: the third in a row pauses the schedule (`failed`).
- **Which server fires it.** A schedule fires only on the kind of server (production, preview or local) that created it or last edited or resumed it. When an edit or a resume moves it to another kind, its next run is recomputed from now, so the new server never creates a catch-up run for occurrences the old one missed.

## Run names

`namePattern` (default `{{workflow}} — {{date}}`) takes these tokens; anything else is kept literally, and the name is cut to 200 characters.

| Token                | Renders                                                                                                                     |
| -------------------- | --------------------------------------------------------------------------------------------------------------------------- |
| `{{workflow}}`       | The workflow's name.                                                                                                        |
| `{{date}}`           | The occurrence's local date, `MMM D, YYYY`.                                                                                 |
| `{{date:<pattern>}}` | The date in a Date-field pattern: `YYYY-MM-DD`, `MM/DD/YYYY`, `DD/MM/YYYY`, `DD-MMM-YYYY`, `MMM D, YYYY` or `MMMM D, YYYY`. |
| `{{time}}`           | The occurrence's local time, `HH:mm`.                                                                                       |

## Runs a schedule creates

Every such run has `startedBy: "schedule"` and `schedule: { id, name }` ([Runs](https://developers.processcart.com/guides/runs)). `scheduledFor` is the occurrence it was created for; it is `null` for a run started with **Run now**. Each occurrence creates at most one run. After a schedule is deleted its runs stay, and their `schedule` reads `null`. Subscribe to `schedule.run_created` to hear about them ([Webhooks](https://developers.processcart.com/webhooks)).

```bash
curl -X POST https://app.processcart.com/api/v1/schedules/5b2a…/run \
  -H "Authorization: Bearer pc_live_…"
```

**Run now** (`POST /v1/schedules/{id}/run`) starts one run right away — paused or not — with the schedule's pattern (rendered at the start instant), presets and overrides. It answers the new Run (201, its `revision` in the body) and an absolute `Location`. Presets the published version no longer accepts are skipped (the schedule's `presetMismatches` names them); a required kickoff field without a preset is refused with 422 `required_fields_unsatisfied`.

## Pause, resume, update, delete

- `POST /v1/schedules/{id}/pause` and `/resume` answer the schedule (200), unchanged when it already was. Resuming a schedule whose end date has passed is 409 `schedule_ended` — change its recurrence first. Creating, resuming or running a schedule now — or a `PATCH` that changes its `presetValues` or `assignmentOverrides` — on an unpublished workflow is 409 `workflow_not_published`, on one in Trash 409 `workflow_trashed`, on an archived one 403 `workflow_not_runnable`.
- `PATCH /v1/schedules/{id}` changes any of `name`, `recurrence`, `timezone`, `namePattern`, `presetValues` and `assignmentOverrides`; a new recurrence or zone recomputes the next run from now. `enabled` is refused (422, `errors[].code` `unknown_key` on `enabled`) — pause and resume own it.
- `DELETE /v1/schedules/{id}` answers 204.
- `GET /v1/schedules` lists schedules newest first; filter by `workflowId` or `enabled`, and use `updatedSince` for a sync ([Incremental sync](https://developers.processcart.com/guides/incremental-sync)).

Schedules carry no `revision`: no schedule operation reads `Expected-Revision` (the SDK's `expectedRevision` option is ignored here), and an update applies to the schedule as it is stored, so the last write wins. Read the schedule again before an update that depends on what another client may have changed.

Every change is recorded in the workspace's audit log under the key's name.

### Why a schedule is paused

`disabledReason` says why `enabled` is `false`:

| Reason              | Meaning                                                                                                                                                                                                    |
| ------------------- | ---------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- |
| `manual`            | Paused by a person or a key.                                                                                                                                                                               |
| `ended`             | The recurrence's end date passed, or it has no occurrence left to schedule (`lastError` then reads “This schedule has no future runs.”).                                                                   |
| `unpublished`       | The workflow has no published version.                                                                                                                                                                     |
| `failed`            | A start was refused (a preset or required field problem), three attempts in a row to create a run failed for a reason other than an outage, or the recurrence can no longer be read; `lastError` says why. |
| `workflow_trashed`  | The workflow moved to Trash. Restoring it does not resume the schedule.                                                                                                                                    |
| `workflow_archived` | The workflow was archived. Unarchiving it does not resume the schedule.                                                                                                                                    |

When the scheduler itself pauses a schedule as `failed` or `unpublished`, the workspace's Builders and Admins are notified. Moving the workflow to Trash or archiving it pauses its schedules at once, without a notification.

## Allowlists

A key restricted to workflows or folders lists only the schedules of workflows inside its allowlist; any other schedule answers 403 `not_allowed_for_key`, and so does creating one for a workflow outside it.
