# Create a maintenance window

A maintenance window tells HostTracker in advance that some monitors will be down on purpose - a deploy, a server
move, a database upgrade. While it runs, the covered monitors keep checking but hold back their down alerts, their
downtime statistics, or both. This page walks every field of the editor and shows the same thing through the API and
MCP. For the concept, see [What a maintenance window is](/maintenance/overview/).

Maintenance windows are available on every plan, with no limit on how many windows you create or how many monitors
one window covers.

## Settings reference

| Setting (UI label) | API field | Type / allowed values | Default | What it does for you |
|---|---|---|---|---|
| **Short description** (the name field) | `name` | Text, 1-255 characters, required | - | How you recognise the window in lists, alerts and on the status page. |
| **Window active** / **Window paused** | `enabled` | `true` / `false` | `true` | A paused window stays saved but suppresses nothing. |
| **Repeat** | `recurrence` | **One time** (`recurrence` absent or `null`) or **Weekly** (`recurrence: {"weekDays": [...]}`) | One time | Whether the window runs once or every week. The editor locks it after the window is created. |
| **Days of week** (Weekly only) | `recurrence.weekDays` | Array of English day names: `Monday` ... `Sunday`, at least one | Monday to Friday in the editor | The days a weekly window repeats on. See [recurring windows](/maintenance/recurring/). |
| **Starts** / **Ends** (One time), **From** / **To** (Weekly, per occurrence) | `from` + `to`, or `from` + `durationSec` | `from` and `to` are Unix seconds; `durationSec` is seconds. Send `to` or `durationSec`, never both | Editor: starts in one hour, lasts one hour | When the window begins and how long it lasts. Minimum length 60 seconds; maximum about 10 years. |
| **Quick add** | - | Buttons **+1m**, **+1h**, **+4h**, **+1d**, **+5d** | - | Each button adds to the current duration; the running **duration** is shown beside them. |
| **Time zone** | `timezone` | An IANA zone id such as `Europe/Berlin` (a Windows zone name is also accepted on write and converted) | `UTC` | The wall clock the start time is read in. A weekly window keeps its local time across daylight-saving changes. |
| **Show on status page** | `showOnStatusPage` | `true` / `false` | `true` | Shows the window on any of your status pages that includes a covered monitor. Display only - it never changes suppression. See [Show maintenance on your status page](/maintenance/status-page-display/). |
| **Affected Monitors** | `monitorIds` (same suppression for all) or `monitors[]` (per monitor) | Monitor ids you own; send one form, not both | - | Which monitors the window covers. A new window must cover at least one. |
| **Alerts** tile (per monitor) | `suppress.alerts` or `monitors[].suppress.alerts` | `true` / `false` | API: `true` when you send `monitorIds` without `suppress`; `false` when you send a `suppress` object that leaves it out | Holds back down notifications for that monitor while the window runs. |
| **Stats** tile (per monitor) | `suppress.stats` or `monitors[].suppress.stats` | `true` / `false` | API: `false` when you send `monitorIds` without `suppress`, or a `suppress` object that leaves it out | Keeps the window's downtime out of that monitor's uptime statistics. |

Each covered monitor needs at least one of **Alerts** or **Stats** turned on - a monitor that suppresses neither is
refused, because the window would do nothing for it. What each one does in detail:
[What a maintenance window suppresses](/maintenance/what-it-suppresses/).

## Set it up in the app

1. Open **Maintenance** in the sidebar (`/maintenance`) and click **Add**. The editor opens as a side panel.
2. Type a **Short description** - for example "Database migration".
3. Leave **Window active** on.
4. Under **Schedule**, choose **Repeat**: **One time** or **Weekly**. This choice cannot be changed after you save.
5. Set the **Window**:
   - One time: pick the **Starts** and **Ends** date and time.
   - Weekly: pick the **Days of week** and the **From** / **To** time of day. An end time earlier than the start time
     runs past midnight (22:00 to 03:00 is a five-hour night window).
   - Use **Quick add** to extend the length quickly; the **duration** updates as you go.
6. Choose the **Time zone** the times are in (the default is UTC).
7. Leave **Show on status page** on if visitors to your status pages should see the window, or turn it off for a
   purely internal window.
8. Under **Affected Monitors**, find each monitor (use **Search monitors...**) and turn on its **Alerts** tile, its
   **Stats** tile, or both. Clicking a column header toggles that column for every monitor the current search shows
   (up to 1,000 at once).
