Uptimeify Docs
Incident management

Schedule Overrides

List, add, edit and delete on-call overrides on a schedule: one-off windows that put someone on call or take someone off call without touching the rotation.

An override is a one-off window on top of a schedule's regular rotation, for a holiday swap or a sick day, without editing the rotation itself. This page covers overrides attached to one schedule. Overrides that belong to the team without a schedule are on Team Overrides.

Authentication

Base IM access: an IM-eligible role or an organization-wide API token, and Incident Management activated for the organization. Listing is open to any caller with that access. Adding, editing and deleting need the team write bar for the schedule's team: organization admin role, or team admin. An API token runs with the role of the user who created it, so only a token created by an organization admin passes.

Exception: giving away your own shift. A plain member of the schedule's team may add one kind of override without the write bar, and delete it again:

  • userId is the caller, type is offline, applyScope is this_team (or omitted), escalationTierOrder is null (or omitted),
  • coveredByUserIds holds exactly one user, not the caller, who is a member of the schedule's team,
  • the window has not ended yet, is at most 7 days long, and from startsAt (or now, if later) to endsAt lies inside one contiguous stretch of the caller's own on-call shifts on this schedule (back-to-back shifts count as one stretch),
  • no offline override of the caller on this schedule overlaps the window yet.

The coverer is put on call exactly for that window. This includes duty an admin put the caller on with an online override: offline overrides apply after online ones, so the member can give that duty away too. The coverer receives an email (the on-call shift notice, unless they turned off email or handoff notices in their notification settings), and another one when the override is deleted again, by the caller or by an admin; an identical repeat (same kind, overlapping window) within 10 minutes is sent once, a change between given and taken back is always sent. Whenever someone takes themselves offline with coverers (a member or an admin), every coverer gets this notice, and every coverer gets the "taken back" notice when the override is deleted. A member can give away at most 10 shifts per hour; the 11th gets 429 imShiftGiveAwayRateLimited. A member may delete only an offline override about themselves that they created themselves; an override an admin created about them stays admin-only. Editing stays admin-only. Anything else a member sends is refused with 403 imShiftGiveAwayDenied and a reason (see Common errors).

Override fields

FieldTypeRequiredDescription
userIdstringYesThe subject: the user who goes on call (online) or off call (offline).
startsAtstringYesISO 8601 timestamp, window start.
endsAtstringYesISO 8601 timestamp, window end. Must be after startsAt.
typestringYesonline: the subject is on call for the window and replaces everyone else on call. offline: the subject is taken off call.
coveredByUserIdsstring[]Nooffline only, up to 200: who takes over the pager. All listed coverers are paged, each one for exactly the pages the subject would get. Empty or omitted leaves a gap that the escalation engine fills from another tier when it pages. Must be empty for online.
applyScopestringNothis_team (default) or all_teams: also apply to every other team the subject is a member of.
escalationTierOrderinteger | nullNoOnly apply on this tier (tierOrder, 1 or more). null or omitted = all tiers.

Every user id (subject and covers) must be an active user of your organization with the organization role admin, editor or responder.

When two online overrides overlap, the one created later wins. There is no priority field, precedence is creation order, which is why lists are ordered by id and an edit keeps the override's id.

How overrides are resolved

An override takes effect the moment it is saved: the escalation engine reads overrides at page time. The calendar and the shift table follow once the shifts are re-materialized, within seconds.

coveredByUserIds is a 1:1 substitution: the coverers receive exactly the pages the covered person would get, and all of them are paged. For each schedule, the on-call set is resolved in this order:

  1. The rotation at that instant.
  2. online overrides: the last active one replaces the whole set by its subject. offline overrides without coverers remove their subject (unavailable, round robin skips them).
  3. Round robin narrows the set per incident.
  4. offline overrides with coverers replace the picked subject by all of their coverers.

Team-level overrides are folded in after the union of the tier's schedules, with the same substitution.

Running incidents. When an override starts or changes while an incident is open, the next page of that incident (a repeat or an escalation) follows it. The round-robin pick is remembered per incident but re-mapped at page time: if the remembered person is covered at that moment, the coverers are paged instead; if they are offline without cover, they are not paged.

List overrides

GET /api/im/schedules/:id/overrides

Returns the schedule's overrides, ordered by id.

Query Parameters

ParameterTypeDefaultDescription
envelopestringnoneitems: answer as { items, total, page, perPage }. Becomes the default on 2027-01-18.

Example (cURL)

curl -X GET "$BASE_URL/api/im/schedules/7/overrides?envelope=items" \
  -H "Authorization: Bearer $TOKEN"

Response

200 OK

{
  "items": [
    {
      "id": 12,
      "scheduleId": 7,
      "userId": "u_abc123",
      "userName": "Ada Lovelace",
      "userImage": null,
      "type": "offline",
      "applyScope": "this_team",
      "escalationTierOrder": null,
      "startsAt": "2026-10-20T00:00:00.000Z",
      "endsAt": "2026-10-27T00:00:00.000Z",
      "createdAt": "2026-10-09T10:00:00.000Z",
      "coveredBy": [
        { "userId": "u_def456", "userName": "Bob Fixit", "userImage": null }
      ]
    }
  ],
  "total": 1,
  "page": 1,
  "perPage": 1
}

List form: with envelope=items the list arrives as { items, total, page, perPage }. Without it the endpoint still answers the current form (a bare array of the same overrides), which is deprecated; { items, ... } becomes the default on 2027-01-18.

