# Maintenance windows API

6 operations under /api/v1. Errors are RFC 9457 problem+json with a stable `code` field. The complete schemas are in https://nebulactrl.com/api/v1/openapi.json.

## GET /maintenance-windows

List maintenance windows

Scheduled maintenance, latest start first.

Authentication: session cookie or `Authorization: Bearer nct_…` token.

Parameters:
- `phase` (query, enum: upcoming | active | past): upcoming: not started; active: running; past: ended or canceled
- `pageId` (query, string (uuid)): Only windows posted to this status page
- `cursor` (query, string): Opaque cursor from a previous page's nextCursor
- `limit` (query, integer (int64)): Page size (default 50, max 200)

Response 200: OK (PageMaintenanceWindowView)
  - `items` (MaintenanceWindowView[] | null, required)
  - `nextCursor` (string): Present when more items exist
Errors: 400 Bad Request; 401 Unauthorized; 403 Forbidden; 422 Unprocessable Entity; 500 Internal Server Error

## POST /maintenance-windows

Schedule a maintenance window

While the window is active its services are in maintenance: no uptime alerts, and their checks are left out of the availability. The pages it is posted to announce it from 14 days ahead. 422 `invalid_window` unless endsAt is after startsAt, `window_in_past` when it ends in the past. Requires the admin scope.

Authentication: session cookie or `Authorization: Bearer nct_…` token.

Parameters:
- `Idempotency-Key` (header, string): Client-chosen key (<=255 chars). A retry with the same key replays the original response for 24 hours.

Request body (application/json, required): CreateMaintenanceInputBody
  - `description` (string)
  - `endsAt` (string (date-time), required): After startsAt and in the future
  - `pageIds` (string[] | null): The status pages that announce it
  - `serviceIds` (string[] | null): The services in maintenance while the window is active
  - `startsAt` (string (date-time), required)
  - `title` (string, required)

Response 201: Created (MaintenanceWindowView)
  - `canceledAt` (string (date-time) | null, required)
  - `createdAt` (string (date-time), required)
  - `description` (string, required)
  - `endsAt` (string (date-time), required)
  - `id` (string, required)
  - `pageIds` (string[] | null, required): The status pages that announce the window
  - `phase` (enum: upcoming | active | ended | canceled, required)
  - `serviceIds` (string[] | null, required): The services in maintenance while it is active: no uptime alerts, excluded from availability
  - `startsAt` (string (date-time), required)
  - `title` (string, required)
  - `updatedAt` (string (date-time), required)
Errors: 401 Unauthorized; 403 Forbidden; 404 Not Found; 409 Conflict; 422 Unprocessable Entity (`invalid_window`); 500 Internal Server Error

## GET /maintenance-windows/{windowId}

Get a maintenance window

Authentication: session cookie or `Authorization: Bearer nct_…` token.

Parameters:
- `windowId` (path, string, required)

Response 200: OK (MaintenanceWindowView)
  - `canceledAt` (string (date-time) | null, required)
  - `createdAt` (string (date-time), required)
  - `description` (string, required)
  - `endsAt` (string (date-time), required)
  - `id` (string, required)
  - `pageIds` (string[] | null, required): The status pages that announce the window
  - `phase` (enum: upcoming | active | ended | canceled, required)
  - `serviceIds` (string[] | null, required): The services in maintenance while it is active: no uptime alerts, excluded from availability
  - `startsAt` (string (date-time), required)
  - `title` (string, required)
  - `updatedAt` (string (date-time), required)
Errors: 401 Unauthorized; 403 Forbidden; 422 Unprocessable Entity; 500 Internal Server Error

## PATCH /maintenance-windows/{windowId}

Update a maintenance window

Omitted fields stay. 409 `window_ended` / `window_canceled` for a window that is over. Requires the admin scope.

Authentication: session cookie or `Authorization: Bearer nct_…` token.

Parameters:
- `windowId` (path, string, required)

Request body (application/json, required): UpdateMaintenanceInputBody
  - `description` (string)
  - `endsAt` (string (date-time))
  - `pageIds` (string[] | null): Replaces the pages; [] clears them
  - `serviceIds` (string[] | null): Replaces the services; [] clears them
  - `startsAt` (string (date-time))
  - `title` (string)

Response 200: OK (MaintenanceWindowView)
  - `canceledAt` (string (date-time) | null, required)
  - `createdAt` (string (date-time), required)
  - `description` (string, required)
  - `endsAt` (string (date-time), required)
  - `id` (string, required)
  - `pageIds` (string[] | null, required): The status pages that announce the window
  - `phase` (enum: upcoming | active | ended | canceled, required)
  - `serviceIds` (string[] | null, required): The services in maintenance while it is active: no uptime alerts, excluded from availability
  - `startsAt` (string (date-time), required)
  - `title` (string, required)
  - `updatedAt` (string (date-time), required)
Errors: 401 Unauthorized; 403 Forbidden; 404 Not Found; 409 Conflict (`window_ended`); 422 Unprocessable Entity; 500 Internal Server Error

## DELETE /maintenance-windows/{windowId}

Delete a maintenance window

Deletes the window. Requires the admin scope.

Authentication: session cookie or `Authorization: Bearer nct_…` token.

Parameters:
- `windowId` (path, string, required)

Response 204: No Content
Errors: 401 Unauthorized; 403 Forbidden; 422 Unprocessable Entity; 500 Internal Server Error

## POST /maintenance-windows/{windowId}/cancel

Cancel a maintenance window

Ends the window now (sets `canceledAt`): its services leave maintenance and the pages stop announcing it. Canceling a canceled window changes nothing; 409 `window_ended` for one that has already ended. Requires the admin scope.

Authentication: session cookie or `Authorization: Bearer nct_…` token.

Parameters:
- `windowId` (path, string, required)

Response 200: OK (MaintenanceWindowView)
  - `canceledAt` (string (date-time) | null, required)
  - `createdAt` (string (date-time), required)
  - `description` (string, required)
  - `endsAt` (string (date-time), required)
  - `id` (string, required)
  - `pageIds` (string[] | null, required): The status pages that announce the window
  - `phase` (enum: upcoming | active | ended | canceled, required)
  - `serviceIds` (string[] | null, required): The services in maintenance while it is active: no uptime alerts, excluded from availability
  - `startsAt` (string (date-time), required)
  - `title` (string, required)
  - `updatedAt` (string (date-time), required)
Errors: 401 Unauthorized; 403 Forbidden; 404 Not Found; 409 Conflict (`window_ended`); 422 Unprocessable Entity; 500 Internal Server Error
