# Maintenance windows

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

A maintenance window is a mute that comes back on a schedule. Alerts are still recorded, nobody is paged, and the time is left out of uptime.

```json
{
  "name": "Sunday patching",
  "description": "Kernel and package updates on the API hosts.",
  "timezone": "Europe/Oslo",
  "starts_at": "2026-10-04T02:00",
  "duration_minutes": 120,
  "rrule": "FREQ=WEEKLY;BYDAY=SU",
  "exdates": ["2026-10-18T02:00"],
  "scope": { "project": false, "checks": ["api-health"], "sources": ["api"], "rules": [] }
}
```

| Field | Meaning |
| --- | --- |
| `name` | Up to 200 characters. |
| `description` | Up to 2000. Shown on a status page when the window is. |
| `timezone` | Required. An IANA zone such as `Europe/Oslo` or `UTC`. Every time in the window is a wall clock in it. |
| `starts_at` | Local time as `YYYY-MM-DDTHH:MM`. For a recurring window it is the rule's `DTSTART`: where the schedule starts and the time of day it repeats at. |
| `ends_at` | One-off windows only: local time, after `starts_at`. |
| `duration_minutes` | Recurring windows only: how long each occurrence lasts. |
| `rrule` | An [RFC 5545](https://datatracker.ietf.org/doc/html/rfc5545#section-3.3.10) recurrence rule, up to 1000 characters. Omit it for a one-off window. |
| `exdates` | Recurring windows only: up to 200 occurrences to skip, each written as its local start time. |
| `scope` | The whole project, or the checks, sources and rules it covers. See [What a window covers](#what-a-window-covers). |
| `status_page`, `components` | Show the window on [status pages](#on-a-status-page). Off by default. |

An occurrence lasts at most 90 days. A window must have an occurrence that ends after now, so one written entirely in the past is refused.

<span class="pill post">POST</span> <span class="pill get">GET</span> `/v1/projects/{id}/maintenance-windows`, then <span class="pill get">GET</span> <span class="pill put">PUT</span> <span class="pill del">DELETE</span> `/v1/projects/{id}/maintenance-windows/{wid}`. In the [web app](https://app.pingtower.com) it is Alerts → Maintenance; in the iOS app, a project's Maintenance screen.

## One-off or recurring

A one-off window has `starts_at` and `ends_at` and nothing else. Sending `duration_minutes` or `exdates` with it is a `400` naming the field.

```json
{ "name": "DB migration", "timezone": "UTC", "starts_at": "2026-10-07T22:00", "ends_at": "2026-10-08T01:30",
  "scope": { "project": true } }
```

A recurring window has `starts_at`, `duration_minutes` and an `rrule`. The rule is the RRULE value, with or without its `RRULE:` prefix:

- `FREQ` may be `HOURLY` or coarser. `SECONDLY` and `MINUTELY` are refused.
- `DTSTART` and `TZID` are refused inside the rule: `starts_at` and `timezone` give them.
- `COUNT` or `UNTIL` ends it, not both. `UNTIL` is a UTC instant (`20261231T230000Z`), a local date-time (`20261231T230000`), or a local date (`20261231`, which includes that whole day).

Times are wall clocks, so 02:00 on Sundays stays 02:00 local on both sides of a daylight-saving change. A start the zone skips moves forward by the gap (02:30 on a spring-forward day becomes 03:30), and one the zone repeats takes its first instance. The length is elapsed time: a two-hour window across a fall-back hour still ends two real hours after it starts.

An entry in `exdates` must be one of the window's occurrences, by its local start time, or the save is refused; an exdate that silently excluded nothing would be worse.

## Preview before saving

<span class="pill post">POST</span> `/v1/projects/{id}/maintenance-windows/preview?count=5` takes a window body, validates it exactly as a create would, saves nothing, and returns the next occurrences, the one in progress first. Bad input gets the same field errors a create would.

```json
{
  "occurrences": [
    { "start": 1791072000, "end": 1791079200 },
    { "start": 1791676800, "end": 1791684000 },
    { "start": 1792886400, "end": 1792893600 },
    { "start": 1793494800, "end": 1793502000 },
    { "start": 1794099600, "end": 1794106800 }
  ]
}
```

Unix seconds, `[start, end)`. `count` is 1 to 100, default 10. 18 October is missing because `exdates` skips it, and the occurrences from 1 November are an hour later in UTC because Oslo has left summer time. This is what the app's editor calls as you type, so no client evaluates an RRULE itself. <span class="pill get">GET</span> `…/maintenance-windows/{wid}/occurrences?count=` answers the same for a saved window.

Every read of a window carries `active`, the occurrence in progress as `current` (`null` when none), and up to five `next_occurrences`. The list puts windows in progress first, then upcoming ones by next start, then those that will not recur.

## What a window covers

`scope` is `{"project": true}` for everything in the project, or any mix of check, source and rule names, up to 64 of each. Names are checked on save. Deleting a check, source or rule removes it from the windows that name it.

Coverage is decided per rule. A window covers a rule when it:

- covers the whole project,
- names the rule,
- names the [check](/docs/service/checks/) whose `alert_if` wrote the rule, or
- names a source the rule lists in `match.sources` or in its [`source_silent`](/docs/service/rules/#sources-that-go-quiet) trigger.

A covered rule is held whole. A window naming one of a rule's sources also holds that rule's alerts from its other sources. A rule that matches every source is not covered by a window naming one source; name the rule or the project instead.

## While a window is in progress

What it covers is recorded but pages nobody, the same as a [muted rule](/docs/service/rules/#muting-a-rule):

- Alerts still open, count and show in every feed and the history.
- No push, no integration delivery, and no [runbook action](/docs/service/actions/) runs for them.
- The escalation ladder does not climb.

When the window ends, or is deleted mid-occurrence, the alerts it held are released at the next tick. One still firing and unacknowledged pages as it would have: its `alert.opened` goes out then, and because a ladder climbs by the alert's age, it pages the rung its age has reached. One acknowledged or resolved in the meantime pages nobody. An alert that had already paged before the window started keeps its ack and resolve notices, so a channel that heard it open hears it close.

## Uptime

Maintenance time does not count against uptime.

- A check run taken while a window covers the project, the check, the source that ran it or the check's rule is left out of the check's success rate and its [uptime report](/docs/service/uptime-reports/): neither up nor down.
- For a status-page component, a window that is not shown on a status page leaves the time it covered out of the component's uptime entirely, outages included, for each component whose every binding it covers. A shown window works differently; see below.

Editing a window applies from now on. The time it already covered stays covered with the scope it had then, so uptime already measured does not move. Deleting it keeps the time it covered excluded as well.

## On a status page

A window appears on a [status page](/docs/service/status-pages/) only if you opt in:

```json
{ "status_page": true, "components": ["…", "…"] }
```

`components` are component ids from any of the team's status pages, up to 50, and at least one when `status_page` is true. A component must be bound to a check or rule in the window's own project; one with no bindings can be picked only by a member not limited to projects. Any other is a `400` `component <id> is not bound to project <P>`. Turning `status_page` off keeps the picks, so turning it back on restores them.

The page lists maintenance in progress, upcoming maintenance starting within the next seven days, and a 90-day maintenance history, each with the window's name and description. While an occurrence runs, each picked component shows Under maintenance in blue, and a day spent entirely in maintenance draws a blue bar.

The worst state still wins. Picking components decides only where the window shows; it does not hide anything. An outage from a rule or check outside the window's scope, or one posted by hand, shows over the maintenance and counts against uptime. Time whose worst state is maintenance is left out of uptime. Alerts the window's scope does hold get no outage on the page while it runs; one still firing when it ends shows, and counts, from that moment. That happens whether or not the window is shown.

## Who can, and how many

Viewers and up can read windows and preview them. Members and up, or any [custom role](/docs/getting-started/concepts/#custom-roles) with `maintenance:write`, can create, edit and delete them. Every write goes to the [audit log](/docs/service/team/#audit-log) and is announced to the project's webhooks and chat integrations as `maintenance.scheduled`, `maintenance.changed` or `maintenance.cancelled`, naming the window, its schedule and who did it, so a window nobody mentioned cannot quietly hold pages back. See [Maintenance notices](/docs/service/integrations/#maintenance-notices).

A project holds at most 100 windows that are still to come or in progress. One whose every occurrence has ended does not count toward the cap, so a one-off window per deploy is never refused for being the hundred-and-first. A window that has ended and will not recur is removed 95 days after its last occurrence. Past the cap the create is a `429` naming `max_maintenance_windows_per_project`; see [limits](/docs/reference/limits/#counts).

An [AI assistant](/docs/service/ai-assistants/) granted `maintenance:write` can schedule a one-off window that ends within 24 hours from now. It cannot schedule recurring windows, edit or delete them, or put them on a status page.

## Window or mute?

A [rule mute](/docs/service/rules/#muting-a-rule) silences one rule from now until a time you pick, up to 90 days, and tells the project's channels it happened. Use it for the noise you are looking at right now. Use a window for work you can put on a calendar: it starts and stops on its own, it repeats, it can cover a whole project or every rule behind a check or source, it keeps the time out of uptime, and it can tell your customers on the status page. Both release what they held in the same way when they end.
