The REST API
CompleteStatus's REST API lets you manage monitors, read and acknowledge incidents, and mute alerts during deploys — everything you need to wire monitoring into CI/CD.
Note: API access requires the Business plan or above. API keys can only be created once your organization is on a plan that includes API access — the "Create key" action is unavailable on Free and Pro.
Creating an API key
- Go to /settings/api-keys (you must be an organization owner or admin).
- Give the key a name (e.g. "CI deploy"), pick its scopes, and optionally set an expiry in days.
- Click create. The plaintext token is shown exactly once — copy it now. CompleteStatus stores only a SHA-256 hash, so the token can never be displayed again.
Tokens look like wt_a1b2c3d4_… (a wt_ prefix, 8 random characters, an underscore, then 40 random characters). Revoke a key at any time from the same page; the list shows each key's scopes, expiry and last-used time.
Scopes
| Scope | Grants |
|---|---|
monitors:read |
List monitors, read one, read its check results |
monitors:write |
Create, update, delete, pause, resume, trigger checks |
incidents:read |
List and read incidents |
incidents:write |
Acknowledge and resolve incidents |
maintenance:write |
Create and end maintenance windows |
hooks:manage |
Subscribe, list and delete automation hooks |
mcp |
Use the MCP server |
Grant the minimum a job needs — a deploy pipeline usually only needs maintenance:write.
Authentication
Send the token on every request, either way works:
curl -H "Authorization: Bearer wt_xxxxxxxx_..." https://completestatus.com/api/v1/ping
# or
curl -H "X-Api-Key: wt_xxxxxxxx_..." https://completestatus.com/api/v1/ping
All data is scoped to the key's organization — a key can never see another tenant's data. Verify a key with GET /api/v1/ping (alias /api/v1/me):
{
"authenticated": true,
"organization": { "id": 1, "name": "Acme Inc.", "plan": "business" },
"api_key": { "id": 3, "name": "CI deploy", "prefix": "wt_a1b2c3d4",
"scopes": ["maintenance:write"], "expires_at": null }
}
Rate limits and errors
Each key may make 120 requests per minute; beyond that you get 429 with a Retry-After header. Other errors: 401 (missing/invalid/expired key), 403 (plan doesn't include API access, or the key lacks a required scope), 404 (resource not in your organization), 422 (validation or plan-limit failure, e.g. monitor cap reached or interval below your plan's minimum).
Monitors
GET /api/v1/monitors ?type=&status=&project_id=&per_page= (paginated, default 25)
POST /api/v1/monitors
GET /api/v1/monitors/{id}
PATCH /api/v1/monitors/{id}
DELETE /api/v1/monitors/{id} → 204
POST /api/v1/monitors/{id}/check → 202, queues a check now
POST /api/v1/monitors/{id}/pause
POST /api/v1/monitors/{id}/resume
GET /api/v1/monitors/{id}/results ?limit= (1–200, default 50)
Create a monitor (requires monitors:write):
curl -X POST https://completestatus.com/api/v1/monitors \
-H "Authorization: Bearer $WT_TOKEN" -H "Content-Type: application/json" \
-d '{
"project_id": 1,
"type": "uptime",
"name": "Marketing site",
"target": "https://example.com",
"interval_sec": 300,
"ownership_attested": true
}'
interval_sec must be one of 60, 300, 900, 1800, 3600, 21600, 86400 (and no faster than your plan allows). ownership_attested: true is required on create — you must own or be authorized to monitor the target. type cannot be changed after creation. Response (201):
{
"data": {
"id": 42, "project_id": 1, "type": "uptime",
"name": "Marketing site", "target": "https://example.com",
"status": "pending", "interval_sec": 300,
"consecutive_failures": 0, "consecutive_successes": 0,
"last_checked_at": null, "next_check_at": "2026-07-04T10:00:00+00:00",
"ownership_attested_at": "2026-07-04T10:00:00+00:00",
"created_at": "2026-07-04T10:00:00+00:00", "updated_at": "2026-07-04T10:00:00+00:00"
}
}
Incidents
GET /api/v1/incidents ?status=open|acknowledged|resolved|all (default open) &monitor_id=&per_page=
GET /api/v1/incidents/{id}
POST /api/v1/incidents/{id}/ack
POST /api/v1/incidents/{id}/resolve
Acknowledge from a chatops script (requires incidents:write):
curl -X POST https://completestatus.com/api/v1/incidents/17/ack \
-H "Authorization: Bearer $WT_TOKEN"
Acknowledgements and resolutions are stamped on the incident timeline as api:<key name>.
Maintenance windows (mute during deploys)
POST /api/v1/maintenance-windows
DELETE /api/v1/maintenance-windows/{id} → 204
Mute a project's alerts for the length of a deploy (requires maintenance:write):
curl -X POST https://completestatus.com/api/v1/maintenance-windows \
-H "Authorization: Bearer $WT_TOKEN" -H "Content-Type: application/json" \
-d '{"project_id": 1, "duration_min": 15, "reason": "Deploying v2"}'
duration_min defaults to 30 and is capped at 1440 (24 h). Pass monitor_ids to mute only specific monitors; omit it to cover the whole project. Incidents are still recorded during a window — only the notifications are suppressed. Delete the window to end the mute early.
Client examples
The API is plain JSON over HTTPS, so any HTTP client works — the examples below use each language's most common one. All four snippets do the same thing: authenticate with the Authorization: Bearer header (the X-Api-Key header shown under Authentication above works identically), list monitors, create a heartbeat monitor, then pause and resume it. For heartbeat monitors, interval_sec is the expected ping interval and config.grace_sec the grace period in seconds; the API response doesn't include the ping token, so open the monitor in the UI to copy its ping URL. Keep the token itself in an environment variable, never in code.
PHP
Plain PHP with the curl extension (in a Laravel app, Http::withToken($token)->get(...) does the same in one line). The helper below throws on any HTTP error so a failing call can't be silently ignored.
<?php
function wt(string $method, string $path, ?array $body = null): array
{
$ch = curl_init('https://completestatus.com/api/v1'.$path);
$headers = [
'Authorization: Bearer '.getenv('WT_TOKEN'),
'Accept: application/json',
];
if ($body !== null) {
$headers[] = 'Content-Type: application/json';
curl_setopt($ch, CURLOPT_POSTFIELDS, json_encode($body));
}
curl_setopt_array($ch, [
CURLOPT_CUSTOMREQUEST => $method,
CURLOPT_RETURNTRANSFER => true,
CURLOPT_TIMEOUT => 15,
CURLOPT_HTTPHEADER => $headers,
]);
$json = curl_exec($ch);
$status = (int) curl_getinfo($ch, CURLINFO_RESPONSE_CODE);
curl_close($ch);
if ($json === false || $status >= 400) {
throw new RuntimeException("CompleteStatus API error: HTTP {$status} {$json}");
}
return json_decode($json, true) ?? [];
}
// List monitors (requires monitors:read)
$monitors = wt('GET', '/monitors')['data'];
// Create a heartbeat monitor (requires monitors:write)
$monitor = wt('POST', '/monitors', [
'project_id' => 1,
'type' => 'heartbeat',
'name' => 'Nightly backup',
'target' => 'nightly-backup',
'interval_sec' => 86400,
'config' => ['grace_sec' => 1200],
'ownership_attested' => true,
])['data'];
// Pause and resume
wt('POST', "/monitors/{$monitor['id']}/pause");
wt('POST', "/monitors/{$monitor['id']}/resume");
Python
Using requests with a Session, so the auth header is set once. raise_for_status() turns 4xx/5xx responses into exceptions.
import os
import requests
BASE = "https://completestatus.com/api/v1"
session = requests.Session()
session.headers["Authorization"] = f"Bearer {os.environ['WT_TOKEN']}"
def wt(method, path, json=None):
resp = session.request(method, BASE + path, json=json, timeout=15)
resp.raise_for_status()
return resp.json() if resp.content else {}
# List monitors (requires monitors:read)
monitors = wt("GET", "/monitors")["data"]
# Create a heartbeat monitor (requires monitors:write)
monitor = wt("POST", "/monitors", json={
"project_id": 1,
"type": "heartbeat",
"name": "Nightly backup",
"target": "nightly-backup",
"interval_sec": 86400,
"config": {"grace_sec": 1200},
"ownership_attested": True,
})["data"]
# Pause and resume
wt("POST", f"/monitors/{monitor['id']}/pause")
wt("POST", f"/monitors/{monitor['id']}/resume")
Node.js
Built-in fetch (Node 18+), no dependencies. The wrapper rejects on non-2xx responses so errors surface as exceptions.
const BASE = "https://completestatus.com/api/v1";
async function wt(method, path, body) {
const res = await fetch(BASE + path, {
method,
headers: {
Authorization: `Bearer ${process.env.WT_TOKEN}`,
Accept: "application/json",
...(body && { "Content-Type": "application/json" }),
},
body: body ? JSON.stringify(body) : undefined,
signal: AbortSignal.timeout(15_000),
});
if (!res.ok) {
throw new Error(`CompleteStatus API error: HTTP ${res.status} ${await res.text()}`);
}
return res.status === 204 ? null : res.json();
}
// List monitors (requires monitors:read)
const { data: monitors } = await wt("GET", "/monitors");
// Create a heartbeat monitor (requires monitors:write)
const { data: monitor } = await wt("POST", "/monitors", {
project_id: 1,
type: "heartbeat",
name: "Nightly backup",
target: "nightly-backup",
interval_sec: 86400,
config: { grace_sec: 1200 },
ownership_attested: true,
});
// Pause and resume
await wt("POST", `/monitors/${monitor.id}/pause`);
await wt("POST", `/monitors/${monitor.id}/resume`);
Related guides
- MCP server — the same capabilities, exposed to AI assistants.
- Cron and heartbeat monitors — the public heartbeat ping endpoint.
- Teams and organizations — who can manage API keys.