Uptimeify Docs
Incident management

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

FieldTypeRequiredDescription
teamIdnumberYesThe team the schedule belongs to. Must belong to your organization.
namestringYesSchedule name, up to 120 characters.
timezonestringYesIANA timezone (e.g. Europe/Berlin). Every handover and time-of-day restriction is evaluated in this zone, across DST.
tierIdnumberNoThe 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.

FieldTypeDescription
rotationModestringnone (one continuous shift, nobody hands over), auto (the member pool is chunked automatically) or explicit (you define the groups).
rotationRepeatsstringdaily, weekly, biweekly, monthly or custom. Must be null when rotationMode is none.
customRepeatUnitstringRequired when rotationRepeats is custom: minutes, hours, days, weeks or months.
customRepeatValuenumberRequired when rotationRepeats is custom. Integer ≥ 1, in the unit above.
startsOnTimestring"HH:mm" — the wall-clock time of day handovers happen, in the schedule's timezone. Minute precision.
startsOnDayOfWeeknumberISO weekday (1 = Monday .. 7 = Sunday). Required for weekly, biweekly and custom + weeks.
startsOnDateOfMonthnumber1–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 Unauthorized when not authenticated
  • 403 Forbidden (imAccessDenied) when using a customer-scoped token, or a session without an IM-eligible role
  • 403 Forbidden (imNotEnabled) when Incident Management is not enabled for the organization
  • 403 Forbidden (imTeamWriteDenied) (create only) when your role is not admin and you are not a team admin of teamId
  • 404 Not Found (imTeamNotFound) (create only) when teamId does not exist, or belongs to another organization
  • 400 Bad Request (invalidRequestBody) (create only) when name, timezone, or a rotation layer field is missing or malformed (see field descriptions above), or a layer/restriction/user-list bound is exceeded

On this page