Schedules
List and create Incident Management on-call schedules: the rotation that decides who is on call, and when, for a team.
GET /api/im/schedules · POST /api/im/schedules
A schedule belongs to exactly one team and defines its on-call rotation: one or more layers of users, each rotating on a daily, weekly, or custom cadence, optionally restricted to specific times of day. A background worker materializes the rotation into concrete shifts (im_schedule_shift) roughly 90 days ahead, which is what Who Is On Call and the escalation engine actually read at runtime. This endpoint manages the rotation's definition, not the materialized shifts directly.
Authentication
Requires the base IM access every endpoint in this API needs (an IM-eligible role, or an organization-wide API token; Incident Management must be enabled for the organization). Listing is available to any IM-eligible role. Creating a schedule additionally requires the write bar: your role must be admin, or you must be a team admin of the schedule's team. An organization-wide API token satisfies the organization-admin bar.
List schedules
GET /api/im/schedules
Returns every schedule in your organization, with its team's name.
Example (cURL)
curl -X GET "$BASE_URL/api/im/schedules" \
-H "Authorization: Bearer $TOKEN" \
-H "Accept: application/json"Response
200 OK: an array, ordered by name.
[
{
"id": 7,
"organizationId": 1,
"teamId": 3,
"teamName": "Platform Team",
"name": "Primary On-Call",
"timezone": "Europe/Berlin",
"rotation": [
{
"users": ["u_abc123", "u_def456"],
"type": "weekly",
"handoverTime": "09:00",
"startDate": "2026-01-05"
}
],
"createdAt": "2026-01-01T00:00:00.000Z",
"updatedAt": "2026-01-01T00:00:00.000Z"
}
]Create a schedule
POST /api/im/schedules
Request Body
| Field | Type | Required | Description |
|---|---|---|---|
teamId | number | Yes | The team the schedule belongs to. Must belong to your organization. |
name | string | Yes | Schedule name, up to 120 characters. |
timezone | string | Yes | IANA timezone (e.g. Europe/Berlin). Every handover and time-of-day restriction is evaluated in this zone, across DST. |
tierId | number | No | The escalation tier the schedule belongs to. |
A newly created schedule has no rotation yet — it is created with rotationMode: "none" and no members. Set the cadence and the members with PATCH /api/im/schedules/:id (see below); this endpoint does not accept them.
Rotation cadence
The cadence decides how often on-call hands over. It is set on the schedule with PATCH /api/im/schedules/:id.
| Field | Type | Description |
|---|---|---|
rotationMode | string | none (one continuous shift, nobody hands over), auto (the member pool is chunked automatically) or explicit (you define the groups). |
rotationRepeats | string | daily, weekly, biweekly, monthly or custom. Must be null when rotationMode is none. |
customRepeatUnit | string | Required when rotationRepeats is custom: minutes, hours, days, weeks or months. |
customRepeatValue | number | Required when rotationRepeats is custom. Integer ≥ 1, in the unit above. |
startsOnTime | string | "HH:mm" — the wall-clock time of day handovers happen, in the schedule's timezone. Minute precision. |
startsOnDayOfWeek | number | ISO weekday (1 = Monday .. 7 = Sunday). Required for weekly, biweekly and custom + weeks. |
startsOnDateOfMonth | number | 1–31, clamped to the month's last day. Required for monthly and custom + months. |
The shortest possible shift is one minute (customRepeatUnit: "minutes", customRepeatValue: 1). Handover times are minute-precise at every cadence, since startsOnTime is an "HH:mm" wall clock.
Sub-day cadences (minutes, hours) step in absolute time rather than wall clock. Across a DST switch they therefore keep handing over every N minutes in real time instead of duplicating or skipping an hour's worth of handovers.
How far ahead shifts are materialized
Shifts are materialized on a rolling horizon of up to 90 days. A fast cadence is materialized less far ahead, because the number of shift rows is the horizon divided by the cadence: a one-minute rotation over 90 days would be 129,600 shifts per member. Such a schedule is instead kept roughly 41 hours ahead, topped up every hour.
This is not a coverage limit. Paging reads the shift that is current now, and the materializer runs far more often than the horizon shrinks. It only means that a preview far into the future is shorter for a fast cadence. Every cadence of an hour or longer keeps the full 90 days.
Example (cURL)
curl -X POST "$BASE_URL/api/im/schedules" \
-H "Authorization: Bearer $TOKEN" \
-H "Content-Type: application/json" \
-d '{
"teamId": 3,
"name": "Primary On-Call",
"timezone": "Europe/Berlin",
"rotation": [
{
"users": ["u_abc123", "u_def456"],
"type": "weekly",
"handoverTime": "09:00",
"startDate": "2026-01-05"
}
]
}'Response
200 OK: the newly created schedule row (same shape as the list above, without teamName).
Common errors
401 Unauthorizedwhen not authenticated403 Forbidden(imAccessDenied) when using a customer-scoped token, or a session without an IM-eligible role403 Forbidden(imNotEnabled) when Incident Management is not enabled for the organization403 Forbidden(imTeamWriteDenied) (create only) when your role is notadminand you are not a team admin ofteamId404 Not Found(imTeamNotFound) (create only) whenteamIddoes not exist, or belongs to another organization400 Bad Request(invalidRequestBody) (create only) whenname,timezone, or a rotation layer field is missing or malformed (see field descriptions above), or a layer/restriction/user-list bound is exceeded