Cover list shape: the list and the single GET return coveredBy as an array of objects (userId, userName, userImage). Adding (POST) and editing (PATCH) return coveredByUserIds instead, an array of user id strings.

Get an override

GET /api/im/schedules/:id/overrides/:overrideId

Reads one override of this schedule back, in the same shape as an entry of List overrides. Any IM-eligible role may call it.

Example (cURL)

curl "$BASE_URL/api/im/schedules/5/overrides/31" \
  -H "Authorization: Bearer $TOKEN"

Response

200 OK, one override object as in the list above. 404 with imOverrideNotFound when :overrideId is not an override of this schedule.

Add an override

POST /api/im/schedules/:id/overrides

Body: the override fields.

Example (cURL)

Ada is on holiday, Bob covers:

curl -X POST "$BASE_URL/api/im/schedules/7/overrides" \
  -H "Authorization: Bearer $TOKEN" \
  -H "Content-Type: application/json" \
  -d '{
    "userId": "u_abc123",
    "type": "offline",
    "coveredByUserIds": ["u_def456"],
    "startsAt": "2026-10-20T00:00:00.000Z",
    "endsAt": "2026-10-27T00:00:00.000Z"
  }'

Response

200 OK: the stored override with the cover list as ids.

{
  "id": 12,
  "teamId": 3,
  "scheduleId": 7,
  "userId": "u_abc123",
  "type": "offline",
  "applyScope": "this_team",
  "escalationTierOrder": null,
  "startsAt": "2026-10-20T00:00:00.000Z",
  "endsAt": "2026-10-27T00:00:00.000Z",
  "createdAt": "2026-10-09T10:00:00.000Z",
  "coveredByUserIds": ["u_def456"]
}

Edit an override

PATCH /api/im/schedules/:id/overrides/:overrideId

A partial update: send only the override fields you want to change, none is required. Fields you leave out keep their stored value, and the rules are checked against the result: endsAt must still be after startsAt (stored or sent), and an override that becomes online drops its covers unless you send coveredByUserIds. The override keeps its id and therefore its precedence.

A body that contains all of userId, type, startsAt and endsAt replaces the override as before: optional fields you leave out fall back to their defaults (applyScope: "this_team", all tiers, no covers).

# Only move the end of the window
curl -X PATCH "$BASE_URL/api/im/schedules/7/overrides/12" \
  -H "Authorization: Bearer $TOKEN" \
  -H "Content-Type: application/json" \
  -d '{ "endsAt": "2026-10-25T00:00:00.000Z" }'

Example (cURL)

curl -X PATCH "$BASE_URL/api/im/schedules/7/overrides/12" \
  -H "Authorization: Bearer $TOKEN" \
  -H "Content-Type: application/json" \
  -d '{
    "userId": "u_abc123",
    "type": "offline",
    "coveredByUserIds": ["u_def456"],
    "startsAt": "2026-10-20T00:00:00.000Z",
    "endsAt": "2026-10-24T00:00:00.000Z"
  }'

Response

200 OK: same shape as when adding.

Delete an override

DELETE /api/im/schedules/:id/overrides/:overrideId

Example (cURL)

curl -X DELETE "$BASE_URL/api/im/schedules/7/overrides/12" \
  -H "Authorization: Bearer $TOKEN"

Response

200 OK

{ "success": true }

Common errors

  • 401 Unauthorized (unauthorized) when not authenticated
  • 403 Forbidden (imAccessDenied) for a customer-scoped token, or a session without an IM-eligible role
  • 403 Forbidden (imNotEnabled) when Incident Management is not activated for the organization
  • 403 Forbidden (imTeamWriteDenied) on add, edit or delete when you are neither organization admin, team admin nor (for add and delete) a member of the schedule's team; on edit for every plain member
  • 403 Forbidden (imShiftGiveAwayDenied) when a plain team member sends something other than giving away their own shift. data.reason says which condition failed: not_self (userId is someone else), type (not offline), scope (applyScope is all_teams), tier (escalationTierOrder is set), covered_by (not exactly one coverer, or the caller covers themselves), window (the window has ended, is longer than 7 days, or is not inside one contiguous stretch of the caller's own shifts on this schedule), coverer_not_in_team (the coverer is not a member of the schedule's team), already_given_away (an offline override of the caller on this schedule already overlaps the window), no_duty (the caller has no shift on this schedule in the window that has not ended yet), not_own_giveaway (delete: the override is not an offline override about the caller that the caller created)
  • 429 Too Many Requests (imShiftGiveAwayRateLimited, with retryAfter in seconds) when a plain team member gives away more than 10 shifts within an hour
  • 404 Not Found (imScheduleNotFound) when :id does not exist or belongs to another organization
  • 404 Not Found (imOverrideNotFound) when :overrideId is not an override of this schedule
  • 404 Not Found (userNotFound) when userId or a covering user does not exist or belongs to another organization
  • 400 Bad Request (invalidRequestBody) when a path id is not a positive integer, userId is missing, startsAt/endsAt is missing or not a valid timestamp, startsAt is not before endsAt, type is not online or offline, applyScope is invalid, escalationTierOrder is not a positive integer or null, coveredByUserIds is not an array of ids or has more than 200 entries, or an online override has covers
  • 400 Bad Request (imUserNotEligible) when the subject or a covering user is inactive or does not have the organization role admin, editor or responder

On this page