---
title: Maintenance Windows
description: The real request/response field shape for POST /v1/maintenance-windows and its /updates route.
order: 6
---

`/v1/maintenance-windows` is a thin proxy in front of the same engine that powers the dashboard's
maintenance tab - so its request/response bodies are plain JSON objects, not one of the
reference's typed schemas (see the [API reference](/reference.html) for why those two endpoints
show up there as a bare `object`). This page is the field-by-field shape. For the incident
counterpart, see the [Incidents](/guides/incidents) guide - the two are separate endpoints, not
variants of each other.

## Schedule a maintenance window

```
POST /v1/maintenance-windows
```

```json
{
  "title": "Database upgrade",
  "service_ids": ["5b1e...", "8a2f..."],
  "message": "Upgrading the primary database. Expect brief connection drops.",
  "scheduled_start": "2026-09-10T02:00:00Z",
  "scheduled_end": "2026-09-10T03:00:00Z"
}
```

| Field | Type | Required | Notes |
| --- | --- | --- | --- |
| `title` | string | yes | Non-empty. |
| `service_ids` | array of uuid | yes | Non-empty; every id must belong to your org. |
| `message` | string | yes | The maintenance window's first update. |
| `scheduled_start` | string (RFC 3339) | yes | When the window begins. |
| `scheduled_end` | string (RFC 3339) | yes | Must be after `scheduled_start`. |

`kind` is not a field you send - every maintenance window created through this endpoint is forced
to kind `maintenance` server-side, even if you send something else. `severity` is never accepted
here either - maintenance windows don't carry one; sending it is rejected as invalid.

A `201` response is the full maintenance window, including its initial update:

```json
{
  "id": "c4e1...",
  "kind": "maintenance",
  "title": "Database upgrade",
  "status": "scheduled",
  "severity": null,
  "created_by": null,
  "created_at": "2026-09-05T12:00:00Z",
  "resolved_at": null,
  "scheduled_start": "2026-09-10T02:00:00Z",
  "scheduled_end": "2026-09-10T03:00:00Z",
  "service_ids": ["5b1e...", "8a2f..."],
  "display_id": "MNT-7",
  "updated_at": "2026-09-05T12:00:00Z",
  "auto_managed": false,
  "updates": [
    { "id": "...", "body": "Upgrading the primary database...", "status_at_update": "scheduled", "created_at": "..." }
  ]
}
```

`created_by` is always `null` for a maintenance window created through the API - there's no human
session behind an API key, so it's attributed to the system.

## Post an update (including completing it)

```
POST /v1/maintenance-windows/{id}/updates
```

```json
{ "body": "Upgrade underway.", "status": "in_progress" }
```

| Field | Type | Required | Notes |
| --- | --- | --- | --- |
| `body` | string | yes | Non-empty. |
| `status` | string | yes | One of `scheduled`, `in_progress`, `completed`. |

There's no separate "complete" endpoint. **Completing a maintenance window is posting an update
with `"status": "completed"`** - that's the one status this API treats as terminal (it sets
`resolved_at` and stops counting the window as active).

```json
{ "body": "Upgrade complete, all services back to normal.", "status": "completed" }
```

## Listing and reading

```
GET /v1/maintenance-windows?section=active
GET /v1/maintenance-windows/{id}
```

`section` is required on the list endpoint: `active` or `resolved` (a completed window counts as
resolved). Optional filters: `q` (searches title and display id, e.g. `MNT-7`), one or more
`service_id` params, and `page`/`limit` for pagination. There's no `severity` filter - maintenance
windows don't have one.
