Skip to main content
Import schedules… reads schedules as the Opsgenie and PagerDuty REST APIs return them, so a team that moves over does not rebuild its rotations by hand. A script fetches them into a file with the source’s API key. The file then goes through the same import as a bundle: tried first, then written all at once. MoleSignal does not keep the key and does not call the source. Importing requires schedules.create. One rule decides what comes over: who is on call at any moment stays the same from now on. What a schedule here cannot express and would change the result stops the import; what does not affect the result from now on is left out and named in the result.

Steps

1

Add the people

Add everyone on the rotations to the workspace with the same email as in the source; see Members. Members are matched by email, without regard to case.
2

Fetch the schedules

Run the script for Opsgenie or PagerDuty on a machine with curl and jq. It writes one file.
3

Try the import

Open Alerts → On-call schedules, select Import/export → Import schedules…, select Choose a file, and select Try. Each schedule is listed with what would happen to it; see Read the result.
4

Fix what stops it

Change the source and fetch again, or edit the file. Nothing is written while any schedule has an error.
5

Import

Select Import, then open an imported schedule and check who is on call. Point an escalation policy at it to start paging through it.

Opsgenie

Set an API key that can read schedules, then run the script. Accounts in the EU use api.eu.opsgenie.com.
The file holds each schedule from GET /v2/schedules/{id} under data, and its GET /v2/schedules/{id}/timeline under timelines. DAYS sets how far ahead the timeline reaches. These stop the import while the rotation is running: Overrides, forwarding rules, escalations, and routing are not imported.

PagerDuty

Set an API key that can read schedules and users, then run the script. Accounts in the EU use api.eu.pagerduty.com.
The file holds each schedule from GET /schedules/{id} and GET /v3/schedules/{id} under schedules, the emails from GET /users under users, because a schedule names people without their email, and the list from GET /v3/schedules under shift_based_schedules. since and until give the stretch PagerDuty renders, and DAYS sets how far ahead it reaches. Fetch schedules one by one, without time_zone. GET /schedules renders restrictions in the timezone of the caller instead of the schedule; the import recognizes a schedule rendered in another timezone and refuses it.

Layered schedules

Shift-based schedules

A schedule that PagerDuty’s V3 API returns is made of events. Events have no priority: when they overlap, the people of all of them are on call at once. These stop the import while the event is running: A shift-based schedule the file only names under shift_based_schedules is left out and named in the result, such as one the script could not fetch. Run the script again to bring it over. The rest of the file still imports. Overrides, escalation policies, and services are not imported.

Read the result

The other lines under a schedule say what was left out, and how it compares with the source.

The comparison with the source

The file holds what the source itself renders: PagerDuty’s final_schedule and Opsgenie’s timeline. The try works out who is on call for that stretch and compares the two, from now on. A difference is an error that gives the times and both people, and nothing is written.
  • Overrides are not imported, so the stretches they hold are not compared.
  • A shift-based schedule that has several people on call at once in final_schedule stops the import, even if its events do not overlap.
  • A schedule disabled in Opsgenie comes over paused and is not compared.
  • When the comparison does not reach a clock change in a timezone with daylight saving, the result says so. Handoffs stay at the same local time here; raise DAYS to compare them across the next change.
  • When the file has no rendered schedule, the result says it was not compared, and the import goes on. Removing final_schedule or timelines from the file is how to import a schedule that differs on purpose.

Limits

A file holds up to 4 MiB and 500 schedules, and a schedule up to 60 running rotations. Lower DAYS when a file is too large.

Import again

Schedules are matched by name, so running the import again updates the rotations of those that changed in the source and leaves the rest as they are. Overrides and shift swaps made here stay, and so do settings the file does not have, such as the team or Swaps need approval.
Last modified on November 1, 2026