On-Call & Schedules

A schedule is a stack of layers. A layer is a rotation of users with optional time restrictions. At any moment, the schedule resolves to a primary (the first layer whose restriction is active right now) plus any secondary, tertiary, etc. that also happen to be active.

This mirrors PagerDuty's layered schedules. Add BetterStack's idea of pinning a specific user to a specific time range — a fixed shift — and you have full coverage for any rotation pattern people actually use.

The resolution stack

For each layer at time T, Happy Uptime walks this priority list:

1

Override (highest)

Active if now ∈ [start_at, end_at). Scope: schedule-wide (covers every layer) or layer-specific. Used when someone needs to cover for someone else for a defined window — vacation, conference, sick day.

2

Fixed shift

A specific user pinned to a specific (layer_id, start_at, end_at) tuple. Wins over rotation but loses to overrides. Used for one-off coverage that doesn't fit the rotation pattern (weekend swap, holiday).

3

Rotation

The default. shiftIdx = floor((now − layerEpoch) / rotationLength), then members[shiftIdx % members.length]. The epoch is the most recent handoff day + hour (in the layer's timezone) at-or-before the layer's created_at, so handoffs always land on Monday 9am (or whenever you configured).

4

Restriction gate

If the layer's restriction (none / weekday-business-hours / weekends-only / custom) does NOT include now, the layer is off-duty for this moment. Resolution falls through to the next layer.

The schedule's primary is the first layer whose user is non-null AND whose restriction is active at the current moment.

Common patterns

One layer. Weekly rotation. No restriction. 5 members. Each gets every 5th week.

Two layers:

  • Primary — Mon–Fri 9am–5pm local. Senior engineers.
  • Secondary — always on. Junior engineers (or external paging service).

During business hours: primary is active, secondary is also active but ignored. Nights/weekends: primary is off-duty, secondary takes over.

Three layers, each with weekday-hours restriction in a different timezone:

  • Asia primary — Mon–Fri 9am–6pm Asia/Tokyo
  • Europe primary — Mon–Fri 9am–6pm Europe/London
  • Americas primary — Mon–Fri 9am–6pm America/New_York

At any given moment, exactly one layer is active. Coverage rolls around the globe.

One rotation layer + an escalation policy:

  • Layer 0: Engineering team rotates weekly.
  • Escalation level 1 (after 5 min unack): page CTO directly.
  • Escalation level 2 (after 15 min unack): post to #incident channel.

The escalation policy is independent of layers — it fires only on unacked incidents.

What each thing controls

ConceptControlsStorage
ScheduleName, organization, default Slack routing, ICS tokenoncall_schedules
LayerRotation cadence, handoff time, timezone, restriction, ordered membersoncall_layers + oncall_members
OverrideOne person covering for the rotation in a time windowoncall_overrides
Fixed shiftOne person pinned to a specific layer for a specific windowoncall_fixed_shifts
Escalation policyWhat happens N minutes after an incident is unackedoncall_escalation_policies

Restrictions in detail

A layer's restriction_type is one of:

TypeActive when
noneAlways (24/7)
weekday_hoursMon–Fri, hours [start, end) in layer's timezone
weekendsSat + Sun, all hours
customA subset of weekdays you pick + optional hour window. Hour windows can wrap midnight (e.g. 22:00 → 06:00 covers nights).

Restrictions are evaluated in the layer's timezone, so DST transitions resolve cleanly.

Build your first rotation

Step-by-step: 5 members, weekly handoff Monday 9am.

Learn More
Multi-layer schedules

Primary + secondary, follow-the-sun, business-hours-only.

Learn More
Restrictions

"Layer X is only active Mon–Fri 9am–5pm".

Learn More
Overrides + fixed shifts

Cover for someone, swap a weekend.

Learn More
Escalation policies

"If unacked in 5 min, page the CTO".

Learn More
Slack paging

Make alerts @-mention the on-call person + ack/resolve from Slack buttons.

Learn More

API

Full CRUD lives under /oncall/schedules/:id — see schedules, layers, overrides, shifts, escalation, timeline, and iCalendar export.

Ask a question... ⌘I