9. Click **Save**. The panel shows a "Saved" tick; the window appears in the list under **ACTIVE NOW**,
   **UPCOMING - NEXT 7 DAYS**, **RECURRING** or **PAST**.

The list page also shows a **Next 7 days** timeline strip, a search box and a **Status** filter (**All**, **Active**,
**Upcoming**, **Recurring**, **Past**, **Disabled**). Each row has a switch to pause or activate the window, a gear to
edit it and a bin to delete it.

## Do it with the API or MCP

Scopes: `monitor:read` to list and read, `monitor:write` to create, change and cancel. See
[API authentication](/integrations/api-authentication/).

**Create** - `POST /maintenance`:

```bash
curl -X POST https://api2.host-tracker.com/maintenance \
  -H "Authorization: Bearer $HT_TOKEN" \
  -H "Content-Type: application/json" \
  -H "Idempotency-Key: maint-db-migration-2026-10-03" \
  -d '{
    "name": "Database migration",
    "from": 1790000000,
    "durationSec": 7200,
    "timezone": "Europe/Berlin",
    "monitorIds": ["MONITOR_ID_1", "MONITOR_ID_2"],
    "suppress": { "alerts": true, "stats": true }
  }'
```

The answer is `201 Created` with the full window and a `Location: /maintenance/{id}` header.

Different suppression per monitor uses `monitors[]` instead of `monitorIds` + `suppress`:

```json
{
  "name": "Web tier deploy",
  "from": 1790000000,
  "to": 1790001800,
  "monitors": [
    { "monitorId": "WEB_MONITOR_ID", "suppress": { "alerts": true, "stats": true } },
    { "monitorId": "API_MONITOR_ID", "suppress": { "alerts": true, "stats": false } }
  ]
}
```

Other operations:

| Task | Operation |
|---|---|
| List windows | `GET /maintenance` - filters `state` (`scheduled`, `active`, `finished`), `monitor`, `from`, `to`, `updatedSince`; `sort=from` (default) or `created` |
| Read one | `GET /maintenance/{id}` |
| Windows covering one monitor | `GET /monitor/{monitorId}/maintenance` |
| Change a window | `PATCH /maintenance/{id}` - send only what changes |
| Pause without deleting | `PATCH /maintenance/{id}` with `{"enabled": false}` |
| Cancel (delete) | `DELETE /maintenance/{id}` |

