---
title: "Update a schedule"
method: PATCH
path: "/api/v1/schedules/{id}"
tags: ["Schedules"]
---

# Update a schedule

`PATCH /api/v1/schedules/{id}`

## Request

- Base URL: `https://volunteerreminder.com`
- URL: `https://volunteerreminder.com/api/v1/schedules/{id}`
- Auth: HTTP bearer

Partial update — only send the fields you want to change. Unset fields keep their current value. Recomputes and re-materializes future occurrences when recurrence fields change. An edit that would remove upcoming shifts saves nothing and answers 409 `confirm_remove_required` with the `removed` days and a `confirm_token`; once the user agrees, resend the same edit with `confirm_remove: true` and that `confirm_token`. Does NOT change the roster: use `POST /schedules/{id}/roster` for that. Requires the `schedules:write` scope.

## Path parameters

- `id` string, required: The schedule's numeric id.

## Request body

- ScheduleUpdateRequest
  - `description` string: Short label shown on the schedule list and in reminder messages, e.g. "Sunday morning greeters".
  - `job_name` string: The role/task volunteers are reminded about. Matched case-insensitively against this account's existing jobs and created if new.
  - `group_id` integer, nullable: Id of an existing volunteer group to attach, or null. An id not owned by this account is silently ignored (treated as null).
  - `schedule_type` 'weekly' | 'biweekly' | 'monthly_fixed' | 'monthly_nth' | 'bimonthly_fixed' | 'bimonthly_nth' | 'custom': Recurrence shape. 'weekly' recurs on one or more days of the week; 'biweekly' is the same weekday mask every 14 days (start_date picks which week — it is not BiMonthly, which is every other MONTH); 'monthly_fixed' / 'bimonthly_fixed' recur on a fixed day of the month; 'monthly_nth' / 'bimonthly_nth' recur on the Nth weekday of the month; the 'bimonthly_*' variants additionally alternate months starting from start_month; 'custom' uses an explicit list of dates.
  - `days_of_week` string[]: Days of the week this recurs on. Required (non-empty) when schedule_type is 'weekly' or 'biweekly'; ignored for every other schedule_type.
  - `nth_weekday` 'sunday' | 'monday' | 'tuesday' | 'wednesday' | 'thursday' | 'friday' | 'saturday' | 'null', nullable: The weekday that recurs. Used by schedule_type 'monthly_nth' / 'bimonthly_nth'; defaults to Sunday if omitted for those types.
  - `week_of_month` integer: Which occurrence of the weekday in the month (1st through 5th). Used by 'monthly_nth' / 'bimonthly_nth'; defaults to 1.
  - `day_of_month` integer: Fixed day of the month. Used by 'monthly_fixed' / 'bimonthly_fixed'; defaults to 1. Clamped to the last day of shorter months (e.g. 31 in February becomes the 28th/29th).
  - `start_month` integer: Anchor month (1=January .. 12=December) the every-other-month cadence steps from. Used by 'bimonthly_fixed' / 'bimonthly_nth'; defaults to the account's current month.
  - `time` string, nullable: Wall-clock time of day "HH:mm" (24h) in the account's timezone, or null for no specific time (midnight).
  - `start_date` string, nullable: "yyyy-MM-dd" in the account's timezone before which no occurrences materialize, or null for no lower bound. For 'biweekly' this date also picks which week the 14-day rotation lands on (Sunday-week of this day is an on-week). When omitted for biweekly, the server stores the first selected weekday with a shift still ahead (today if it is selected and its start time hasn't passed, else the next selected weekday; the first shift), and the rotation counts from there. On PATCH of an existing biweekly schedule, null keeps the stored date (the rotation only moves when you send a new date).
  - `end_date` string, nullable: "yyyy-MM-dd" in the account's timezone after which occurrence materialization stops, or null for no end date.
  - `custom_dates` string[]: Explicit list of "yyyy-MM-dd" dates. Required (non-empty) when schedule_type is 'custom'; ignored otherwise.
  - `notes` string, nullable: Free-text internal notes.
  - `confirm_remove` boolean: Set true, together with `confirm_token`, to apply an edit that removes upcoming shifts (e.g. a new start_month or fewer weekdays). Without both, such an edit saves nothing and answers 409 `confirm_remove_required` listing the shifts; resend only once the user has agreed to drop them.
  - `confirm_token` string: The `confirm_token` from the 409 `confirm_remove_required` for this same edit. It only matches while the edit and the shifts it removes are unchanged; otherwise you get a fresh 409 to review.

