# On-call schedules

Source: https://www.pingtower.com/docs/service/oncall/

A schedule answers "who is on call right now". Rules name schedules in their escalation ladders, so the rule never changes when the rota does.

<div class="docs-callout note"><svg viewBox="0 0 16 16" fill="none" stroke="currentColor" stroke-width="1.5" stroke-linecap="round"><circle cx="8" cy="8" r="6.5"/><path d="M8 7v4M8 5v.5"/></svg><div><span class="t">API only for now</span>Schedules, overrides and the who-is-on-call-now answer are served by the hosted service and by self-hosted boxes alike. The iOS app does not yet have screens for them; manage them from the API until it does.</div>
</div>


## Layers and rotations

A schedule is per project. It has a timezone and one or more layers. Each layer rotates through its participants every `shift_minutes`, starting at `rotation_start`. A layer with a `restriction` is active only inside that window, and a later layer wins over an earlier one while both are active. That is how "weekday office hours go to the ops account, everything else to the weekly rota" is written.

<span class="pill post">POST</span> `/v1/projects/{id}/oncall/schedules`

```json
{
  "name": "backend-primary",
  "timezone": "Europe/Stockholm",
  "layers": [
    { "name": "weekly",
      "participants": ["ana@acme.example", "jon@acme.example", "mei@acme.example"],
      "rotation_start": 1756677600, "shift_minutes": 10080 },
    { "name": "office-hours",
      "participants": ["ops@acme.example"],
      "rotation_start": 1756677600, "shift_minutes": 1440,
      "restriction": { "days": [1, 2, 3, 4, 5], "start_minute": 540, "end_minute": 1080 } }
  ]
}
```

`rotation_start` is a unix timestamp. `days` are 0 Sunday through 6 Saturday. `start_minute` and `end_minute` are minutes after midnight in the schedule's timezone; an end before a start wraps overnight. Participants must be members of the tenant; a stranger is a `422` naming them.

<span class="pill get">GET</span> <span class="pill put">PUT</span> <span class="pill del">DELETE</span> `/v1/projects/{id}/oncall/schedules/{sid}`.

## Overrides

An override swaps one person in for a window without editing the rota. Newest override wins where two overlap.

<span class="pill post">POST</span> `/v1/projects/{id}/oncall/schedules/{sid}/overrides`

```json
{ "account_id": "…", "starts_at": 1756800000, "ends_at": 1756886400 }
```

Unix timestamps, half-open `[starts_at, ends_at)`, at most a year long. <span class="pill get">GET</span> lists them; <span class="pill del">DELETE</span> `…/overrides/{oid}` removes one.

## Who is on call now

<span class="pill get">GET</span> `/v1/projects/{id}/oncall/schedules/{sid}/current`

```json
{ "on_call": true, "account_id": "…", "email": "ana@acme.example", "via": "layer:weekly" }
```

## Using it from a rule

Name the schedule in an escalation rung: `{"after_minutes": 15, "schedules": ["backend-primary"]}`. When the rung is reached the schedule is resolved at that instant and the person on call is added to the page. Shift changes emit `oncall.shift_started` and `oncall.shift_ended` events to integrations that subscribe to them.

<div class="docs-callout warn"><svg viewBox="0 0 16 16" fill="none" stroke="currentColor" stroke-width="1.5" stroke-linecap="round" stroke-linejoin="round"><path d="M8 2 1.5 13.5h13L8 2Z"/><path d="M8 6.5v3M8 11.5v.5"/></svg><div><span class="t">Deleting a schedule a rule still names</span>Returns <code>409</code> with the rules referencing it. Remove it from those ladders first; a rung that pointed at nothing would page nobody. Removing a member who is a participant is refused the same way, listing the schedules, escalations and overrides that name them.</div>
</div>
