Skip to content

Plan a maintenance window

POST
/maintenance
curl --request POST \
--url https://app.tunnelhq.com/api/v1/maintenance \
--header 'Content-Type: application/json' \
--header 'X-API-Key: <X-API-Key>' \
--header 'X-Project-Id: prj_12' \
--data '{ "title": "Frankfurt servers upgrade", "timezone": "Europe/Berlin", "repeat": "once", "start": "2026-11-12T02:00", "end": "2026-11-12T04:00", "scope": "monitors", "monitor_ids": [ "srv_42", "srv_43" ] }'

Plans a window, once or on chosen weekdays, from a time to a time in an IANA time zone. While it runs, checks of the monitors it covers pause, their alerts stay quiet, no incidents open, and the time doesn’t count against uptime. Every status page with a covered service shows it while it runs, and up to 7 days before it starts.

  • repeat: once: start and end are YYYY-MM-DDTHH:mm in timezone. It ends after it starts and lasts up to 31 days.
  • repeat: weekly: start and end are HH:mm, and days names the weekdays (["mon", "thu"]; all seven for every day). An end earlier than start runs past midnight.

scope is what it covers: monitors (the monitors and groups in monitor_ids; a group covers its members), tunnels (every tunnel), uptime (every Uptime check) or all. Tunnels and checks added later are covered by tunnels, uptime and all too.

It needs a role that can mute and pause monitors (Owner, Admin and Manager have it by default).

Need an API key? See Getting an API key.

X-Project-Id
required
string

The project, as prj_12 or 12. A workspace key can only reach its own workspace’s projects.

Example
prj_12
Media typeapplication/json
object
title
required

What people read, such as “Frankfurt servers upgrade”.

string
>= 1 characters <= 150 characters
description

A note shown with it.

string
<= 1000 characters
timezone
required

An IANA time zone, such as Europe/Berlin or UTC. start and end are in it.

string
repeat
required
string
Allowed values: once weekly
start
required

YYYY-MM-DDTHH:mm for once, HH:mm for weekly.

string
end
required

Like start. A one-time window lasts up to 31 days; a weekly one may end after midnight.

string
days

For weekly: the days it runs. Numbers 0 (Sunday) to 6 work too.

Array<string>
>= 1 items <= 7 items
Allowed values: sun mon tue wed thu fri sat
scope
required
string
Allowed values: monitors tunnels uptime all
monitor_ids

For scope: monitors: the monitors and groups it covers, all in this project.

Array<string>
>= 1 items <= 1000 items
active

false makes it paused from the start.

boolean
default: true
Examples

Once

{
"title": "Frankfurt servers upgrade",
"timezone": "Europe/Berlin",
"repeat": "once",
"start": "2026-11-12T02:00",
"end": "2026-11-12T04:00",
"scope": "monitors",
"monitor_ids": [
"srv_42",
"srv_43"
]
}

The new window.

Media typeapplication/json
object
id
required
string
title
required
string
description
required

The note shown with it; empty for none.

string
active
required

false while it is paused.

boolean
status
required

running now, scheduled for later, ended, or paused. A weekly window can read unknown for a moment while the server schedules it.

string
Allowed values: running scheduled ended paused
repeat
required
string
Allowed values: once weekly
start
required

YYYY-MM-DDTHH:mm for once, HH:mm for weekly, in timezone.

string
end
required

Like start.

string
days
required

The weekdays a weekly window runs; empty for once.

Array<string>
Allowed values: sun mon tue wed thu fri sat
timezone
required
string
scope
required
string
Allowed values: monitors tunnels uptime all
monitor_ids
required

The monitors and groups it covers, for scope: monitors; empty otherwise.

Array<string>
current
required
One of:
object
start
required

In UTC.

string format: date-time
end
required

In UTC.

string format: date-time
next
required
One of:
object
start
required

In UTC.

string format: date-time
end
required

In UTC.

string format: date-time
Examples
Exampledefault

A one-time window

{
"id": "mnt_4",
"title": "Frankfurt servers upgrade",
"description": "",
"active": true,
"status": "scheduled",
"repeat": "once",
"start": "2026-11-12T02:00",
"end": "2026-11-12T04:00",
"days": [],
"timezone": "Europe/Berlin",
"scope": "monitors",
"monitor_ids": [
"srv_42",
"srv_43"
],
"current": null,
"next": {
"start": "2026-11-12T01:00:00.000Z",
"end": "2026-11-12T03:00:00.000Z"
}
}
X-RateLimit-Limit
integer

Requests the workspace’s plan allows per minute.

X-RateLimit-Remaining
integer

Requests left in the current minute.

X-RateLimit-Reset
integer

Unix time, in seconds, when the current minute ends.

A field is unknown or its value isn’t allowed.

Media typeapplication/json
object
error
required
boolean
code
required

The HTTP status, repeated.

integer
message
required

What went wrong.

string
details

More detail, on some errors.

retry_after

Seconds to wait, on the 429 for wrong keys.

integer
Examples
{
"error": true,
"code": 400,
"message": "Unknown field: x. The fields are title, description, timezone, repeat, start, end, days, scope, monitor_ids, active."
}

The API key is missing, wrong, expired, switched off, or no longer valid.

Media typeapplication/json
object
error
required
boolean
code
required

The HTTP status, repeated.

integer
message
required

What went wrong.

string
details

More detail, on some errors.

retry_after

Seconds to wait, on the 429 for wrong keys.

integer
Examples
{
"error": true,
"code": 401,
"message": "API key is required. Provide X-API-Key header or Authorization: Bearer <key>"
}

The role can’t mute and pause monitors.

Media typeapplication/json
object
error
required
boolean
code
required

The HTTP status, repeated.

integer
message
required

What went wrong.

string
details

More detail, on some errors.

retry_after

Seconds to wait, on the 429 for wrong keys.

integer
Example
{
"error": true,
"code": 403,
"message": "Planning maintenance needs a role that can mute and pause monitors in this project."
}

No active project has this ID.

Media typeapplication/json
object
error
required
boolean
code
required

The HTTP status, repeated.

integer
message
required

What went wrong.

string
details

More detail, on some errors.

retry_after

Seconds to wait, on the 429 for wrong keys.

integer
Example
{
"error": true,
"code": 404,
"message": "Project not found or inactive"
}

A usage limit was reached, or this IP address made too many requests with a wrong key.

Usage limits are per workspace and plan: per minute (Retry-After: 60), per day (Retry-After: 3600), and per month (no Retry-After). They use their own body shape. An IP address may send 10 wrong keys a minute; one more is allowed every 6 seconds. While that allowance is used up, every request from the address is refused, even one with a valid key.

Media typeapplication/json
One of:
object
error
required
string
Allowed values: Rate limit exceeded Daily limit exceeded Monthly limit exceeded
message
required
string
retry_after

Seconds to wait. Absent for the monthly limit.

integer
Examples
{
"error": "Rate limit exceeded",
"message": "Per-minute limit of 60 requests exceeded",
"retry_after": 60
}
Retry-After
integer

Seconds to wait. 60 for the per-minute limit, 3600 for the daily limit, 6 after wrong keys. Absent for the monthly limit.

Something failed on TunnelHQ’s side.

Media typeapplication/json
object
error
required
boolean
code
required

The HTTP status, repeated.

integer
message
required

What went wrong.

string
details

More detail, on some errors.

retry_after

Seconds to wait, on the 429 for wrong keys.

integer
Example
{
"error": true,
"code": 500,
"message": "Internal server error"
}