## Response `200`

The updated schedule.

- Schedule
  - `id` number, required: Schedule id.
  - `description` string, required: Short label shown on the schedule list and in reminder messages, e.g. "Sunday morning greeters".
  - `job_id` number, required: Job/role id (use for per-job reminder overrides).
  - `job_name` string, nullable, required: Resolved job/role name.
  - `group_id` integer, nullable, required: Id of an existing volunteer group to attach, or null. An id not owned by this account is silently ignored (treated as null).
  - `group_name` string, nullable, required: Resolved group name, or null.
  - `schedule_type` 'weekly' | 'biweekly' | 'monthly_fixed' | 'monthly_nth' | 'bimonthly_fixed' | 'bimonthly_nth' | 'custom', required: Recurrence shape. 'weekly' recurs on one or more days of the week; 'biweekly' is the same weekday mask every 14 days (start_date picks which week — it is not BiMonthly, which is every other MONTH); 'monthly_fixed' / 'bimonthly_fixed' recur on a fixed day of the month; 'monthly_nth' / 'bimonthly_nth' recur on the Nth weekday of the month; the 'bimonthly_*' variants additionally alternate months starting from start_month; 'custom' uses an explicit list of dates.
  - `days_of_week` string[], required: Days of the week this recurs on. Required (non-empty) when schedule_type is 'weekly' or 'biweekly'; ignored for every other schedule_type.
  - `nth_weekday` 'sunday' | 'monday' | 'tuesday' | 'wednesday' | 'thursday' | 'friday' | 'saturday' | 'null', nullable, required: The weekday that recurs. Used by schedule_type 'monthly_nth' / 'bimonthly_nth'; defaults to Sunday if omitted for those types.
  - `week_of_month` number, nullable, required: Nth-weekday occurrence (1-5), or null when schedule_type doesn't use it.
  - `day_of_month` number, nullable, required: Fixed day of month, or null when schedule_type doesn't use it.
  - `start_month` number, nullable, required: Bimonthly anchor month (1-12), or null when schedule_type doesn't use it.
  - `time` string, nullable, required: Wall-clock time of day "HH:mm" (24h) in the account's timezone, or null for no specific time (midnight).
  - `start_date` string, nullable, required: "yyyy-MM-dd" in the account's timezone before which no occurrences materialize, or null for no lower bound. For 'biweekly' this date also picks which week the 14-day rotation lands on (Sunday-week of this day is an on-week). When omitted for biweekly, the server stores the first selected weekday with a shift still ahead (today if it is selected and its start time hasn't passed, else the next selected weekday; the first shift), and the rotation counts from there. On PATCH of an existing biweekly schedule, null keeps the stored date (the rotation only moves when you send a new date).
  - `end_date` string, nullable, required: "yyyy-MM-dd" in the account's timezone after which occurrence materialization stops, or null for no end date.
  - `custom_dates` string[], required: Explicit list of "yyyy-MM-dd" dates. Required (non-empty) when schedule_type is 'custom'; ignored otherwise.
  - `contact_ids` integer[], required: Volunteer roster for this schedule. Ids not owned by this account are silently dropped.
  - `notes` string, nullable, required: Free-text internal notes.
  - `next_occurrence_at` number, nullable, required: Epoch ms (UTC) of the next upcoming, attendable occurrence, or null.
  - `is_muted` boolean, required: Whether reminders for this schedule are muted.
  - `created_at` number, required: Epoch ms (UTC) this schedule was created.

## Other responses

- `400`: The request failed schema validation (`validation_error`). On the import endpoints, also when the text is UTF-16 (Excel "Unicode Text"; `unsupported_encoding`): resend it as CSV UTF-8.
- `401`: Missing or invalid API key.
- `403`: The API key lacks a scope required by this endpoint.
- `404`: The requested resource does not exist.
- `409`: The edit would remove upcoming shifts and was not confirmed with a matching `confirm_token`. Nothing was saved.

---

[API](https://skmtc.dev/volunteerreminder/apis/volunteerreminder-api.md) · [All operations](https://skmtc.dev/volunteerreminder/apis/volunteerreminder-api/llms.txt) · [OpenAPI document](https://skmtc.dev/volunteerreminder/apis/volunteerreminder-api/revisions/f8e7e26ba96f?raw)
