---
title: "Create Maintenance Window"
description: "Creates a new maintenance window. Accepts a single monitor (legacy), multiple monitors, tag-based (including org-wide), or customer-level targeting."
---

`POST /api/maintenance-windows`

## Body

```json
{
  "name": "Database migration",
  "startTime": "2026-07-10T22:00:00.000Z",
  "endTime": "2026-07-11T01:00:00.000Z",
  "targets": [
    { "type": "website", "id": 101 },
    { "type": "icmp", "id": 5 }
  ],
  "tagIds": [7],
  "description": "Planned schema migration",
  "isRecurring": false,
  "isActive": true
}
```

### Required fields

- `name` (string): A friendly label for the window.
- `startTime` (ISO 8601 datetime): When the window begins.
- `endTime` (ISO 8601 datetime): When the window ends. Must be after `startTime`.
- **At least one targeting field** (see below).

### Target selection (mutually exclusive modes)

Exactly one targeting mode must be used. `targets` and `tagIds` may be combined within the multi-monitor mode.

| Field | Type | Description |
|-------|------|-------------|
| `websiteId` | number | Legacy: single website monitor |
| `icmpMonitorId` | number | Legacy: single ICMP monitor |
| `smtpMonitorId` | number | Legacy: single SMTP monitor |
| `sshMonitorId` | number | Legacy: single SSH monitor |
| `ftpMonitorId` | number | Legacy: single FTP monitor |
| `imapPopMonitorId` | number | Legacy: single IMAP/POP monitor |
| `dnsMonitorId` | number | Legacy: single DNS monitor |
| `customerIpId` | number | Legacy: single customer IP |
| `customerDomainId` | number | Legacy: single customer domain |
| `targets` | `{ type, id }[]` | Multi-monitor: `type` is one of `website` \| `dns` \| `icmp` \| `smtp` \| `ssh` \| `ftp` \| `imap_pop` |
| `tagIds` | number[] | Tag-based: see [Tag-only (org-wide) mode](#tag-only-org-wide-mode) below |
| `customerId` | number | Customer-level: covers every monitor of that customer |

### Tag-only (org-wide) mode

Sending `tagIds` **without** `customerId`, `targets`, or any legacy field creates an **org-wide** maintenance window. The window dynamically covers every monitor across the entire organization that currently carries the specified tags, across all customers.

```json
{
  "name": "Infra tag freeze",
  "startTime": "2026-07-10T22:00:00.000Z",
  "endTime": "2026-07-11T01:00:00.000Z",
  "tagIds": [7]
}
```

- **Requires admin or editor role.** A readonly user attempting this receives `403 Forbidden`.
- The tag membership is resolved live at check time: monitors tagged after the window is created are automatically covered.
- All `tagIds` must belong to the same organization; mixing tags from different organizations returns `400 mixedTagOrganizations`.

### Combination rules

- **Minimum one** targeting field is required. Omitting all is a Zod validation error (standard `400` with a validation body, not a `data.code` error).
- **`customerId` (customer-level mode)** cannot be combined with `targets`, `tagIds`, or any legacy field. Combining them is a Zod validation error (standard `400`).
- All monitors in `targets` must belong to the **same customer**; mixing customers returns `{ data: { code: "mixedCustomers" } }`.
- All `tagIds` must belong to the same organization; mixing organizations returns `{ data: { code: "mixedTagOrganizations" } }`.

### Optional fields

| Field | Type | Description |
|-------|------|-------------|
| `description` | string | Free-text notes (shown in history). |
| `isRecurring` | boolean | Default `false`. Set to `true` to enable `recurrencePattern`. |
| `recurrencePattern` | object | Required when `isRecurring` is `true`. See [Recurrence](#recurrence). |
| `isActive` | boolean | Default `true`. Set to `false` to create a disabled window. |
| `timezone` | string | Default `"UTC"`. The IANA zone (e.g. `"Europe/Berlin"`) the recurrence pattern is interpreted in. Case-insensitive spellings and legacy IANA link names (e.g. `"gmt"`, `"Zulu"`) are accepted and stored under the runtime's canonical name, `"utc"` and `"Zulu"` are both stored as `"UTC"`. A raw UTC offset (e.g. `"+05:00"`) is rejected: it carries no daylight-saving rule, so it cannot express what a zone name does. |

### Recurrence

```json
{
  "frequency": "weekly",
  "interval": 1,
  "daysOfWeek": [1, 3],
  "dayOfMonth": null,
  "endRecurrenceDate": "2026-12-31"
}
```

| Field | Values |
|-------|--------|
| `frequency` | `daily` \| `weekly` \| `monthly` |
| `interval` | Integer ≥ 1 (repeat every N periods) |
| `daysOfWeek` | Array of `0`-`6` (0 = Sunday); used when `frequency` is `weekly` |
| `dayOfMonth` | `1`-`31`; used when `frequency` is `monthly` |
| `endRecurrenceDate` | ISO date string; optional, stops recurrence after this date |

## Dynamic tag behavior

Tag coverage is resolved live by the monitoring worker at check time. A monitor tagged *after* the window is created is automatically covered: no window update needed. Removing a tag from a monitor drops it from coverage immediately.

## Example (cURL)

```bash
BASE_URL="https://uptimeify.io"
TOKEN="<your-api-token>"

curl -X POST "$BASE_URL/api/maintenance-windows" \
  -H "Authorization: Bearer $TOKEN" \
  -H "Content-Type: application/json" \
  -d '{
    "name": "Database migration",
    "startTime": "2026-07-10T22:00:00.000Z",
    "endTime": "2026-07-11T01:00:00.000Z",
    "targets": [
      { "type": "website", "id": 101 }
    ]
  }'
```

## Response

```json
{
  "id": 42,
  "publicId": "aaaaaaaa-aaaa-4aaa-8aaa-aaaaaaaaaaaa",
  "organizationId": 10,
  "customerId": 1,
  "name": "Database migration",
  "description": null,
  "startTime": "2026-07-10T22:00:00.000Z",
  "endTime": "2026-07-11T01:00:00.000Z",
  "isRecurring": false,
  "recurrencePattern": null,
  "isActive": true,
  "timezone": "UTC",
  "targets": [
    { "type": "website", "id": 101 }
  ],
  "tags": [],
  "websiteId": null,
  "icmpMonitorId": null,
  "smtpMonitorId": null,
  "sshMonitorId": null,
  "ftpMonitorId": null,
  "imapPopMonitorId": null,
  "dnsMonitorId": null,
  "customerIpId": null,
  "customerDomainId": null,
  "createdAt": "2026-06-29T10:00:00.000Z",
  "updatedAt": "2026-06-29T10:00:00.000Z"
}
```

## Common errors

| Status | Description |
|--------|-------------|
| `400` (validation) | No targeting field provided, `customerId` combined with other target fields, or `timezone` is a raw UTC offset or not a zone name the runtime recognizes. These are Zod request-validation errors; the response body is a standard validation error, **not** `{ data: { code } }`. |
| `400` `{ data: { code: "mixedCustomers" } }` | `targets` contains monitors from different customers. |
| `400` `{ data: { code: "mixedTagOrganizations" } }` | `tagIds` contains tags from different organizations. |
| `401 Unauthorized` | Not logged in. |
| `403 Forbidden` | You cannot access the organization, the target is out of scope (global supporter accounts cannot create maintenance windows), or you are a readonly user attempting an org-wide tag-only window. |
| `404` `{ data: { code: "tagNotFound" } }` | A `tagId` does not exist in your organization. |
