Plan a maintenance window
const url = 'https://app.tunnelhq.com/api/v1/maintenance';const options = { method: 'POST', headers: { 'X-Project-Id': 'prj_12', 'X-API-Key': '<X-API-Key>', 'Content-Type': 'application/json' }, body: '{"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"]}'};
try { const response = await fetch(url, options); const data = await response.json(); console.log(data);} catch (error) { console.error(error);}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:startandendareYYYY-MM-DDTHH:mmintimezone. It ends after it starts and lasts up to 31 days.repeat: weekly:startandendareHH:mm, anddaysnames the weekdays (["mon", "thu"]; all seven for every day). Anendearlier thanstartruns 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.
Authorizations
Section titled “Authorizations”Parameters
Section titled “ Parameters ”Header Parameters
Section titled “Header Parameters”The project, as prj_12 or 12. A workspace key can only reach its own workspace’s projects.
Example
prj_12Request Bodyrequired
Section titled “Request Bodyrequired”object
What people read, such as “Frankfurt servers upgrade”.
A note shown with it.
An IANA time zone, such as Europe/Berlin or UTC. start and end are in it.
YYYY-MM-DDTHH:mm for once, HH:mm for weekly.
Like start. A one-time window lasts up to 31 days; a weekly one may end after midnight.
For weekly: the days it runs. Numbers 0 (Sunday) to 6 work too.
For scope: monitors: the monitors and groups it covers, all in this project.
false makes it paused from the start.
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" ]}Every week
{ "title": "Nightly backup", "description": "Websites may be slow.", "timezone": "Asia/Karachi", "repeat": "weekly", "start": "02:00", "end": "02:30", "days": [ "mon", "wed", "fri" ], "scope": "uptime"}Responses
Section titled “ Responses ”The new window.
object
The note shown with it; empty for none.
false while it is paused.
running now, scheduled for later, ended, or paused. A weekly window can read unknown for a moment while the server schedules it.
YYYY-MM-DDTHH:mm for once, HH:mm for weekly, in timezone.
Like start.
The weekdays a weekly window runs; empty for once.
The monitors and groups it covers, for scope: monitors; empty otherwise.
Examples
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" }}Headers
Section titled “Headers”Requests the workspace’s plan allows per minute.
Requests left in the current minute.
Unix time, in seconds, when the current minute ends.
A field is unknown or its value isn’t allowed.
object
The HTTP status, repeated.
What went wrong.
More detail, on some errors.
Seconds to wait, on the 429 for wrong keys.
Examples
{ "error": true, "code": 400, "message": "Unknown field: x. The fields are title, description, timezone, repeat, start, end, days, scope, monitor_ids, active."}{ "error": true, "code": 400, "message": "Give the maintenance a name, up to 150 characters."}{ "error": true, "code": 400, "message": "Choose a time zone."}{ "error": true, "code": 400, "message": "Choose once, every day or every week."}{ "error": true, "code": 400, "message": "Choose when it starts and ends."}{ "error": true, "code": 400, "message": "It has to end after it starts."}{ "error": true, "code": 400, "message": "A maintenance can last up to 31 days."}{ "error": true, "code": 400, "message": "It has to end at a different time than it starts."}{ "error": true, "code": 400, "message": "Choose at least one day."}{ "error": true, "code": 400, "message": "scope must be one of monitors, tunnels, uptime, all."}{ "error": true, "code": 400, "message": "Choose at least one monitor or group."}{ "error": true, "code": 400, "message": "monitor_ids must be a list of monitor ids, such as [\"srv_42\"]."}{ "error": true, "code": 400, "message": "Some of monitor_ids are not monitors of this project."}{ "error": true, "code": 400, "message": "The note can be up to 1,000 characters."}{ "error": true, "code": 400, "message": "Paused has to be on or off."}The API key is missing, wrong, expired, switched off, or no longer valid.
object
The HTTP status, repeated.
What went wrong.
More detail, on some errors.
Seconds to wait, on the 429 for wrong keys.
Examples
{ "error": true, "code": 401, "message": "API key is required. Provide X-API-Key header or Authorization: Bearer <key>"}{ "error": true, "code": 401, "message": "Invalid or expired API key"}{ "error": true, "code": 401, "message": "API key has expired"}{ "error": true, "code": 401, "message": "API key owner account is inactive"}{ "error": true, "code": 401, "message": "This API key's workspace access has been removed"}The role can’t mute and pause monitors.
object
The HTTP status, repeated.
What went wrong.
More detail, on some errors.
Seconds to wait, on the 429 for wrong keys.
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.
object
The HTTP status, repeated.
What went wrong.
More detail, on some errors.
Seconds to wait, on the 429 for wrong keys.
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.
object
Seconds to wait. Absent for the monthly limit.
object
The HTTP status, repeated.
What went wrong.
More detail, on some errors.
Seconds to wait, on the 429 for wrong keys.
Examples
{ "error": "Rate limit exceeded", "message": "Per-minute limit of 60 requests exceeded", "retry_after": 60}{ "error": "Daily limit exceeded", "message": "Daily limit of 25000 requests exceeded", "retry_after": 3600}{ "error": "Monthly limit exceeded", "message": "Monthly limit of 250000 requests exceeded"}{ "error": true, "code": 429, "message": "Too many failed API key attempts from this address. Wait a few seconds and try again.", "retry_after": 6}Headers
Section titled “Headers”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.
object
The HTTP status, repeated.
What went wrong.
More detail, on some errors.
Seconds to wait, on the 429 for wrong keys.
Example
{ "error": true, "code": 500, "message": "Internal server error"}