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:
userIdis the caller,typeisoffline,applyScopeisthis_team(or omitted),escalationTierOrderisnull(or omitted),coveredByUserIdsholds 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) toendsAtlies inside one contiguous stretch of the caller's own on-call shifts on this schedule (back-to-back shifts count as one stretch), - no
offlineoverride 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
| Field | Type | Required | Description |
|---|---|---|---|
userId | string | Yes | The subject: the user who goes on call (online) or off call (offline). |
startsAt | string | Yes | ISO 8601 timestamp, window start. |
endsAt | string | Yes | ISO 8601 timestamp, window end. Must be after startsAt. |
type | string | Yes | online: the subject is on call for the window and replaces everyone else on call. offline: the subject is taken off call. |
coveredByUserIds | string[] | No | offline 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. |
applyScope | string | No | this_team (default) or all_teams: also apply to every other team the subject is a member of. |
escalationTierOrder | integer | null | No | Only 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:
- The rotation at that instant.
onlineoverrides: the last active one replaces the whole set by its subject.offlineoverrides without coverers remove their subject (unavailable, round robin skips them).- Round robin narrows the set per incident.
offlineoverrides 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
| Parameter | Type | Default | Description |
|---|---|---|---|
envelope | string | none | items: 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 authenticated403 Forbidden(imAccessDenied) for a customer-scoped token, or a session without an IM-eligible role403 Forbidden(imNotEnabled) when Incident Management is not activated for the organization403 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 member403 Forbidden(imShiftGiveAwayDenied) when a plain team member sends something other than giving away their own shift.data.reasonsays which condition failed:not_self(userIdis someone else),type(notoffline),scope(applyScopeisall_teams),tier(escalationTierOrderis 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(anofflineoverride 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 anofflineoverride about the caller that the caller created)429 Too Many Requests(imShiftGiveAwayRateLimited, withretryAfterin seconds) when a plain team member gives away more than 10 shifts within an hour404 Not Found(imScheduleNotFound) when:iddoes not exist or belongs to another organization404 Not Found(imOverrideNotFound) when:overrideIdis not an override of this schedule404 Not Found(userNotFound) whenuserIdor a covering user does not exist or belongs to another organization400 Bad Request(invalidRequestBody) when a path id is not a positive integer,userIdis missing,startsAt/endsAtis missing or not a valid timestamp,startsAtis not beforeendsAt,typeis notonlineoroffline,applyScopeis invalid,escalationTierOrderis not a positive integer ornull,coveredByUserIdsis not an array of ids or has more than 200 entries, or anonlineoverride has covers400 Bad Request(imUserNotEligible) when the subject or a covering user is inactive or does not have the organization roleadmin,editororresponder
Preview a schedule
Compute the shifts and coverage gaps a schedule's saved rotation produces over the next four weeks, without writing anything.
Alert Source Setup Guides
Step-by-step guides for wiring Zabbix, Datadog, Grafana Alerting, Prometheus Alertmanager, Sentry, Beszel, or any custom webhook to Uptimeify Incident Management.