The window you read back carries `id`, `name`, `from`, `to`, `durationSec`, `timezone`, `recurrence`, `enabled`,
`state` (`scheduled`, `active` or `finished`), `overlimited`, `showOnStatusPage`, `suppress` (omitted when the
monitors' suppressions differ), `monitorIds`, `monitors[]`, `created` and `updated`.

**MCP:** `create_maintenance` takes `name`, `from` (Unix seconds), `monitorIds` (a comma-separated string of
ids), and `to` or `durationSec`, plus optional `timezone`, `suppressAlerts`, `suppressStats` and `weekDays`
(comma-separated day names). Send both suppression flags when you want both: with only `suppressStats=true` the
window would **not** hold back alerts, because an absent flag counts as `false` once either is sent (with neither,
the window suppresses alerts only). Example - silence alerts and exclude from statistics for 90 minutes:

```
create_maintenance(name="Staging deploy", from=1790632800, durationSec=5400, timezone="UTC",
                   monitorIds="MONITOR_ID_1,MONITOR_ID_2", suppressAlerts=true, suppressStats=true)
```

`update_maintenance`, `list_maintenance` and `delete_maintenance` cover the rest; `update_maintenance` cannot change
the suppression flags, the weekly days or `showOnStatusPage` - use `api_request` with `PATCH /maintenance/{id}` for
those. The MCP tools have no `showOnStatusPage` argument; windows they create show on status pages by default.

### Cover every monitor with a tag

A window has no tag selector: it covers the explicit list of monitors you send (`monitorIds` or `monitors[]`), and
that list is a **snapshot**. Webhooks can be scoped by tag; maintenance windows cannot. To silence "everything
tagged `staging`":

1. List the monitors: `GET /monitor?tag=staging&limit=500` (MCP `list_monitors(tag="staging")`, 50 per page). Follow
   `nextCursor` while `hasMore` is true and collect every `id`. Several tags (`tag=staging,qa`) match monitors that
   carry any of them.
2. Create the window with those ids: `POST /maintenance` with `"monitorIds": [...]` (MCP `create_maintenance` with
   `monitorIds="ID1,ID2,..."`).
3. A monitor that gets the tag **after** the window was created is **not** covered, and a monitor that loses the tag
   stays covered. For a standing weekly window over a tag, re-run steps 1-2 and send the fresh list with
   `PATCH /maintenance/{id}` `{"monitorIds": [...]}` (it replaces the coverage) whenever the tagged set changes.
   Added ids get the suppression the window already applies to all its monitors; if its monitors differ, send
   `suppress` in the same request or it is refused (`422`, pointer `/suppress`).

In the app, **Search monitors...** under **Affected Monitors** matches monitor names and addresses, not tags. If
your tagged monitors share a word in their name or address, search for it and click the **Alerts** or **Stats**
column header to toggle every monitor the search shows; otherwise turn the tiles on one by one, or use the API.

## What happens next

- The window takes effect by itself at its start time. HostTracker's checking engine picks up new and changed
  windows within about a minute, so a window saved a minute before it starts is on time.
- Checks keep running during the window and every result is recorded - see
  [What a maintenance window suppresses](/maintenance/what-it-suppresses/).
- If **Show on status page** is on, the window appears on your status pages as planned maintenance from 7 days
  before it starts.
- When the window ends, suppression stops at once. A monitor that is still down then sends one down alert.
- A `maintenance.ended` [webhook event](/reference/webhook-events/) fires when the window ends on schedule or is
  cancelled while active. There is no "maintenance started" event.

## Editing and cancelling

- Open the window from the **Maintenance** list (gear icon) to change its time, time zone, monitors or suppression.
  Changes to a window that is already running apply within about a minute.
- An edit may remove every monitor; the editor then warns "This window covers no monitors, so it will not suppress
  anything." A new window cannot be saved without a monitor.
- In the API, a `monitorIds` list on `PATCH` replaces the coverage and keeps each remaining monitor's own
  suppression. A `monitors[]` list replaces the whole coverage, and `monitors: []` removes it.
- Deleting (cancelling) an active window makes its monitors alert again immediately. The delete answers with a
  receipt: `{"id": ..., "deleted": true, "type": "maintenance", "name": ..., "wasActive": ..., "cascaded":
  {"monitorSubscriptions": N}}`.

## Limits and gotchas

- **The repeat type is fixed in the editor.** A one-time window cannot become weekly or the other way round there;
  delete it and create a new one. The API can switch it with `PATCH` (`"recurrence": null` or a `recurrence`
  object).
- **Start dates in the past.** The editor refuses a one-time start date before today ("Start date cannot be in the
  past"). The API accepts past dates, which is how you record maintenance after the fact so it shows on your status
  page history - see [Show maintenance on your status page](/maintenance/status-page-display/).
- **No tag scoping.** A window covers a fixed list of monitor ids; there is no `tags` member. See
  [Cover every monitor with a tag](#cover-every-monitor-with-a-tag).
- **Length.** At least 60 seconds (`422 invalid_range`, reason `too_short`); an end before the start is refused
  with reason `inverted`.
- **Validation refusals** answer `422 validation_failed` with a reason: `required` (a missing name, time or
  monitor), `empty_selection` (a monitor with neither alerts nor stats), `conflicting_members` (`to` and
  `durationSec` together, or `monitorIds` and `monitors` together), `unknown_monitor` (an id that is not yours).
  A bad day name is `422 unknown_enum_value`; a zone that cannot be used is `422` with reason
  `unmappable_timezone`.
- **Daylight saving and one-time windows.** The API computes `to` and `state` in real elapsed time, while the
  checking engine follows the wall clock of the window's zone. For a one-time window that spans a clock change, the
  two can disagree by an hour about when suppression ends. Avoid scheduling one-time windows across a DST change,
  or pad them by an hour.
- **Over limit.** A window marked **over limit** (`overlimited: true`) is saved but not applied because of an
  account-level billing restriction. Contact [ht2support@host-tracker.com](mailto:ht2support@host-tracker.com) if
  you see it.

## Related

- [What a maintenance window suppresses](/maintenance/what-it-suppresses/)
- [Recurring windows](/maintenance/recurring/)
- [Show maintenance on your status page](/maintenance/status-page-display/)
- [REST API v2](/integrations/rest-api/)
