Uptimia API v2 (2.0.0)
Download OpenAPI specification:Download
The Uptimia REST API lets you do everything you can do in the web app — manage monitors, contacts, status pages, maintenance windows, reports and teams, and read incidents, logs and uptime statistics — over HTTPS, returning JSON.
This is API v2, the current version. The v1 API remains available but is legacy; new integrations should use v2.
Uptimia is a website uptime and performance monitoring tool. With the API you
can access the features you normally use in your browser via simple HTTPS calls.
The API supports the standard GET, POST, PATCH and DELETE methods.
Send requests to the host where your account lives:
https://www.uptimia.com/api/v2/for accounts created at www.uptimia.comhttps://app.uptimia.com/api/v2/for accounts created at app.uptimia.com (AppSumo)
An API key works only on the host that issued it. A key from app.uptimia.com
sent to www.uptimia.com, or the other way round, returns 401 invalid_api_key.
Every call needs an API key — see Authentication below — then browse the available endpoints in the reference that follows.
The Uptimia API uses Bearer authentication. Include your API key in an
Authorization header on every request:
Authorization: Bearer YOUR_API_KEY
- Log in to your Uptimia account and open Settings → API Keys.
- Click Create API key and give it a name (for your own reference).
- Copy the key that is shown — for security you won't be able to see it again.
- Use the key in the
Authorizationheader of your requests.
Keep your API key secret. Don't share it or commit it to source control. If you think a key may be compromised, delete it and create a new one.
Pass the key as a Bearer token. For example, with cURL:
curl "https://www.uptimia.com/api/v2/uptime" \
-H "Authorization: Bearer YOUR_API_KEY"
For an app.uptimia.com account, use https://app.uptimia.com/api/v2/uptime.
Failed authentication returns 401 Unauthorized with the same error
envelope as every other API error (status / code / message).
A request with no Authorization header at all is rejected by the
endpoint itself:
{
"status": "error",
"code": "unauthorized",
"message": "Authentication required"
}
A request that does carry an Authorization header, but whose key is
unknown, revoked, malformed (anything other than Bearer YOUR_API_KEY),
belongs to a deleted account, was minted for a team seat that no longer
exists, or was issued on the other host (www.uptimia.com vs app.uptimia.com),
is rejected before the endpoint runs:
{
"status": "error",
"code": "invalid_api_key",
"message": "API key invalid",
"error_code": "invalid_api_key"
}
error_code repeats code for clients written against older versions of
these docs. Read code, and treat any 401 as "not authenticated".
Every monitor carries a contacts array naming who gets alerted. Each entry
is a reference, not an address:
{ "id": 41930, "is_team": 0, "is_user": 0, "is_operator": 1, "is_individual_contact": 0 }
The flag decides which kind of thing id refers to, and each kind lives in
its own table with its own id sequence:
| Flag set | id refers to |
Resolve it with | Who is actually alerted |
|---|---|---|---|
is_team |
a team | GET /api/v2/teams |
the contacts of every member of that team |
is_user |
the account owner | GET /api/v2/users |
the contacts the owner owns (operator_id 0) |
is_operator |
a team-member seat | GET /api/v2/users/{id} |
every contact that seat owns |
is_individual_contact |
one contact | GET /api/v2/contacts/{id} |
that one contact |
Ids are unique per kind, not across kinds. A team and a contact can both have id
41930and mean completely different things. Always read the flags before resolving an id — never guess the endpoint.
Only is_individual_contact names a single address. The other three name a
seat or group, which is expanded when the alert is sent. A monitor showing
just one is_operator entry may well page a dozen addresses — every contact
belonging to that seat, including ones added after the monitor was configured.
So a short contacts array does not mean few recipients. To see the
addresses a monitor resolves to, use:
GET /api/v2/contacts/selected/{monitorId}/{type}
It returns your whole address book with an is_selected flag on each contact,
already accounting for team, owner and seat expansion, plus an operator_id
telling you which seat owns each contact.
Alerts go only to verified e-mail addresses and phone numbers. There is no
setting to turn this off. A new e-mail, SMS or phone-call contact, or one whose
address or number changes, starts unverified (is_confirmed 0, verified
false) and gets a verification message at once: an e-mail with a confirmation
link, or an SMS with a 6-digit code and a link. Verification SMS are free.
Integrations need no verification.
Until the link is used or the code is entered, every alert to the contact is
recorded as skipped / contact_unconfirmed in its delivery record, and an
escalation step counts it as not reached.
| Call | What it does |
|---|---|
POST /api/v2/contacts/{id}/send-verification |
send the message again (also at /request-confirmation) |
POST /api/v2/contacts/{id}/verify |
enter the SMS code (also at /confirm) |
POST /api/v2/contacts/{id}/test |
send a test alert to a verified contact |
GET /api/v2/contacts/verification-summary |
unverified contacts that active monitors alert |
Your own login address is verified without a message once your account e-mail is verified. A link works for 7 days, and one reminder goes out after 24 hours.
API v1. /api/v1/contacts keeps its paths and response shapes. A contact
created or changed through v1 follows the same rule: it starts unverified, the
verification message goes out, and it receives no alert until it is verified.
v1 responses do not carry the verification fields; read them from
GET /api/v2/contacts/{id}.
Instead of polling incidents, subscribe a URL to an event and Uptimia POSTs to it the moment the event happens. This is the mechanism behind the Uptimia app in Zapier, and it is open to any tool that can receive an HTTPS request — Make, n8n, Pipedream, or your own service.
| Event | Fires when | Extra keys |
|---|---|---|
monitor.down |
a check is confirmed down (after its alert delay) | — |
monitor.up |
the check passes again | — |
expiry.warning |
an SSL certificate or a domain is nearing expiry | kind: ssl or domain |
incident.acknowledged |
someone acknowledges the incident | acknowledged_by |
monitor.down respects the monitor's alert delay ("alert after N
seconds down"), the same as your contacts: an outage that recovers before
the delay sends neither monitor.down nor monitor.up. An already-expired
certificate or domain is a monitor.down, not an expiry.warning.
POST /api/v2/hooks create a subscription
GET /api/v2/hooks list your subscriptions
DELETE /api/v2/hooks/{id} remove one
GET /api/v2/hooks/events?event=monitor.down&limit=3
recent real events, in the delivery shape (for samples)
POST /api/v2/hooks takes a JSON body with event and target_url, and
optionally monitor_type and/or monitor_id to narrow the subscription to
one monitor family or one monitor:
{
"event": "monitor.down",
"target_url": "https://example.com/uptimia/down",
"monitor_type": "uptime",
"monitor_id": 4471
}
It answers 201 Created with the subscription, including its secret —
returned once, on creation, and never again. Store it: every delivery is
signed with it. Creating a subscription that already exists (same event,
target URL and filters) answers 409 Conflict with the existing
subscription's id, so a client that retries a create never ends up with
two. If that existing subscription was disabled (see below), it is replaced
instead: the create answers 201 Created with a new id and secret.
The target URL must be https and publicly reachable; private, loopback and
otherwise reserved addresses are refused. A Zapier target must be on
hooks.zapier.com.
Every delivery is an HTTP POST with a JSON body and three headers:
| Header | Value |
|---|---|
X-Uptimia-Event |
the event name, e.g. monitor.down |
X-Uptimia-Delivery |
the id of this queued delivery — retries of it carry the same id |
X-Uptimia-Signature |
sha256=<hex> — HMAC-SHA256 of the raw request body, keyed with the subscription's secret |
The body carries the same keys for every event: event_id (stable per
event — a retried delivery reuses it, so deduplicate on this), event,
occurred_at, then the monitor (id, monitor_type, monitor_name,
monitor_unique_id, monitor_url, monitor_notes, cp_url — the
monitor's page in the control panel), the incident (monitor_status,
severity, incident_start_time, incident_end_time,
incident_duration_seconds, error_reason), the one-line message, kind
and acknowledged_by. kind is ssl or domain on expiry.warning and
empty otherwise (the days remaining are in message and error_reason,
e.g. "will expire in 6 days"); acknowledged_by is set on
incident.acknowledged and empty otherwise. When several monitors on one escalation policy fail
together you receive one event per monitor, never a bundle. No event
carries an acknowledge link.
- Any 2xx counts as delivered.
410 Gonetells Uptimia the endpoint is finished with the subscription: it is deleted and nothing more is sent. A404fromhooks.zapier.com— what Zapier answers once a Zap is turned off — is treated the same way.- Any other failure (a timeout, a 5xx, a refused connection) is retried five times with backoff — up to six attempts per delivery, about 15 minutes in all.
- After 25 consecutive failed deliveries (events whose every attempt
failed; a single successful delivery resets the count) the subscription
is disabled: its
disabled_atis set, it stops receiving events, and it stays listed inGET /api/v2/hooksso you can see why the events stopped. Re-create it (the samePOST /api/v2/hooks) to resume: the disabled subscription is replaced by a new one with a new secret.
Compute HMAC-SHA256 over the raw request body (before any JSON parsing) with the subscription's secret, and compare it to the header in constant time.
PHP:
$rawBody = file_get_contents('php://input');
$expected = 'sha256=' . hash_hmac('sha256', $rawBody, $secret);
if (!hash_equals($expected, $_SERVER['HTTP_X_UPTIMIA_SIGNATURE'] ?? '')) {
http_response_code(401);
exit;
}
Node:
const crypto = require('crypto');
const expected = 'sha256=' + crypto.createHmac('sha256', secret).update(rawBody).digest('hex');
const given = req.headers['x-uptimia-signature'] || '';
const ok = expected.length === given.length
&& crypto.timingSafeEqual(Buffer.from(expected), Buffer.from(given));
Answer 2xx only after the signature checks out; an unsigned or mis-signed
request should be rejected and never acted on.
Exchange credentials for an API key (mobile/native sign-in)
Verifies an account's email and password and returns a Bearer API key the native app stores securely and sends on every later /api/v2 request.
Lifetime and refresh. The key never expires and there is no refresh token — it is the same kind of key the control panel's API keys screen issues, so once stored it keeps working until it is revoked or the account's (or that seat's) password changes: a password change or reset revokes every key this endpoint minted for that identity, except the key the change itself was made with. There is nothing to renew and no rotation schedule to follow: call this endpoint once per device, keep the key, and only call it again after a revocation or a re-install. Every successful call mints a new key rather than returning an existing one, so an app that re-authenticates on each launch accumulates one key per launch in the account's API keys list. The response carries the new key's id as api_key_id. To revoke the key on sign-out, call DELETE /api/v2/auth/token with it as the Bearer (works for every team-member role); an owner or admin can also revoke any key with DELETE /api/v2/api-keys/{id}. Either takes effect on the next request. The account owner is emailed each time a key is created, including by this endpoint.
Two-factor. If the account (or, for a team member, that seat) has two-factor authentication enabled, a call without code responds 401 2fa_required with method saying where the code comes from. method: "email": that call emailed a six-digit one-time code; the app re-sends the same email and password together with the code and the challenge. method: "totp" (authenticator app): nothing is sent, the user reads the six-digit code from their authenticator app (a one-time recovery code is accepted too); a code may be sent on the very first call. A challenge allows five verification attempts; after that it is spent and a resend will not revive it. Wrong, replayed or spent codes answer 401 invalid_2fa_code. For authenticator-app accounts, ten wrong codes in a row lock code entry for 15 minutes (429 2fa_locked), and 503 totp_unavailable means the server cannot check app codes at the moment.
Scope of the key. Signing in as a team member mints a key stamped with that seat, so it carries the seat's role and group scope rather than full owner authority; operator_id in the response tells the app which it got.
The returned api_key is the FULL secret and is revealed only once — persist it in secure device storage, because every later read masks it. This endpoint is public: send no Authorization header on it (a stale Bearer is rejected by the auth layer before this handler runs). It is rate limited to 20 attempts per 15 minutes per client IP, and the account itself locks out after 5 consecutive failures.
Authorizations:
Request Body schema: application/json
| email required | string Account email address. Surrounding whitespace is trimmed. A team member signs in with their own address. |
| password required | string <password> Account password, sent verbatim (not trimmed) |
| code | string Two-factor code, required only when the account has 2FA enabled. Email method: the six digits from the email the first call sends, on the second call. Authenticator-app method: the six digits from the app (or a one-time recovery code, |
| challenge | string Opaque handle returned with the 401 |
| device_name | string <= 100 characters Optional label stored on the minted key and shown in the API keys list. Longer values are silently truncated to 100 characters; an empty or omitted value stores |
Responses
Request samples
- Payload
{- "password": "pa$$word",
- "code": "482915",
- "challenge": "c1f0…",
- "device_name": "iPhone 15"
}Response samples
- 201
- 401
- 403
- 429
- 500
- 503
{- "data": {
- "api_key": "nH4pQ2rXbK7yTf0LmA9sVd3EgW6cZuJ1",
- "api_key_id": 9120,
- "user_id": 4821,
- "operator_id": null
}
}Revoke the API key this request is signed with (mobile sign-out)
Deletes the API key sent as Authorization: Bearer <api_key> on this very request, so a signed-out device leaves no working credential behind. It needs no key id and no special permission: every team-member role may call it, because it can only delete the caller's own key. The key stops working immediately; later calls with it answer 401 invalid_api_key. A control-panel session (cookie, no Bearer) has no key to revoke and gets 400 api_key_required.
Authorizations:
Responses
Response samples
- 200
- 400
{- "status": "success"
}List all uptime monitors
Returns a paginated list of all uptime monitors for the authenticated account. Supports filtering by monitor status.
Authorizations:
query Parameters
| limit | integer Example: limit=25 Maximum number of monitors to return (0 = no limit) |
| offset | integer Example: offset=0 Number of monitors to skip |
| show_is_up | boolean Default: true Include monitors that are up |
| show_is_critical | boolean Default: true Include monitors with critical incidents |
| show_is_trouble | boolean Default: true Include monitors with trouble incidents |
| show_is_suspended | boolean Default: true Include suspended monitors |
| show_is_paused | boolean Default: true Include paused monitors |
| show_is_maintenance | boolean Default: true Include monitors in maintenance |
Responses
Response samples
- 200
- 401
{- "data": [
- {
- "id": 0,
- "name": "string",
- "is_up": true,
- "is_critical": true,
- "is_trouble": true,
- "is_suspended": true,
- "is_paused": true,
- "is_maintenance": true,
- "groups": [
- {
- "id": 0,
- "name": "string"
}
]
}
], - "metadata": {
- "total": 0
}
}Create a new uptime monitor
Creates a new uptime monitor with the specified configuration. The domain field is required.
Authorizations:
Request Body schema: application/json
| domain required | string URL or hostname to monitor |
| unique_id | string Custom unique identifier used in public status page URLs. |
| project_name | string Monitor display name |
| monit_type | integer Monitor type: 1=HTTP, 2=HTTPS, 3=TCP, 4=PING, 5=DNS, 6=UDP, 7=SMTP, 8=POP3, 9=IMAP |
| port | string Port number (auto-detected if empty) |
| username | string HTTP Basic Auth username |
| password | string HTTP Basic Auth password |
| string_send | string String to send for TCP/UDP protocol checks |
| string_expect | string Expected string in TCP/UDP response |
| encrypted | integer Use SSL/TLS for TCP connections (0=no, 1=yes) |
| monitoring_interval_slider | integer [ 0 .. 65 ] How often to check, in MINUTES (0-60; 0 = every 30 seconds). Values 61-65 are period codes for longer cadences: 61=2h, 62=3h, 63=6h, 64=12h, 65=24h. A value below your plan's fastest cadence is raised to that floor. |
| report_down_slider | integer [ 0 .. 60 ] How long an outage must persist before it is reported, in MINUTES (0-60). 0 reports as soon as the outage is confirmed. |
| probes | string Monitoring locations: "all" for all locations, or comma-separated probe IDs |
| contacts required | Array of integers Contact IDs to notify on incidents |
| groups | Array of integers Group IDs to assign the monitor to |
object Error types to report on | |
Array of objects Keywords to check for in response body | |
Array of objects Custom HTTP headers to send | |
| payload | string Raw POST/PUT request body |
| request_type | string Enum: "GET" "POST" "PUT" "DELETE" "HEAD" "POSTRAW" HTTP method for the check |
| follow_redirects | integer Follow HTTP redirects (0=no, 1=yes) |
| max_timeout | integer Maximum timeout in seconds |
| report_up | integer Send notification when monitor recovers (0=no, 1=yes) |
| branding_id | integer Branding template ID for notifications |
(string or null) or (integer or null) Records WHICH saved Location Profile the vantage selection came from, so the editor's Locations tab can show the applied profile. Either a built-in synthetic id ("builtin: |
Responses
Request samples
- Payload
{- "unique_id": "string",
- "project_name": "My Website",
- "monit_type": 2,
- "port": "443",
- "username": "string",
- "password": "string",
- "string_send": "string",
- "string_expect": "string",
- "encrypted": 0,
- "monitoring_interval_slider": 5,
- "report_down_slider": 1,
- "probes": "all",
- "contacts": [
- 0
], - "groups": [
- 0
], - "error_types": {
- "connection": 1,
- "timeout": 1,
- "unknown": 1,
- "redirect": 1,
- "keyword": 1,
- "http": {
- "response1xx": 0,
- "response2xx": 0,
- "response3xx": 0,
- "response4xx": 1,
- "response5xx": 1
}
}, - "keywords": [
- {
- "keyword": "string",
- "state": "must_exist"
}
], - "custom_headers": [
- {
- "name": "string",
- "value": "string"
}
], - "payload": "string",
- "request_type": "GET",
- "follow_redirects": 1,
- "max_timeout": 30,
- "report_up": 0,
- "branding_id": 0,
- "location_profile": "builtin:europe"
}Response samples
- 201
- 400
- 401
{- "data": {
- "id": 0
}
}Get a single uptime monitor
Returns full details of a specific uptime monitor including configuration, status, and assigned contacts.
Authorizations:
path Parameters
| id required | integer\d+ Monitor ID |
Responses
Response samples
- 200
- 401
- 404
{- "data": {
- "id": 0,
- "name": "string",
- "unique_id": "string",
- "is_up": true,
- "is_critical": true,
- "is_trouble": true,
- "is_suspended": true,
- "is_paused": true,
- "is_maintenance": true,
- "groups": [
- { }
], - "monit_type": 0,
- "monitor_type": "HTTP",
- "interval": 0,
- "last_check": 0,
- "report_down": 0,
- "report_up": true,
- "url": "string",
- "port": 0,
- "username": "string",
- "password": "string",
- "has_password": true,
- "incident_id": 0,
- "string_send": "string",
- "string_expect": "string",
- "is_encrypted": true,
- "follow_redirects": true,
- "max_timeout": 0,
- "report_error_types": { },
- "keywords": [
- {
- "keyword": "string",
- "state": "must_exist"
}
], - "locations": [
- 0
], - "request_type": "GET",
- "custom_headers": [
- {
- "name": "string",
- "value": "string"
}
], - "post_values": "string",
- "branding_id": 0,
- "payload": "string",
- "contacts": [
- {
- "id": 0,
- "is_team": true,
- "is_user": true,
- "is_operator": true,
- "is_individual_contact": true
}
], - "applied_location_profile": "builtin:europe"
}
}Delete an uptime monitor
Permanently deletes an uptime monitor and its associated reporting configuration. Incident and log data may be retained.
Authorizations:
path Parameters
| id required | integer\d+ Monitor ID |
Responses
Response samples
- 200
- 400
- 401
- 404
{- "status": "success"
}Update an existing uptime monitor
Updates an existing uptime monitor. Only include fields you want to change. Omitted fields retain their current values.
Authorizations:
path Parameters
| id required | integer\d+ Monitor ID |
Request Body schema: application/json
| is_paused | boolean Pause (true) or resume (false) the monitor. When present, only the pause/resume action is performed and other fields are ignored. |
| unique_id | string Custom unique identifier used in public status page URLs. |
| domain | string URL or hostname to monitor |
| project_name | string Monitor display name |
| monit_type | integer Monitor type: 1=HTTP, 2=HTTPS, 3=TCP, 4=PING, 5=DNS, 6=UDP, 7=SMTP, 8=POP3, 9=IMAP |
| port | string Port number |
| username | string HTTP Basic Auth username |
| password | string New HTTP Basic Auth password. Omitting it — or sending an empty string, which is what a GET response round-trips — keeps the stored secret. To remove the password, send clear_password: true. |
| clear_password | boolean Set true to remove the stored HTTP Basic Auth password. Ignored when a non-empty password is sent in the same request (that rotates instead). |
| string_send | string String to send for TCP/UDP checks |
| string_expect | string Expected string in response |
| encrypted | integer Use SSL/TLS for TCP connections (0=no, 1=yes) |
| monitoring_interval_slider | integer [ 0 .. 65 ] How often to check, in MINUTES (0-60; 0 = every 30 seconds). Values 61-65 are period codes: 61=2h, 62=3h, 63=6h, 64=12h, 65=24h. |
| report_down_slider | integer [ 0 .. 60 ] How long an outage must persist before it is reported, in MINUTES (0-60). 0 reports as soon as the outage is confirmed. |
| probes | string Monitoring locations: "all" or comma-separated probe IDs |
| contacts | Array of integers Contact IDs to notify |
| groups | Array of integers Group IDs |
| error_types | object Error types to report on |
Array of objects Keywords to check in the response body. Replaces the whole list; [] clears it. | |
| custom_headers | Array of objects Custom HTTP headers |
| payload | string Raw POST/PUT request body |
| request_type | string Enum: "GET" "POST" "PUT" "DELETE" "HEAD" "POSTRAW" HTTP method |
| follow_redirects | integer Follow HTTP redirects (0=no, 1=yes) |
| max_timeout | integer Maximum timeout in seconds |
| report_up | integer Send notification when monitor recovers (0=no, 1=yes) |
| branding_id | integer Branding template ID |
(string or null) or (integer or null) Records WHICH saved Location Profile the vantage selection came from, so the editor's Locations tab can show the applied profile. Either a built-in synthetic id ("builtin: |
Responses
Request samples
- Payload
{- "is_paused": true,
- "unique_id": "string",
- "domain": "string",
- "project_name": "string",
- "monit_type": 0,
- "port": "string",
- "username": "string",
- "password": "string",
- "clear_password": true,
- "string_send": "string",
- "string_expect": "string",
- "encrypted": 0,
- "monitoring_interval_slider": 65,
- "report_down_slider": 60,
- "probes": "string",
- "contacts": [
- 0
], - "groups": [
- 0
], - "error_types": { },
- "keywords": [
- {
- "keyword": "string",
- "state": "must_exist"
}
], - "custom_headers": [
- { }
], - "payload": "string",
- "request_type": "GET",
- "follow_redirects": 0,
- "max_timeout": 0,
- "report_up": 0,
- "branding_id": 0,
- "location_profile": "builtin:europe"
}Response samples
- 200
- 400
- 401
- 404
{- "data": {
- "id": 0
}
}List incidents for an uptime monitor
Returns a list of incidents for the specified monitor within the given date range. Defaults to the last 30 days. date_start is raised to the account plan's history retention floor.
Authorizations:
path Parameters
| id required | integer\d+ Monitor ID |
query Parameters
| date_start | integer Start date as Unix timestamp (defaults to 30 days ago) |
| date_end | integer End date as Unix timestamp (defaults to now) |
| limit | integer Maximum number of incidents to return |
| offset | integer Number of incidents to skip |
Responses
Response samples
- 200
- 401
- 404
{- "data": [
- {
- "error_id": 0,
- "impact": { },
- "error_display": "string",
- "time_started": "string",
- "date_ended": "string",
- "time_elapsed": "string"
}
], - "metadata": {
- "total": 0
}
}Get a single incident
Returns details of a specific incident for the given uptime monitor.
Authorizations:
path Parameters
| id required | integer\d+ Monitor ID |
| incidentId required | integer\d+ Incident ID |
Responses
Response samples
- 200
- 401
- 404
{- "data": {
- "id": 0,
- "impact": { },
- "name": "string",
- "monitor_id": 0,
- "type": "HTTP",
- "type_code": "uptime",
- "url": "string",
- "port": 0,
- "string_send": "string",
- "string_expect": "string",
- "time_started": "string",
- "time_started_unix": 0,
- "time_ended": "string",
- "time_ended_unix": 0,
- "error": "string",
- "duration": "string",
- "errors": [
- {
- "error": "string",
- "count": 0
}
], - "servers": [
- {
- "name": "string",
- "flag": "string",
- "ip": "string",
- "date": "string",
- "error": "string"
}
], - "response_headers": "string",
- "response_body": "string",
- "traceroute": "string",
- "screenshot": "string",
- "keywords": [
- { }
], - "is_critical": true,
- "is_trouble": true
}
}Delete an incident
Permanently deletes a specific incident and its associated status page threads. The incident must belong to the monitor in the path; otherwise 404 incident_not_found.
Authorizations:
path Parameters
| id required | integer\d+ Monitor ID |
| incidentId required | integer\d+ Incident ID |
Responses
Response samples
- 200
- 400
- 401
- 404
{- "status": "success"
}List monitoring logs across all monitors
Returns a paginated list of monitoring check logs across all uptime monitors for the authenticated account. Supports date range filtering and pagination. date_start is raised to the account plan's history retention floor, and an omitted date_start starts at that floor.
Authorizations:
query Parameters
| limit | integer Example: limit=50 Maximum number of log entries to return (default 50) |
| offset | integer Example: offset=0 Number of log entries to skip |
| date_start | integer Start date as Unix timestamp |
| date_end | integer End date as Unix timestamp |
Responses
Response samples
- 200
- 401
{- "data": [
- {
- "id": 0,
- "monitor_id": 0,
- "monitor_name": "string",
- "flag": "string",
- "location": "string",
- "time": "2019-08-24T14:15:22Z",
- "status": "up",
- "speed": 0,
- "dns": 0,
- "connect": 0,
- "send": 0,
- "wait": 0,
- "receive": 0,
- "is_backup": true
}
], - "metadata": {
- "total": 0
}
}List monitoring logs
Returns monitoring check logs for the specified monitor, newest first. Defaults to the last 14 days and a page size of 20; use limit/offset to page through the whole window. date_start is raised to the account plan's history retention floor.
Authorizations:
path Parameters
| id required | integer\d+ Monitor ID |
query Parameters
| limit | integer Example: limit=20 Page size (default 20) |
| offset | integer Number of log entries to skip |
| date_start | integer Start date as Unix timestamp to filter the log range (defaults to 14 days ago, raised to the plan retention floor) |
| date_end | integer End date as Unix timestamp to filter the log range |
Responses
Response samples
- 200
- 401
- 404
{- "data": [
- {
- "id": 0,
- "flag": "string",
- "location": "string",
- "time_elapsed": "string",
- "speed": "string",
- "status_icon": "string",
- "status_alt": "string"
}
], - "metadata": {
- "total": 0
}
}Get a single monitoring log entry
Returns full details of a specific monitoring check log.
Authorizations:
path Parameters
| id required | integer\d+ Monitor ID |
| logId required | integer\d+ Example: 1756000000 Log entry ID — post-cutover this is the check's unix-second |
Responses
Response samples
- 200
- 401
- 404
{- "data": {
- "id": 0,
- "type_code": "uptime",
- "name": "My Website",
- "type": "HTTPS",
- "type_id": 2,
- "port": 443,
- "string_send": "",
- "string_expect": "",
- "keyword_expect": "[{\"keyword\":\"Sign in\",\"state\":\"must_exist\"}]",
- "status": 1,
- "response_time": "412 ms",
- "time": "Mar 15 2026, 14:30:00",
- "flag": "gb",
- "location": "London",
- "response": "",
- "error": ""
}
}Get timeline segments for an uptime monitor
Returns timeline segments showing uptime, downtime, maintenance, and unknown periods for the given date range. date_start is raised to the account plan's history retention floor.
Authorizations:
path Parameters
| id required | integer\d+ Monitor ID |
query Parameters
| date_start | integer Start date as Unix timestamp (defaults to 7 days ago) |
| date_end | integer End date as Unix timestamp (defaults to now) |
Responses
Response samples
- 200
- 400
- 401
- 404
{- "data": {
- "segments": [
- {
- "type": "up",
- "durationSeconds": 86400,
- "startTime": "2026-03-15T14:30:00+00:00",
- "endTime": "2026-03-16T14:30:00+00:00",
- "label": "1 day",
- "error": [
- "Timeout error",
- "Connection timed out"
], - "incidentId": 90211
}
], - "intervals": {
- "1": "15:00",
- "2": "17:15",
- "3": "19:45",
- "4": "22:15",
- "5": "00:45"
}
}
}Get availability percentages
Calculates uptime and downtime percentages for the specified monitor within the given date range. Defaults to the last 24 hours. date_start is raised to the account plan's history retention floor.
Authorizations:
path Parameters
| id required | integer\d+ Monitor ID |
query Parameters
| date_start | integer Start date as Unix timestamp (defaults to 24 hours ago) |
| date_end | integer End date as Unix timestamp (defaults to now) |
Responses
Response samples
- 200
- 400
- 401
- 404
{- "data": {
- "uptime": 99.95,
- "downtime": 0.05,
- "window_start": 1790000000,
- "window_end": 1790086400
}
}Get response time history
Returns response time chart data for the specified monitor. Includes timing breakdown (DNS, connect, send, wait, receive) for HTTP/HTTPS monitors. Data is cached for 60 seconds. date_start is raised to the account plan's history retention floor.
Authorizations:
path Parameters
| id required | integer\d+ Monitor ID |
query Parameters
| date_start | integer Start date as Unix timestamp (defaults to 24 hours ago) |
| date_end | integer End date as Unix timestamp (defaults to now) |
Responses
Response samples
- 200
- 401
- 404
{- "data": {
- "response_time": [
- 0
], - "dns": [
- 0
], - "connect": [
- 0
], - "send": [
- 0
], - "wait": [
- 0
], - "receive": [
- 0
], - "downtime": [
- 0
], - "date": [
- "string"
], - "max_value": 0
}
}List all SSL monitors
Returns a paginated list of all SSL monitors for the authenticated account. Supports filtering by monitor status.
Authorizations:
query Parameters
| limit | integer Example: limit=25 Maximum number of monitors to return (0 = no limit) |
| offset | integer Example: offset=0 Number of monitors to skip |
| show_is_up | boolean Default: true Include monitors that are up |
| show_is_critical | boolean Default: true Include monitors with critical incidents |
| show_is_trouble | boolean Default: true Include monitors with trouble incidents |
| show_is_suspended | boolean Default: true Include suspended monitors |
| show_is_paused | boolean Default: true Include paused monitors |
| show_is_maintenance | boolean Default: true Include monitors in maintenance |
Responses
Response samples
- 200
- 401
{- "data": [
- {
- "id": 0,
- "name": "string",
- "is_up": true,
- "is_critical": true,
- "is_trouble": true,
- "is_suspended": true,
- "is_paused": true,
- "is_maintenance": true,
- "groups": [
- {
- "id": 0,
- "name": "string"
}
], - "issuer": "Let's Encrypt",
- "valid_to": 1790000000,
- "days_remaining": 42
}
], - "metadata": {
- "total": 0
}
}Create a new SSL monitor
Creates a new SSL certificate monitor for the specified URL.
Authorizations:
Request Body schema: application/json
| url required | string URL to monitor SSL certificate for |
| region | integer Monitoring region ID (defaults to 1). |
| project_name | string Monitor display name |
| monitoring_interval_slider | integer Check frequency in minutes (0-60; 0 = every 30 seconds) or a period code 61-65 (2h, 3h, 6h, 12h, 24h). -1 selects the 10-second cadence where enabled. |
| report_down_slider | integer Minutes to wait before reporting an outage (0-60; 0 = report as soon as the outage is confirmed) |
| change_error_level_time | integer Seconds before changing error level. Preferred field — sent in seconds by the editor. |
| change_error_level_time_slider | integer Legacy slider value for the change error level time (1-10). Only used by the bulk path when change_error_level_time is omitted. |
| expire_alert_days | integer Days before certificate expiry to alert |
| probes | string Monitoring locations: "all" for all locations, or comma-separated probe IDs |
| contacts | Array of integers Contact IDs to notify on incidents |
| groups | Array of integers Group IDs to assign the monitor to |
| report_up | integer Send notification when monitor recovers (0=no, 1=yes) |
| branding_id | integer Branding template ID for notifications |
(string or null) or (integer or null) Records WHICH saved Location Profile the vantage selection came from, so the editor's Locations tab can show the applied profile. Either a built-in synthetic id ("builtin: |
Responses
Request samples
- Payload
{- "region": 1,
- "project_name": "My Website SSL",
- "monitoring_interval_slider": 5,
- "report_down_slider": 1,
- "change_error_level_time": 3600,
- "change_error_level_time_slider": 5,
- "expire_alert_days": 7,
- "probes": "all",
- "contacts": [
- 0
], - "groups": [
- 0
], - "report_up": 0,
- "branding_id": 0,
- "location_profile": "builtin:europe"
}Response samples
- 201
- 400
- 401
{- "data": {
- "id": 0
}
}Get a single SSL monitor
Returns full details of a specific SSL monitor including configuration, status, certificate info, and assigned contacts.
Authorizations:
path Parameters
| id required | integer\d+ Monitor ID |
Responses
Response samples
- 200
- 401
- 404
{- "data": {
- "id": 0,
- "name": "string",
- "is_up": true,
- "is_critical": true,
- "is_trouble": true,
- "is_suspended": true,
- "is_paused": true,
- "is_maintenance": true,
- "groups": [
- { }
], - "interval": 0,
- "report_down": 0,
- "report_up": true,
- "url": "string",
- "incident_id": 0,
- "locations": [
- 0
], - "expire_alert_days": 0,
- "change_error_level_time": 0,
- "branding_id": 0,
- "contacts": [
- {
- "id": 0,
- "is_team": true,
- "is_user": true,
- "is_operator": true,
- "is_individual_contact": true
}
], - "issuer": "string",
- "protocol": "string",
- "domain_name": "string",
- "valid_from": 0,
- "valid_to": 0,
- "days_remaining": 0,
- "error_code": "string",
- "status": 0,
- "applied_location_profile": "builtin:europe"
}
}Delete an SSL monitor
Permanently deletes an SSL monitor and its associated reporting configuration. Incident and log data may be retained.
Authorizations:
path Parameters
| id required | integer\d+ Monitor ID |
Responses
Response samples
- 200
- 400
- 401
- 404
{- "status": "success"
}Update an existing SSL monitor
Updates an existing SSL monitor. Only include fields you want to change. Omitted fields retain their current values.
Authorizations:
path Parameters
| id required | integer\d+ Monitor ID |
Request Body schema: application/json
| is_paused | boolean Pause (true) or resume (false) the monitor. When present, only the pause/resume action is performed and other fields are ignored. |
| url | string URL to monitor SSL certificate for |
| region | integer Monitoring region ID (defaults to 1). |
| project_name | string Monitor display name |
| monitoring_interval_slider | integer Check frequency in minutes (0-60; 0 = every 30 seconds) or a period code 61-65 (2h, 3h, 6h, 12h, 24h) |
| report_down_slider | integer Minutes to wait before reporting an outage (0-60) |
| change_error_level_time | integer Seconds before changing error level. Preferred field — sent in seconds by the editor. |
| change_error_level_time_slider | integer Legacy slider value for the change error level time (1-10). Only used by the bulk path when change_error_level_time is omitted. |
| expire_alert_days | integer Days before certificate expiry to alert |
| probes | string Monitoring locations: "all" or comma-separated probe IDs |
| contacts | Array of integers Contact IDs to notify |
| groups | Array of integers Group IDs |
| report_up | integer Send notification when monitor recovers (0=no, 1=yes) |
| branding_id | integer Branding template ID |
(string or null) or (integer or null) Records WHICH saved Location Profile the vantage selection came from, so the editor's Locations tab can show the applied profile. Either a built-in synthetic id ("builtin: |
Responses
Request samples
- Payload
{- "is_paused": true,
- "url": "string",
- "region": 1,
- "project_name": "string",
- "monitoring_interval_slider": 0,
- "report_down_slider": 0,
- "change_error_level_time": 3600,
- "change_error_level_time_slider": 0,
- "expire_alert_days": 0,
- "probes": "string",
- "contacts": [
- 0
], - "groups": [
- 0
], - "report_up": 0,
- "branding_id": 0,
- "location_profile": "builtin:europe"
}Response samples
- 200
- 400
- 401
- 404
{- "data": {
- "id": 0
}
}List incidents for an SSL monitor
Returns a list of incidents for the specified SSL monitor within the given date range. Defaults to the last 30 days.
Authorizations:
path Parameters
| id required | integer\d+ Monitor ID |
query Parameters
| date_start | integer Start date as Unix timestamp (defaults to 30 days ago) |
| date_end | integer End date as Unix timestamp (defaults to now) |
| limit | integer Maximum number of incidents to return |
| offset | integer Number of incidents to skip |
Responses
Response samples
- 200
- 401
- 404
{- "data": [
- {
- "error_id": 0,
- "impact": { },
- "error_display": "string",
- "time_started": "string",
- "date_ended": "string",
- "time_elapsed": "string"
}
], - "metadata": {
- "total": 0
}
}Get a single SSL incident
Returns full details of a specific incident for the given SSL monitor.
Authorizations:
path Parameters
| id required | integer\d+ Monitor ID |
| incidentId required | integer\d+ Incident ID |
Responses
Response samples
- 200
- 401
- 404
{- "data": {
- "id": 0,
- "impact": { },
- "name": "string",
- "monitor_id": 0,
- "type": "SSL",
- "type_code": "ssl",
- "url": "string",
- "port": 443,
- "time_started": "string",
- "time_started_unix": 0,
- "time_ended": "string",
- "time_ended_unix": 0,
- "error": "string",
- "duration": "string",
- "errors": [
- {
- "error": "string",
- "count": 0
}
], - "servers": [
- {
- "name": "string",
- "flag": "string",
- "ip": "string",
- "date": "string",
- "error": "string"
}
], - "response_headers": "string",
- "response_body": "string",
- "traceroute": "string",
- "screenshot": "string",
- "is_critical": true,
- "is_trouble": true
}
}Delete an SSL incident
Permanently deletes a specific SSL incident and its associated status page threads. The incident must belong to the monitor in the path; otherwise 404 incident_not_found.
Authorizations:
path Parameters
| id required | integer\d+ Monitor ID |
| incidentId required | integer\d+ Incident ID |
Responses
Response samples
- 200
- 400
- 401
- 404
{- "status": "success"
}List SSL monitoring logs
Returns recent monitoring check logs for the specified SSL monitor. Shows the last 90 days of data, limited to 20 entries.
Authorizations:
path Parameters
| id required | integer\d+ Monitor ID |
query Parameters
| limit | integer Maximum number of log entries to return |
| offset | integer Number of log entries to skip |
| date_start | integer Start date as Unix timestamp to filter the log range |
| date_end | integer End date as Unix timestamp to filter the log range |
Responses
Response samples
- 200
- 401
- 404
{- "data": [
- {
- "id": 0,
- "flag": "string",
- "location": "string",
- "time_elapsed": "string",
- "speed": "string",
- "status_icon": "string",
- "status_alt": "string"
}
], - "metadata": {
- "total": 0
}
}Get a single SSL monitoring log entry
Returns full details of a specific SSL monitoring check log.
Authorizations:
path Parameters
| id required | integer\d+ Monitor ID |
| logId required | integer\d+ Example: 1756000000 Log entry ID — post-cutover this is the check's unix-second |
Responses
Response samples
- 200
- 401
- 404
{- "data": {
- "id": 1756000000,
- "type_code": "ssl",
- "name": "My Website SSL",
- "type": "SSL",
- "port": 443,
- "status": 1,
- "response_time": "312 ms",
- "time": "Mar 15 2026, 14:30:00",
- "flag": "gb",
- "location": "London",
- "response": "",
- "error": ""
}
}Get timeline segments for an SSL monitor
Returns up, down, maintenance and unknown timeline segments for the SSL monitor over the requested date range, suitable for rendering an availability timeline strip.
Authorizations:
path Parameters
| id required | integer\d+ Monitor ID |
query Parameters
| date_start | integer Start date as Unix timestamp (defaults to 7 days ago) |
| date_end | integer End date as Unix timestamp (defaults to now) |
Responses
Response samples
- 200
- 400
- 401
- 404
{- "data": {
- "segments": [
- {
- "type": "up",
- "durationSeconds": 86400,
- "startTime": "2026-03-15T14:30:00+00:00",
- "endTime": "2026-03-16T14:30:00+00:00",
- "label": "1 day",
- "error": [
- "Certificate has already expired",
- "Your SSL certificate expired 3 days ago"
], - "incidentId": 90211
}
], - "intervals": {
- "1": "15:00",
- "2": "17:15",
- "3": "19:45",
- "4": "22:15",
- "5": "00:45"
}
}
}Get SSL response time history
Returns response time and downtime chart data for the specified SSL monitor. Data is cached for 60 seconds.
Authorizations:
path Parameters
| id required | integer\d+ Monitor ID |
query Parameters
| date_start | integer Start date as Unix timestamp (defaults to 24 hours ago) |
| date_end | integer End date as Unix timestamp (defaults to now) |
Responses
Response samples
- 200
- 401
- 404
{- "data": {
- "speed": [
- 0
], - "downtime": [
- 0
], - "date": [
- "string"
], - "max_value": 0
}
}List all domain monitors
Returns a paginated list of all domain monitors for the authenticated account. Supports filtering by monitor status.
Authorizations:
query Parameters
| limit | integer Example: limit=25 Maximum number of monitors to return (0 = no limit) |
| offset | integer Example: offset=0 Number of monitors to skip |
| show_is_up | boolean Default: true Include monitors that are up |
| show_is_critical | boolean Default: true Include monitors with critical incidents |
| show_is_trouble | boolean Default: true Include monitors with trouble incidents |
| show_is_suspended | boolean Default: true Include suspended monitors |
| show_is_paused | boolean Default: true Include paused monitors |
| show_is_maintenance | boolean Default: true Include monitors in maintenance |
Responses
Response samples
- 200
- 401
{- "data": [
- {
- "id": 0,
- "name": "string",
- "is_up": true,
- "is_critical": true,
- "is_trouble": true,
- "is_suspended": true,
- "is_paused": true,
- "is_maintenance": true,
- "groups": [
- {
- "id": 0,
- "name": "string"
}
], - "registrar": "Gandi SAS",
- "domain_expiry_date": 1790000000,
- "days_remaining": 42
}
], - "metadata": {
- "total": 0
}
}Create a new domain monitor
Creates a new domain expiration monitor. Validates domain zone against supported TLD whitelist.
Authorizations:
Request Body schema: application/json
| url required | string Domain name to monitor (e.g. example.com) |
| project_name required | string Monitor display name |
| monitoring_interval_slider | integer WHOIS check frequency, 71-79 (71 = every day … 79 = every 9 days) |
| monitoring_interval | integer WHOIS check frequency in seconds, 86400-2592000 (1 to 30 days). Alternative to monitoring_interval_slider |
| report_down_slider | integer Minutes to wait before reporting a failed check, 0-60 (0 = report as soon as the check fails) |
| expire_alert_days | integer Days before domain expiry to alert (1-90, default 7) |
| expiry_date | string Manual expiry date in YYYY-MM-DD format. Required for a manual zone (and for a third-level name in a zone that sells them): without it the create is refused with expiry_date_required. Ignored for other zones. |
| contacts | Array of integers Contact IDs to notify on incidents |
| groups | Array of integers Group IDs to assign the monitor to |
| report_up | integer Send notification when monitor recovers (0=no, 1=yes) |
| branding_id | integer Branding template ID for notifications |
| confirmations_required | integer How many backup checks must agree before an outage is confirmed (1-3, default 3) |
| escalation_policy_id | integer Escalation policy to alert through (0 = alert every recipient at once) |
| notes | string Free-text operator note (plain text, up to 500 characters) |
| bulk_data | string Bulk import: newline-separated list of domains |
Responses
Request samples
- Payload
{- "url": "example.com",
- "project_name": "My Domain",
- "monitoring_interval_slider": 77,
- "monitoring_interval": 604800,
- "report_down_slider": 0,
- "expire_alert_days": 7,
- "expiry_date": "2027-01-15",
- "contacts": [
- 0
], - "groups": [
- 0
], - "report_up": 0,
- "branding_id": 0,
- "confirmations_required": 3,
- "escalation_policy_id": 0,
- "notes": "",
- "bulk_data": ""
}Response samples
- 201
- 400
- 401
{- "data": {
- "id": 0
}
}Look up a domain zone
Resolves a domain name (or a bare TLD) to its zone and reports whether the zone is monitored and whether its expiry date has to be supplied by hand. A manual zone has no queryable WHOIS server, so a monitor in it must be created with an expiry_date. The monitor editor calls this while the user types, to decide whether to show the expiry-date field.
Authorizations:
query Parameters
| url | string Example: url=example.de Domain name or URL to resolve (e.g. "example.de" or "https://example.de/path") |
| tld | string Example: tld=example.de Alias for url — accepted so a caller that already has the bare TLD can pass it directly |
Responses
Response samples
- 200
- 400
- 401
{- "data": {
- "zone": ".de",
- "accepted": true,
- "is_manual": true
}
}Get a single domain monitor
Returns full details of a specific domain monitor including WHOIS data, expiration info, registrar, and nameservers.
Authorizations:
path Parameters
| id required | integer\d+ Monitor ID |
Responses
Response samples
- 200
- 401
- 404
{- "data": {
- "id": 0,
- "name": "string",
- "is_up": true,
- "is_critical": true,
- "is_trouble": true,
- "is_suspended": true,
- "is_paused": true,
- "is_maintenance": true,
- "groups": [
- { }
], - "interval": 0,
- "report_down": 0,
- "report_up": true,
- "url": "string",
- "incident_id": 0,
- "expire_alert_days": 0,
- "branding_id": 0,
- "contacts": [
- {
- "id": 0,
- "is_team": true,
- "is_user": true,
- "is_operator": true,
- "is_individual_contact": true
}
], - "is_manual": true,
- "tld": "string",
- "zone": "string",
- "domain_creation_date": 0,
- "domain_updated_date": 0,
- "domain_expiry_date": 0,
- "days_remaining": 0,
- "registrar": "string",
- "nameservers": "string",
- "confirmations_required": 0,
- "last_check": 0,
- "escalation_policy_id": 0,
- "notes": "string"
}
}Delete a domain monitor
Permanently deletes a domain monitor and its associated reporting configuration. Incident and log data may be retained.
Authorizations:
path Parameters
| id required | integer\d+ Monitor ID |
Responses
Response samples
- 200
- 400
- 401
- 404
{- "status": "success"
}Update an existing domain monitor
Updates an existing domain monitor. Only include fields you want to change. Omitted fields retain their current values.
Authorizations:
path Parameters
| id required | integer\d+ Monitor ID |
Request Body schema: application/json
| is_paused | boolean Pause (true) or resume (false) the monitor. When present, only the pause/resume action is performed and other fields are ignored. |
| url | string Domain name to monitor |
| project_name | string Monitor display name |
| monitoring_interval_slider | integer WHOIS check frequency, 71-79 (71 = every day … 79 = every 9 days) |
| monitoring_interval | integer WHOIS check frequency in seconds, 86400-2592000 (1 to 30 days). Alternative to monitoring_interval_slider |
| report_down_slider | integer Minutes to wait before reporting a failed check, 0-60 |
| expire_alert_days | integer Days before domain expiry to alert (1-90) |
| expiry_date | string Manual expiry date in YYYY-MM-DD format (only for manual zones) |
| contacts | Array of integers Contact IDs to notify |
| groups | Array of integers Group IDs |
| report_up | integer Send notification when monitor recovers (0=no, 1=yes) |
| branding_id | integer Branding template ID |
| confirmations_required | integer How many backup checks must agree before an outage is confirmed (1-3) |
| escalation_policy_id | integer Escalation policy to alert through (0 = alert every recipient at once) |
| notes | string Free-text operator note (plain text, up to 500 characters) |
Responses
Request samples
- Payload
{- "is_paused": true,
- "url": "string",
- "project_name": "string",
- "monitoring_interval_slider": 0,
- "monitoring_interval": 0,
- "report_down_slider": 0,
- "expire_alert_days": 0,
- "expiry_date": "2027-01-15",
- "contacts": [
- 0
], - "groups": [
- 0
], - "report_up": 0,
- "branding_id": 0,
- "confirmations_required": 0,
- "escalation_policy_id": 0,
- "notes": "string"
}Response samples
- 200
- 400
- 401
- 404
{- "data": {
- "id": 0
}
}List incidents for a domain monitor
Returns a list of domain expiration incidents within the given date range. Defaults to the last 30 days. Domain-specific error codes: domain_expires_soon, domain_expires_very_soon, domain_expired.
Authorizations:
path Parameters
| id required | integer\d+ Monitor ID |
query Parameters
| date_start | integer Start date as Unix timestamp (defaults to 30 days ago) |
| date_end | integer End date as Unix timestamp (defaults to now) |
| limit | integer Maximum number of incidents to return |
| offset | integer Number of incidents to skip |
Responses
Response samples
- 200
- 401
- 404
{- "data": [
- {
- "error_id": 0,
- "impact": { },
- "error_display": "string",
- "time_started": "string",
- "date_ended": "string",
- "time_elapsed": "string"
}
], - "metadata": {
- "total": 0
}
}Get a single domain incident
Returns details of a specific domain expiration incident.
Authorizations:
path Parameters
| id required | integer\d+ Monitor ID |
| incidentId required | integer\d+ Incident ID |
Responses
Response samples
- 200
- 401
- 404
{- "data": {
- "id": 0,
- "impact": { },
- "name": "string",
- "monitor_id": 0,
- "type": "Domain",
- "type_code": "domain",
- "url": "string",
- "port": 0,
- "time_started": "string",
- "time_started_unix": 0,
- "time_ended": "string",
- "time_ended_unix": 0,
- "error": "string",
- "duration": "string",
- "errors": [
- {
- "error": "string",
- "count": 0
}
], - "servers": [
- {
- "name": "string",
- "flag": "string",
- "ip": "string",
- "date": "string",
- "error": "string"
}
], - "response_headers": "string",
- "response_body": "string",
- "traceroute": "string",
- "screenshot": "string",
- "is_critical": true,
- "is_trouble": true
}
}Delete a domain incident
Permanently deletes a specific domain expiration incident and its associated status page threads. The incident must belong to the monitor in the path; otherwise 404 incident_not_found.
Authorizations:
path Parameters
| id required | integer\d+ Monitor ID |
| incidentId required | integer\d+ Incident ID |
Responses
Response samples
- 200
- 400
- 401
- 404
{- "status": "success"
}List domain WHOIS check logs
Returns recent WHOIS monitoring check logs for the specified domain monitor. Shows the last 90 days of data, limited to 20 entries.
Authorizations:
path Parameters
| id required | integer\d+ Monitor ID |
query Parameters
| limit | integer Maximum number of log entries to return |
| offset | integer Number of log entries to skip |
| date_start | integer Start date as Unix timestamp to filter the log range |
| date_end | integer End date as Unix timestamp to filter the log range |
Responses
Response samples
- 200
- 401
- 404
{- "data": [
- {
- "id": 0,
- "flag": "string",
- "location": "string",
- "time_elapsed": "string",
- "speed": "string",
- "response_time": "string",
- "status_icon": "string",
- "status_alt": "string"
}
], - "metadata": {
- "total": 0
}
}Get timeline segments for a domain monitor
Returns up, down, maintenance and unknown timeline segments for the domain monitor over the requested date range, suitable for rendering an availability timeline strip.
Authorizations:
path Parameters
| id required | integer\d+ Monitor ID |
query Parameters
| date_start | integer Start date as Unix timestamp (defaults to 7 days ago) |
| date_end | integer End date as Unix timestamp (defaults to now) |
Responses
Get domain WHOIS response time history
Returns WHOIS response time and downtime chart data for the specified domain monitor. Data is cached for 60 seconds.
Authorizations:
path Parameters
| id required | integer\d+ Monitor ID |
query Parameters
| date_start | integer Start date as Unix timestamp (defaults to 24 hours ago) |
| date_end | integer End date as Unix timestamp (defaults to now) |
Responses
Response samples
- 200
- 401
- 404
{- "data": {
- "speed": [
- 0
], - "downtime": [
- 0
], - "date": [
- "string"
], - "max_value": 0
}
}List all speed monitors
Returns a paginated list of all speed monitors for the authenticated account. Supports filtering by monitor status.
Authorizations:
query Parameters
| limit | integer Example: limit=25 Maximum number of monitors to return (0 = no limit) |
| offset | integer Example: offset=0 Number of monitors to skip |
| show_is_up | boolean Default: true Include monitors that are up |
| show_is_critical | boolean Default: true Include monitors with critical incidents |
| show_is_trouble | boolean Default: true Include monitors with trouble incidents |
| show_is_suspended | boolean Default: true Include suspended monitors |
| show_is_paused | boolean Default: true Include paused monitors |
| show_is_maintenance | boolean Default: true Include monitors in maintenance |
Responses
Response samples
- 200
- 401
{- "data": [
- {
- "id": 0,
- "name": "string",
- "is_up": true,
- "is_critical": true,
- "is_trouble": true,
- "is_suspended": true,
- "is_paused": true,
- "is_maintenance": true,
- "groups": [
- {
- "id": 0,
- "name": "string"
}
]
}
], - "metadata": {
- "total": 0
}
}Create a new speed monitor
Creates a new speed/page-load monitor for the specified URL. Speed and Transaction monitors share ONE "Advanced" plan pool, so creation is refused with 403 plan_exceeded once that pool is full — the check runs before anything is written. Three fields must be supplied: url, project_name and contacts; every other field falls back to its create default. An explicit probes list must name at least 6 locations, and location_profile records which saved Location Profile that list came from without itself changing where the monitor runs.
Authorizations:
Request Body schema: application/json
| url required | string URL to monitor page speed for. Required — an absent or empty value is refused with |
| project_name required | string Monitor display name. Required on create: it has no usable default, so an absent or empty value is always refused with |
| monitoring_interval_slider | integer Monitoring interval slider value: 0-60 (minutes; 0 = every 30 seconds) or a period code 61-65 (2h, 3h, 6h, 12h, 24h). Out of range is refused with |
| report_down_slider | integer Report-down delay slider value: 0-60 minutes to wait before an outage is reported (0 = report as soon as it is confirmed). Out of range is refused with |
| probes | string Monitoring locations: "all" for every location, or a comma-separated list of probe ids. An explicit list must name at least 6 probes, otherwise |
| contacts required | Array of integers Alert recipient ids. Required in practice: on create an omitted (or empty) list is refused with |
| groups | Array of integers Monitor-group ids to assign the monitor to. Every id must belong to the account ( |
| is_alert_speed_drops | integer Enable speed drop alerts (0=no, 1=yes) |
| alert_speed_threshold | integer Speed alert threshold in seconds (1-30) |
| alert_speed_time | integer Speed alert time window in minutes (20-60) |
| is_alert_vitals | integer Master toggle for web-vitals budget alerts (0=no, 1=yes). Stored as 1 only while at least one budget below is > 0. |
| alert_lcp_ms | integer LCP budget in ms (0 = recorded but not alerting; else 100-60000) |
| alert_fcp_ms | integer FCP budget in ms (0 = recorded but not alerting; else 100-60000) |
| alert_ttfb_ms | integer TTFB budget in ms (0 = recorded but not alerting; else 50-30000) |
| alert_cls_x1000 | integer CLS budget ×1000 (0 = recorded but not alerting; else 10-10000; 100 = CLS 0.10) |
| is_alert_dead_elements | integer Alert when a page element keeps failing (0=no, 1=yes); detection itself always runs |
| dead_element_checks | integer Consecutive failing checks before the dead-element alert fires (1-10) |
| dead_element_watch_types | string Comma-separated element types to alert on, from: image,script,css,other |
| dead_element_ignore | string Ignore globs, one per line — matching elements are recorded but never alert |
| report_up | integer Send notification when monitor recovers (0=no, 1=yes) |
| branding_id | integer Branding template ID for notifications. 0 (the default) uses the default theme; an id not owned by the account is refused with |
| confirmations_required | integer Enum: 1 2 3 How many probes must independently observe the failure before it is acted on. Clamped into 1-3 (and clamped again by the manager), so an out-of-range value is silently corrected rather than refused. Defaults to 3 on create. |
| escalation_policy_id | integer Escalation policy that routes this monitor's alerts. 0 (the default) alerts every recipient at once; a value above 0 must reference a policy owned by the account or the request is refused with |
| notes | string Free-text operator note stored on the monitor. Trimmed and truncated to 500 characters; a non-string value stores an empty string. Shown on the incident page and included in alert emails and webhook payloads. |
(string or null) or (integer or null) Records WHICH saved Location Profile the vantage selection came from, so the editor's Locations tab can show the applied profile. Either a built-in synthetic id ("builtin: |
Responses
Request samples
- Payload
{- "project_name": "My Website Speed",
- "monitoring_interval_slider": 5,
- "report_down_slider": 1,
- "probes": "all",
- "contacts": [
- 42
], - "groups": [
- 7
], - "is_alert_speed_drops": 0,
- "alert_speed_threshold": 5,
- "alert_speed_time": 30,
- "is_alert_vitals": 0,
- "alert_lcp_ms": 2500,
- "alert_fcp_ms": 0,
- "alert_ttfb_ms": 800,
- "alert_cls_x1000": 100,
- "is_alert_dead_elements": 0,
- "dead_element_checks": 3,
- "dead_element_watch_types": "image,script,css",
- "dead_element_ignore": "*/pixel/*.gif",
- "report_up": 0,
- "branding_id": 0,
- "confirmations_required": 3,
- "escalation_policy_id": 0,
- "notes": "Checkout page — owned by the storefront team.",
- "location_profile": "builtin:europe"
}Response samples
- 201
- 400
- 401
- 403
{- "data": {
- "id": 4211
}
}Get a single speed monitor
Returns full details of a specific speed monitor including configuration, status, speed alert settings, and assigned contacts.
Authorizations:
path Parameters
| id required | integer\d+ Monitor ID |
Responses
Response samples
- 200
- 401
- 404
{- "data": {
- "id": "4211",
- "name": "My Website Speed",
- "is_up": true,
- "is_critical": false,
- "is_trouble": false,
- "is_suspended": false,
- "is_paused": false,
- "is_maintenance": false,
- "groups": [
- "7"
], - "interval": "300",
- "report_down": 60,
- "report_up": false,
- "incident_id": "90211",
- "locations": [
- 12
], - "is_alert_speed_drops": "0",
- "alert_speed_threshold": "5",
- "alert_speed_time": "30",
- "is_alert_vitals": 0,
- "alert_lcp_ms": 2500,
- "alert_fcp_ms": 0,
- "alert_ttfb_ms": 800,
- "alert_cls_x1000": 100,
- "is_alert_dead_elements": 0,
- "dead_element_checks": 3,
- "dead_element_watch_types": "image,script,css",
- "dead_element_ignore": "*/pixel/*.gif",
- "branding_id": 0,
- "confirmations_required": 3,
- "contacts": [
- {
- "id": "42",
- "is_team": "0",
- "is_user": "0",
- "is_operator": "0",
- "is_individual_contact": "1"
}
], - "escalation_policy_id": 0,
- "notes": "",
- "applied_location_profile": "builtin:europe"
}
}Delete a speed monitor
Permanently deletes a speed monitor and its associated reporting configuration. Incident and log data may be retained.
Authorizations:
path Parameters
| id required | integer\d+ Monitor ID |
Responses
Response samples
- 200
- 400
- 401
- 404
{- "status": "success"
}Update an existing speed monitor
Updates an existing speed monitor. Only include fields you want to change — omitted fields retain their stored values. Two groups are touch-gated: the five web-vitals keys are only written when at least ONE of them is present (and the missing siblings are then backfilled from the stored config), and the four dead-element keys only when is_alert_dead_elements is present. Sending is_paused short-circuits the whole request: the pause/resume is applied and every other field in the same body is ignored.
Authorizations:
path Parameters
| id required | integer\d+ Monitor ID |
Request Body schema: application/json
| is_paused | boolean Pause (true) or resume (false) the monitor. When present and non-null, ONLY the pause/resume action is performed — every other field in the same body is silently ignored and the response is just {data:{id}}. An explicit null is not a pause instruction and falls through to the normal update. |
| url | string URL to monitor page speed for. |
| project_name | string Monitor display name. It must stay unique within the account, otherwise the request is refused with |
| monitoring_interval_slider | integer Monitoring interval slider value: 0-60 (minutes; 0 = every 30 seconds) or a period code 61-65 (2h, 3h, 6h, 12h, 24h). Out of range is refused with |
| report_down_slider | integer Report-down delay slider value: 0-60 minutes to wait before an outage is reported (0 = report as soon as it is confirmed). Out of range is refused with |
| probes | string Monitoring locations: "all", or a comma-separated list of probe ids. An explicit list must name at least 6 probes, otherwise |
| contacts | Array of integers Alert recipient ids. Omitting the key leaves the stored recipients alone (the manager receives a "no_changes" sentinel); sending an empty list is refused with |
| groups | Array of integers Monitor-group ids. Every id must belong to the account ( |
| is_alert_speed_drops | integer Enable speed-drop alerts (0=no, 1=yes). The threshold/window ranges below are only enforced while this is 1. |
| alert_speed_threshold | integer Speed alert threshold in seconds (1-30); |
| alert_speed_time | integer Speed alert evaluation window in minutes (20-60); |
| is_alert_vitals | integer Master toggle for web-vitals budget alerts (0=no, 1=yes). Part of the touch-gated vitals group: send any one of the five vitals keys and all five are written, the ones you omit backfilled from the stored config. |
| alert_lcp_ms | integer LCP budget in ms. 0 records the metric without alerting; any other value must be 100-60000 or the request is refused with |
| alert_fcp_ms | integer FCP budget in ms. 0 records the metric without alerting; any other value must be 100-60000 ( |
| alert_ttfb_ms | integer TTFB budget in ms. 0 records the metric without alerting; any other value must be 50-30000 ( |
| alert_cls_x1000 | integer CLS budget multiplied by 1000 (100 = a CLS of 0.10). 0 records the metric without alerting; any other value must be 10-10000 ( |
| is_alert_dead_elements | integer Alert when a page element keeps failing (0=no, 1=yes); detection itself always runs. This key is the gate for the whole dead-element group — the three fields below are only written when it is present. |
| dead_element_checks | integer Consecutive failing checks before the dead-element alert fires (1-10); |
| dead_element_watch_types | string Comma-separated element types to alert on, from image, script, css and other. Only read when |
| dead_element_ignore | string Ignore globs, one per line — matching elements are recorded but never alert. Only read when |
| report_up | integer Send notification when monitor recovers (0=no, 1=yes). |
| branding_id | integer Branding template used for this monitor's alert emails and reports. 0 uses the default theme; an id not owned by the account (or a non-numeric value) is refused with |
| confirmations_required | integer Enum: 1 2 3 How many probes must independently observe the failure before it is acted on. Clamped into 1-3 rather than refused. Omitted, the stored value is preserved. |
| escalation_policy_id | integer Escalation policy that routes this monitor's alerts. 0 detaches (alert every recipient at once); a value above 0 must reference a policy owned by the account or the request is refused with |
| notes | string Free-text operator note. Trimmed and truncated to 500 characters; a non-string value stores an empty string. |
(string or null) or (integer or null) Records WHICH saved Location Profile the vantage selection came from, so the editor's Locations tab can show the applied profile. Either a built-in synthetic id ("builtin: |
Responses
Request samples
- Payload
{- "is_paused": false,
- "project_name": "My Website Speed",
- "monitoring_interval_slider": 5,
- "report_down_slider": 1,
- "probes": "all",
- "contacts": [
- 42
], - "groups": [
- 7
], - "is_alert_speed_drops": 0,
- "alert_speed_threshold": 5,
- "alert_speed_time": 30,
- "is_alert_vitals": 0,
- "alert_lcp_ms": 2500,
- "alert_fcp_ms": 0,
- "alert_ttfb_ms": 800,
- "alert_cls_x1000": 100,
- "is_alert_dead_elements": 0,
- "dead_element_checks": 3,
- "dead_element_watch_types": "image,script,css",
- "dead_element_ignore": "*/pixel/*.gif",
- "report_up": 0,
- "branding_id": 0,
- "confirmations_required": 3,
- "escalation_policy_id": 0,
- "notes": "Checkout page — owned by the storefront team.",
- "location_profile": "builtin:europe"
}Response samples
- 200
- 400
- 401
- 404
{- "data": {
- "id": 4211
}
}List incidents for a speed monitor
Returns a list of incidents for the specified speed monitor within the given date range. Defaults to the last 30 days.
Authorizations:
path Parameters
| id required | integer\d+ Monitor ID |
query Parameters
| date_start | integer Start date as Unix timestamp (defaults to 30 days ago) |
| date_end | integer End date as Unix timestamp (defaults to now) |
| limit | integer Maximum number of incidents to return |
| offset | integer Number of incidents to skip |
Responses
Response samples
- 200
- 401
- 404
{- "data": [
- {
- "error_id": 0,
- "impact": { },
- "error_display": "string",
- "time_started": "string",
- "date_ended": "string",
- "time_elapsed": "string"
}
], - "metadata": {
- "total": 0
}
}Get a single speed incident
Returns the full detail of a specific incident for the given speed monitor, including the parsed error list, the probe servers that detected the incident, the captured response headers/body, traceroute and screenshot.
Authorizations:
path Parameters
| id required | integer\d+ Monitor ID |
| incidentId required | integer\d+ Incident ID |
Responses
Response samples
- 200
- 401
- 404
{- "data": {
- "id": 0,
- "impact": { },
- "name": "string",
- "monitor_id": 0,
- "type": "Speed",
- "type_code": "speed",
- "url": "string",
- "port": 0,
- "time_started": "string",
- "time_started_unix": 0,
- "time_ended": "string",
- "time_ended_unix": 0,
- "error": "string",
- "duration": "string",
- "errors": [
- {
- "error": "string",
- "count": 0
}
], - "servers": [
- {
- "name": "string",
- "flag": "string",
- "ip": "string",
- "date": "string",
- "error": "string"
}
], - "response_headers": "string",
- "response_body": "string",
- "traceroute": "string",
- "screenshot": "string",
- "is_critical": true,
- "is_trouble": true
}
}Delete a speed incident
Permanently deletes a specific speed incident and its associated status page threads. The incident must belong to the monitor in the path; otherwise 404 incident_not_found.
Authorizations:
path Parameters
| id required | integer\d+ Monitor ID |
| incidentId required | integer\d+ Incident ID |
Responses
Response samples
- 200
- 400
- 401
- 404
- 409
{- "status": "success"
}List speed monitoring logs
Returns monitoring check logs for the specified speed monitor, newest first. Defaults to the last 14 days and a page size of 10; use limit/offset to page through the window. date_start is raised to the account plan's history retention floor.
Authorizations:
path Parameters
| id required | integer\d+ Monitor ID |
query Parameters
| date_start | integer Start date as Unix timestamp (defaults to 14 days ago) |
| date_end | integer End date as Unix timestamp (defaults to now) |
| limit | integer Page size (default 10, capped at 500) |
| offset | integer Number of log entries to skip |
Responses
Response samples
- 200
- 401
- 404
{- "data": [
- {
- "id": 0,
- "flag": "string",
- "location": "string",
- "time_elapsed": "string",
- "speed": "string",
- "status_icon": "string",
- "status_alt": "string"
}
], - "metadata": {
- "total": 0
}
}Get a single speed monitoring log entry
Returns full details of a specific speed monitoring check log. The row is addressed by its unix-second time, which is the same value GET /api/v2/speed/{id}/logs returns as each entry's id. Timestamps render in the account timezone.
Authorizations:
path Parameters
| id required | integer\d+ Monitor ID |
| logId required | integer\d+ Example: 1756000000 Log entry ID — the check's unix-second |
Responses
Response samples
- 200
- 401
- 404
{- "data": {
- "id": 0,
- "type_code": "speed",
- "name": "My Website Speed",
- "type": "Speed",
- "port": 0,
- "status": 1,
- "response_time": "1.4 sec",
- "time": "Mar 15 2026, 14:30:00",
- "flag": "gb",
- "location": "London",
- "response": "",
- "error": ""
}
}Get timeline segments for a speed monitor
Returns up, down, trouble, maintenance, paused and unknown timeline segments for the speed monitor over the requested date range, suitable for rendering an availability timeline strip. date_start is raised to the account plan's history retention floor.
Authorizations:
path Parameters
| id required | integer\d+ Monitor ID |
query Parameters
| date_start | integer Start date as Unix timestamp (defaults to 7 days ago) |
| date_end | integer End date as Unix timestamp (defaults to now) |
Responses
Response samples
- 200
- 400
- 401
- 404
{- "data": {
- "segments": [
- {
- "type": "up",
- "durationSeconds": 86400,
- "startTime": "2026-03-15T14:30:00+00:00",
- "endTime": "2026-03-16T14:30:00+00:00",
- "label": "1 day",
- "error": "Connection timed out",
- "incidentId": "90211"
}
], - "intervals": {
- "1": "15:00",
- "2": "17:15",
- "3": "19:45",
- "4": "22:15",
- "5": "00:45"
}
}
}Get speed load time chart data
Returns load time and downtime chart data for the specified speed monitor, including per-point probe location. Data is cached for 60 seconds.
Authorizations:
path Parameters
| id required | integer\d+ Monitor ID |
query Parameters
| date_start | integer Start date as Unix timestamp (defaults to 24 hours ago) |
| date_end | integer End date as Unix timestamp (defaults to now) |
Responses
Response samples
- 200
- 401
- 404
{- "data": {
- "speed": [
- 0
], - "downtime": [
- 0
], - "date": [
- "string"
], - "location": [
- {
- "name": "string",
- "flag": "string"
}
], - "max_value": 0
}
}Get speed monitor averages
Returns aggregated average metrics for the specified speed monitor within the given date range: load time, requests, size, own domain time, and number of hosts.
Authorizations:
path Parameters
| id required | integer\d+ Monitor ID |
query Parameters
| date_start | integer Start date as Unix timestamp (defaults to 30 days ago) |
| date_end | integer End date as Unix timestamp (defaults to now) |
Responses
Response samples
- 200
- 401
- 404
{- "data": {
- "load_time": 0,
- "total_resource_time": 0,
- "requests": 0,
- "size": 0,
- "own_domain_time": 0,
- "own_domain_pct": 0,
- "third_party_time": 0,
- "hosts": 0,
- "total_checks": 0
}
}Get web-vitals & breakdown chart data
Consolidated per-check history for the speed detail page: median-bucketed series for full page load, lab web vitals (TTFB/FCP/LCP/CLS), own-vs-third-party time, requests, hosts and per-type transfer sizes; plus range summary, the latest check per location, the configured budgets, and the locations present in the range. Vitals are lab values measured in the same Chrome run as the load time; checks from probes that do not record vitals yet are excluded from the vitals series (never zero-filled).
Authorizations:
path Parameters
| id required | integer\d+ Monitor ID |
query Parameters
| date_start | integer Start as Unix timestamp (defaults to 24 hours ago) |
| date_end | integer End as Unix timestamp (defaults to now) |
| location | string Example: location=all Probe server ID to filter to, or 'all' (default) for the cross-location median |
Responses
Response samples
- 200
- 401
- 404
{- "data": {
- "bucket_seconds": 0,
- "checks_total": 300,
- "buckets": [
- {
- "t": 0,
- "checks": 0,
- "load_ms": 0,
- "ttfb_ms": 0,
- "fcp_ms": 0,
- "lcp_ms": 0,
- "cls": 0,
- "own_ms": 0,
- "third_ms": 0,
- "hosts": 0,
- "requests": 0,
- "size_image": 0,
- "size_script": 0,
- "size_css": 0,
- "size_doc": 0,
- "size_other": 0
}
], - "summary": {
- "checks": 288,
- "vitals_checks": 240,
- "load_ms": 1412.5,
- "ttfb_ms": 210,
- "fcp_ms": 940,
- "lcp_ms": 1830,
- "cls": 0.05,
- "hosts": 8,
- "requests": 64
}, - "latest": [
- {
- "server": 12,
- "name": "London",
- "flag": "GB",
- "time": 1756100300,
- "load_ms": 1380,
- "ttfb_ms": 205,
- "fcp_ms": 910,
- "lcp_ms": 1790,
- "cls": 0.04
}
], - "locations": [
- {
- "id": 12,
- "name": "London",
- "flag": "GB"
}
], - "budgets": {
- "is_alert_vitals": 0,
- "lcp_ms": 2500,
- "fcp_ms": 0,
- "ttfb_ms": 800,
- "cls": 0.1,
- "window_minutes": 100,
- "is_alert_speed_drops": 0,
- "speed_threshold_s": 5
}, - "error": "data_unavailable"
}
}Get dead/broken elements
Returns the dead-element report for the monitored page: every failing asset seen in the range with its type, sighting streaks, status and probe locations, plus a bucketed sightings series, the ignore-list hit count and the monitor alert config.
Authorizations:
path Parameters
| id required | integer\d+ Monitor ID |
query Parameters
| date_start | integer Start date as Unix timestamp (defaults to 30 days ago) |
| date_end | integer End date as Unix timestamp (defaults to now) |
Responses
Response samples
- 200
- 401
- 404
{- "report": {
- "elements": [
- {
- "link": "string",
- "type": "string",
- "error": "string",
- "first_seen": 0,
- "last_seen": 0,
- "sightings": 0,
- "streak": 0,
- "max_streak": 0,
- "status": "string",
- "locations": [
- "string"
]
}
], - "ignored_count": 0,
- "series": [
- { }
], - "bucket_seconds": 0,
- "config": { }
}
}Get PageSpeed/Lighthouse scores
Returns the latest Lighthouse/PageSpeed analysis for the monitored page, including score, host count, resource count, page size, and detailed insights.
Authorizations:
path Parameters
| id required | integer\d+ Monitor ID |
Responses
Response samples
- 200
- 401
- 404
{- "data": {
- "summary": {
- "id": 0,
- "score": 0,
- "number_of_hosts": 0,
- "number_of_resources": 0,
- "page_size": "string",
- "page_size_plain": 0,
- "time": 0
}, - "insights": { }
}
}Get component breakdown
Returns the time (seconds), average size, and request count broken down by component type (script, HTML, CSS, image, text, redirect) for the specified date range.
Authorizations:
path Parameters
| id required | integer\d+ Monitor ID |
query Parameters
| date_start | integer Start date as Unix timestamp (defaults to 30 days ago) |
| date_end | integer End date as Unix timestamp (defaults to now) |
Responses
Response samples
- 200
- 401
- 404
{- "data": {
- "script": {
- "speed": 0.05,
- "size": "string",
- "requests": 0.5
}, - "html": {
- "speed": 0,
- "size": "string",
- "requests": 0
}, - "css": {
- "speed": 0,
- "size": "string",
- "requests": 0
}, - "image": {
- "speed": 0,
- "size": "string",
- "requests": 0
}, - "text": {
- "speed": 0,
- "size": "string",
- "requests": 0
}, - "redirect": {
- "speed": 0,
- "size": "string",
- "requests": 0
}
}
}List all transaction monitors
Returns a list of all transaction monitors for the authenticated account. Supports filtering by monitor status.
Authorizations:
query Parameters
| show_is_up | boolean Default: true Include monitors that are up |
| show_is_critical | boolean Default: true Include monitors with critical incidents |
| show_is_trouble | boolean Default: true Include monitors with trouble incidents |
| show_is_suspended | boolean Default: true Include suspended monitors |
| show_is_paused | boolean Default: true Include paused monitors |
| show_is_maintenance | boolean Default: true Include monitors in maintenance |
| limit | integer Example: limit=25 Maximum number of monitors to return (0 = no limit) |
| offset | integer Example: offset=0 Number of monitors to skip |
Responses
Response samples
- 200
- 401
{- "data": [
- {
- "id": 0,
- "name": "string",
- "is_up": true,
- "is_critical": true,
- "is_trouble": true,
- "is_suspended": true,
- "is_paused": true,
- "is_maintenance": true,
- "groups": [
- 0
]
}
], - "metadata": {
- "total": 0,
- "up": 0,
- "critical": 0,
- "trouble": 0,
- "suspended": 0,
- "paused": 0,
- "maintenance": 0
}
}Create a new transaction monitor
Creates a new transaction monitor with its step definitions — the user journey a probe replays on every check. Speed and Transaction share ONE "Advanced" plan pool, so creation is refused with 403 plan_exceeded once that pool is full; the check runs before anything is written. At least one step is required, and the FIRST step (the one numbered 1) must carry a usable URL in element_value_1 — that value is what the blacklist and URL validation are run against.
Authorizations:
Request Body schema: application/json
| project_name required | string Monitor display name. Required — an empty value is refused with |
| monitoring_interval_slider | integer Monitoring interval slider value in minutes. Defaults to 5. Unlike the speed family this is NOT range-validated at the API edge, and the resolved interval is then floored at 600 seconds — a transaction monitor cannot run more often than every 10 minutes, so anything shorter is snapped up rather than refused. |
| report_down_slider | integer Report-down delay slider value — minutes to wait before an outage is reported; 0 alerts as soon as the failure is confirmed. Defaults to 1. Not range-validated at the API edge. |
| contacts required | Array of integers Alert recipient ids. Required in practice: on create an omitted (or empty) list is refused with |
| groups | Array of integers Monitor-group ids. Every id must belong to the account ( |
| report_up | integer Send a notification when the monitor recovers (0=no, 1=yes). Defaults to 1 on create — the opposite of the speed family, which defaults to 0. |
| branding_id | integer Branding template used for this monitor's alert emails and reports. 0 (the default) uses the default theme; an id not owned by the account, or a non-numeric value, is refused with |
string or Array of integers Vantage selection: the literal string "all" (the default) or a list of probe ids. Transaction does NOT enforce the six-location minimum the uptime/ssl/speed families apply, so a shorter explicit list is accepted here. | |
| confirmations_required | integer Enum: 1 2 3 How many probes must independently observe the failure before it is acted on. Clamped into 1-3 (and clamped again by the save path), so an out-of-range value is silently corrected rather than refused. Defaults to 3 on create. |
| escalation_policy_id | integer Escalation policy that routes this monitor's alerts. 0 (the default) alerts every recipient at once; a value above 0 must reference a policy owned by the account or the request is refused with |
| notes | string Free-text operator note stored on the monitor. Trimmed and truncated to 500 characters; a non-string value stores an empty string. Shown on the incident page and included in alert emails and webhook payloads. |
(string or null) or (integer or null) Records WHICH saved Location Profile the vantage selection came from, so the editor's Locations tab can show the applied profile. Either a built-in synthetic id ("builtin: | |
required | Array of objects The journey to replay, at least one step. Each entry is keyed by its own |
Responses
Request samples
- Payload
{- "project_name": "Checkout journey",
- "monitoring_interval_slider": 10,
- "report_down_slider": 1,
- "contacts": [
- 42
], - "groups": [
- 7
], - "report_up": 1,
- "branding_id": 0,
- "probes": "all",
- "confirmations_required": 3,
- "escalation_policy_id": 0,
- "notes": "Runs against the staging card processor.",
- "location_profile": "builtin:europe",
- "steps": [
- {
- "number": 1,
- "action_name": "go_to_url",
- "element_value_2": "",
- "element_value_3": "",
- "skip_step_if_fails": 0,
- "wait_before_starting": 0,
- "perform_if_element_found": "",
- "perform_if_text_found": "",
- "perform_if_element_found_existance": "",
- "perform_if_text_found_existance": ""
}
]
}Response samples
- 201
- 400
- 401
- 403
{- "data": {
- "id": 3180
}
}Get a single transaction monitor
Returns full details of a specific transaction monitor: configuration, alert recipients, group memberships, the applied Location Profile and the ordered step definitions. This is the editor's load path, so the stored selectors are cleaned for display — a legacy ":(N)" token is rewritten to ":nth-child(N)" and ":(0)" is dropped — which means a selector read here can differ from the exact bytes stored.
Authorizations:
path Parameters
| id required | integer\d+ Monitor ID |
Responses
Response samples
- 200
- 401
- 404
{- "data": {
- "id": "3180",
- "name": "Checkout journey",
- "is_up": true,
- "is_critical": false,
- "is_trouble": false,
- "is_suspended": false,
- "is_paused": false,
- "is_maintenance": false,
- "groups": [
- "7"
], - "interval": 300,
- "report_down": 60,
- "report_up": 1,
- "incident_id": 90211,
- "locations": [
- 12
], - "branding_id": 0,
- "contacts": [
- {
- "id": "42",
- "is_team": "0",
- "is_user": "0",
- "is_operator": "0",
- "is_individual_contact": "1"
}
], - "confirmations_required": 3,
- "escalation_policy_id": 0,
- "notes": "",
- "applied_location_profile": "builtin:europe",
- "last_check": 1761600000,
- "steps": [
- {
- "number": 1,
- "action_name": "go_to_url",
- "element_value_2": "",
- "element_value_3": "",
- "skip_step_if_fails": 0,
- "wait_before_starting": 0,
- "perform_if_element_found": "",
- "perform_if_text_found": "",
- "perform_if_element_found_existance": "",
- "perform_if_text_found_existance": ""
}
]
}
}Update a transaction monitor
Updates an existing transaction monitor. Only include fields you want to change — omitted fields, and the whole step list, retain their stored values. steps is a FULL REPLACE when present: the step table is rewritten from what you send, so a partial list deletes the steps you left out. Pause/resume rides along instead of short-circuiting: is_paused is lifted out of the payload and applied AFTER the rest of the update succeeds, so a rejected save leaves the paused state untouched; sending is_paused alone answers with the bare id.
Authorizations:
path Parameters
| id required | integer\d+ Monitor ID |
Request Body schema: application/json
| is_paused | boolean true pauses the monitor, false resumes it. Applied only after the rest of the update has been saved, so a validation failure never commits the pause. An explicit null is not a pause instruction. When it is the ONLY key in the body the response is just {data:{id}}; the write is read back, and a pause that did not land answers 400 |
| project_name | string Monitor display name. Must stay non-empty ( |
| monitoring_interval_slider | integer Monitoring interval slider value in minutes. Not range-validated at the API edge, and the resolved interval is floored at 600 seconds — a transaction monitor cannot run more often than every 10 minutes. Omit it to preserve the stored interval in seconds. |
| report_down_slider | integer Report-down delay slider value — minutes to wait before an outage is reported; 0 alerts as soon as the failure is confirmed. Omit it to preserve the stored delay in seconds. |
| contacts | Array of integers Alert recipient ids. Omitting the key preserves the stored recipients — the editor rewrites the join tables unconditionally, so this family re-sends the current list rather than passing a sentinel. Sending an empty list is refused with |
| groups | Array of integers Monitor-group ids. Every id must belong to the account ( |
| report_up | integer Send a notification when the monitor recovers (0=no, 1=yes). |
| branding_id | integer Branding template used for this monitor's alert emails and reports. 0 uses the default theme; an id not owned by the account, or a non-numeric value, is refused with |
string or Array of integers Vantage selection: "all" or a list of probe ids. Omitted, the stored selection is preserved rather than widened back to "all". Transaction does NOT enforce the six-location minimum the uptime/ssl/speed families apply. | |
| confirmations_required | integer Enum: 1 2 3 How many probes must independently observe the failure before it is acted on. Clamped into 1-3 rather than refused. Omitted, the stored value is preserved. |
| escalation_policy_id | integer Escalation policy that routes this monitor's alerts. 0 detaches (alert every recipient at once); a value above 0 must reference a policy owned by the account or the request is refused with |
| notes | string Free-text operator note. Trimmed and truncated to 500 characters; a non-string value stores an empty string. |
(string or null) or (integer or null) Records WHICH saved Location Profile the vantage selection came from, so the editor's Locations tab can show the applied profile. Either a built-in synthetic id ("builtin: | |
Array of objects FULL REPLACE of the journey — send every step you want to keep, in order. Omit the key entirely to preserve the stored steps. The first entry must be the one numbered 1 carrying the opening URL, which is what |
Responses
Request samples
- Payload
{- "is_paused": false,
- "project_name": "Checkout journey",
- "monitoring_interval_slider": 10,
- "report_down_slider": 1,
- "contacts": [
- 42
], - "groups": [
- 7
], - "report_up": 1,
- "branding_id": 0,
- "probes": "all",
- "confirmations_required": 3,
- "escalation_policy_id": 0,
- "notes": "Runs against the staging card processor.",
- "location_profile": "builtin:europe",
- "steps": [
- {
- "number": 1,
- "action_name": "go_to_url",
- "element_value_2": "",
- "element_value_3": "",
- "skip_step_if_fails": 0,
- "wait_before_starting": 0,
- "perform_if_element_found": "",
- "perform_if_text_found": "",
- "perform_if_element_found_existance": "",
- "perform_if_text_found_existance": ""
}
]
}Response samples
- 200
- 400
- 401
- 404
{- "data": {
- "id": 3180
}
}List incidents for a transaction monitor
Returns a list of incidents for the specified transaction monitor within the given date range. Defaults to the last 30 days.
Authorizations:
path Parameters
| id required | integer\d+ Monitor ID |
query Parameters
| date_start | integer Start date as Unix timestamp (defaults to 30 days ago) |
| date_end | integer End date as Unix timestamp (defaults to now) |
| limit | integer Maximum number of incidents to return |
| offset | integer Number of incidents to skip |
Responses
Response samples
- 200
- 401
- 404
{- "data": [
- {
- "error_id": 0,
- "impact": { },
- "error_display": "string",
- "time_started": "string",
- "date_ended": "string",
- "time_elapsed": "string"
}
], - "metadata": {
- "total": 0
}
}Get incident detail with step breakdown
Returns details of a specific transaction incident, including a per-step breakdown with durations, action names and screenshot URLs. The incident must belong to the monitor in the path. The step breakdown is only assembled while the run is within the one-week step-retention window and its log rows still exist; outside that the steps array comes back empty while the rest of the incident is still reported. Timestamps render in the account timezone.
Authorizations:
path Parameters
| id required | integer\d+ Monitor ID |
| incidentId required | integer\d+ Incident ID |
Responses
Response samples
- 200
- 401
- 404
{- "data": {
- "id": 90211,
- "impact": { },
- "time_started": "March 15, 2026 14:30",
- "time_started_unix": 1773584400,
- "error_display": "Step 3 failed: element not found",
- "date_ended": "Ongoing",
- "steps": [
- {
- "number": 3,
- "action_name": "Click on the element",
- "attribute": "#checkout-submit",
- "duration": "412 ms",
- "status": "error",
- "screenshot_url": "/uploads/transaction-screenshots/3180-1756000000-3.png"
}
]
}
}Delete a transaction incident
Permanently deletes one CLOSED incident of the specified monitor. The currently open incident cannot be deleted.
Authorizations:
path Parameters
| id required | integer\d+ Monitor ID |
| incidentId required | integer\d+ Incident ID |
Responses
Response samples
- 200
- 400
- 401
- 404
- 409
{- "status": "success"
}List monitoring logs for a transaction monitor
Returns recent monitoring check logs for the specified transaction monitor within the given date range, limited to 20 entries. Defaults to the last 14 days when no range is supplied.
Authorizations:
path Parameters
| id required | integer\d+ Monitor ID |
query Parameters
| date_start | integer Start date as Unix timestamp (defaults to 14 days ago) |
| date_end | integer End date as Unix timestamp (defaults to now) |
| limit | integer Maximum number of log entries to return |
| offset | integer Number of log entries to skip |
Responses
Response samples
- 200
- 401
- 404
{- "data": [
- {
- "id": 0,
- "flag": "string",
- "location": "string",
- "time_elapsed": "string",
- "speed": "string",
- "status_icon": "string",
- "status_alt": "string"
}
], - "metadata": {
- "total": 0
}
}Get a single transaction monitoring log entry
Returns full details of a specific transaction monitoring check log — the run summary, not the per-step breakdown (use GET /api/v2/transaction/{id}/last-run or the incident detail endpoint for steps). A transaction result is keyed by monitor + unix-second time, and that time is both the {logId} you address here and the id the list endpoint returns, so the two round-trip. Timestamps render in the account timezone.
Authorizations:
path Parameters
| id required | integer\d+ Monitor ID |
| logId required | integer\d+ Example: 1756000000 Log entry ID — the run's unix-second |
Responses
Response samples
- 200
- 401
- 404
{- "data": {
- "id": 1756000000,
- "type_code": "transaction",
- "name": "Checkout journey",
- "type": "Transaction",
- "port": 0,
- "status": 1,
- "response_time": "8.4 s",
- "time": "Mar 15 2026, 14:30:00",
- "flag": "gb",
- "location": "London",
- "response": "",
- "error": ""
}
}Get the last run of a transaction monitor
Returns the most recent transaction run with its overall status, total duration and per-step breakdown (step number, action name, attribute, duration, status and a screenshot URL when one was captured). data is null when the monitor has never run, and also when the run exists but its per-step log rows or the monitor's step definitions could not be read — the two cases are indistinguishable from the response. Unlike the incident breakdown, only the LAST step of a failed run is marked error; every earlier step is labelled success because it was logged, not because it was verified.
Authorizations:
path Parameters
| id required | integer\d+ Monitor ID |
Responses
Response samples
- 200
- 401
- 404
{- "data": {
- "time": 1756000000,
- "status": "success",
- "total_duration": 8.42,
- "steps": [
- {
- "number": 1,
- "action_name": "Go to URL",
- "duration": "412 ms",
- "status": "success",
- "screenshot_url": "/uploads/transaction-screenshots/3180-1756000000-1.png"
}
]
}
}Get timeline segments for a transaction monitor
Returns up, down, trouble, maintenance, paused and unknown timeline segments for the transaction monitor over the requested date range, suitable for rendering an availability timeline strip. Unlike the speed family this endpoint does NOT clamp date_start to the plan's history retention floor, so an older window is answered as asked.
Authorizations:
path Parameters
| id required | integer\d+ Monitor ID |
query Parameters
| date_start | integer Start date as Unix timestamp (defaults to 7 days ago) |
| date_end | integer End date as Unix timestamp (defaults to now) |
Responses
Response samples
- 200
- 400
- 401
- 404
{- "data": {
- "segments": [
- {
- "type": "up",
- "durationSeconds": 86400,
- "startTime": "2026-03-15T14:30:00+00:00",
- "endTime": "2026-03-16T14:30:00+00:00",
- "label": "1 day",
- "error": "Step 3 failed",
- "incidentId": "90211"
}
], - "intervals": {
- "1": "15:00",
- "2": "17:15",
- "3": "19:45",
- "4": "22:15",
- "5": "00:45"
}
}
}Get stacked step chart data
Returns step-by-step timing data for building a stacked chart of transaction execution durations. In addition to the fixed fields, the response contains one dynamic series per step ("step_1", "step_2", ... up to "step_N" where N is max_steps); each step_N is an array of that step's duration in seconds for each data point (0 when the step did not run). Returns an empty object when there is no data in the range.
Authorizations:
path Parameters
| id required | integer\d+ Monitor ID |
query Parameters
| date_start | integer Start date as Unix timestamp (defaults to 24 hours ago) |
| date_end | integer End date as Unix timestamp (defaults to now) |
Responses
Response samples
- 200
- 401
- 404
{- "data": {
- "date": [
- "string"
], - "max_value": 0,
- "max_steps": 0,
- "total": [
- 0
], - "step_1": [
- 0
], - "property1": [
- 0
], - "property2": [
- 0
]
}
}Get availability percentages for a transaction monitor
Calculates uptime and downtime percentages for the specified transaction monitor within the given date range. Defaults to the last 24 hours.
Authorizations:
path Parameters
| id required | integer\d+ Monitor ID |
query Parameters
| date_start | integer Start date as Unix timestamp (defaults to 24 hours ago) |
| date_end | integer End date as Unix timestamp (defaults to now) |
Responses
Response samples
- 200
- 400
- 401
- 404
{- "data": {
- "uptime": 99.95,
- "downtime": 0.05
}
}Get step definitions for a transaction monitor
Returns just the configured step definitions for the specified transaction monitor — the same steps array GET /api/v2/transaction/{id} embeds, without the rest of the record. Like that endpoint it is an editor load path, so a legacy ":(N)" selector token is rewritten to ":nth-child(N)" and ":(0)" is dropped, which means a selector read here can differ from the exact bytes stored. An empty array when the monitor has no steps.
Authorizations:
path Parameters
| id required | integer\d+ Monitor ID |
Responses
Response samples
- 200
- 401
- 404
{- "data": [
- {
- "number": 1,
- "action_name": "go_to_url",
- "element_value_2": "",
- "element_value_3": "",
- "skip_step_if_fails": 0,
- "wait_before_starting": 0,
- "perform_if_element_found": "",
- "perform_if_text_found": "",
- "perform_if_element_found_existance": "",
- "perform_if_text_found_existance": ""
}
]
}Queue an immediate test run for a transaction monitor
Schedules the specified transaction monitor for an immediate check by setting its next check time to now. This is an asynchronous operation: the test is queued for a probe to pick up and does NOT run synchronously, so live step results and screenshots are not returned here. The response always reports a "queued" status, echoes the monitor ID, and lists the monitor's steps with a placeholder "queued" status (duration "0ms", no screenshot). Poll GET /api/v2/transaction/{id}/last-run afterwards to retrieve the actual run results.
Authorizations:
path Parameters
| id required | integer\d+ Monitor ID |
Responses
Response samples
- 200
- 401
- 404
{- "data": {
- "status": "queued",
- "monitor_id": 0,
- "steps": [
- {
- "number": 0,
- "action_name": "string",
- "status": "queued",
- "duration": "0ms",
- "screenshot_url": "string"
}
]
}
}Run tests for all transaction monitors
Queues every non-suspended transaction monitor on the account for an immediate check by setting its next check time to now. Asynchronous, like the per-monitor variant: nothing runs synchronously and no results are returned — poll GET /api/v2/transaction/{id}/last-run for each monitor afterwards. Suspended monitors are skipped and are not counted. The endpoint reads no request body.
Authorizations:
Responses
Response samples
- 200
- 401
{- "data": {
- "status": "queued",
- "monitors_queued": 4
}
}Malware, software version, TLS, security header and email authentication checks of your sites.
List domain verifications
Returns the account's domain ownership verifications with the DNS record and file each one expects.
Authorizations:
Responses
Response samples
- 200
{- "data": [
- {
- "id": 41,
- "domain": "example.com",
- "token": "3f9c2a7d1e8b4c6a9d0e2f4b6a8c1d3e",
- "status": "pending",
- "method": "dns",
- "verified_at": 0,
- "last_checked": 0,
- "created_at": 0,
- "dns_record": {
- "type": "TXT",
- "host": "example.com",
- "value": "uptimia-verification=3f9c2a7d1e8b4c6a9d0e2f4b6a8c1d3e"
}, - "file": {
- "body": "uptimia-verification=3f9c2a7d1e8b4c6a9d0e2f4b6a8c1d3e"
}
}
]
}Start a domain verification
Creates a pending verification for the domain and returns its token, the TXT record to publish and the file to serve. Prove ownership with either one, then call POST /api/v2/security/verifications/{id}/check. A verification covers the domain and all its subdomains. When the account already has a verification for the domain, that one is returned (200) with its existing token.
Authorizations:
Request Body schema: application/json
| domain required | string Domain or host to verify. A pasted URL is reduced to its host; internationalised names are stored in their ASCII (punycode) form. |
Responses
Request samples
- Payload
{- "domain": "example.com"
}Response samples
- 200
- 201
- 400
- 403
{- "data": {
- "id": 41,
- "domain": "example.com",
- "token": "3f9c2a7d1e8b4c6a9d0e2f4b6a8c1d3e",
- "status": "pending",
- "method": "dns",
- "verified_at": 0,
- "last_checked": 0,
- "created_at": 0,
- "dns_record": {
- "type": "TXT",
- "host": "example.com",
- "value": "uptimia-verification=3f9c2a7d1e8b4c6a9d0e2f4b6a8c1d3e"
}, - "file": {
- "body": "uptimia-verification=3f9c2a7d1e8b4c6a9d0e2f4b6a8c1d3e"
}
}
}Check a domain verification now
Looks for the TXT record, then fetches the verification file (https first, then http; redirects only within the same host), and stores the result. Either proof verifies the domain. A failed check leaves a pending verification pending; on a verified domain it counts toward the 3 failed checks in a row that mark it lost.
Authorizations:
path Parameters
| id required | integer\d+ Verification ID |
Responses
Response samples
- 200
- 404
- 429
{- "data": {
- "id": 41,
- "domain": "example.com",
- "token": "3f9c2a7d1e8b4c6a9d0e2f4b6a8c1d3e",
- "status": "pending",
- "method": "dns",
- "verified_at": 0,
- "last_checked": 0,
- "created_at": 0,
- "dns_record": {
- "type": "TXT",
- "host": "example.com",
- "value": "uptimia-verification=3f9c2a7d1e8b4c6a9d0e2f4b6a8c1d3e"
}, - "file": {
- "body": "uptimia-verification=3f9c2a7d1e8b4c6a9d0e2f4b6a8c1d3e"
}
}, - "checked": {
- "dns": true,
- "file": true
}
}List all security monitors
Returns a paginated list of all security monitors for the authenticated account. Supports filtering by monitor status.
Authorizations:
query Parameters
| limit | integer Example: limit=25 Maximum number of monitors to return (0 = no limit) |
| offset | integer Example: offset=0 Number of monitors to skip |
| show_is_up | boolean Default: true Include monitors that are up |
| show_is_critical | boolean Default: true Include monitors with critical incidents |
| show_is_trouble | boolean Default: true Include monitors with trouble incidents |
| show_is_suspended | boolean Default: true Include suspended monitors |
| show_is_paused | boolean Default: true Include paused monitors |
| show_is_maintenance | boolean Default: true Include monitors in maintenance |
Responses
Response samples
- 200
- 401
{- "data": [
- {
- "id": 0,
- "name": "string",
- "is_up": true,
- "is_critical": true,
- "is_trouble": true,
- "is_suspended": true,
- "is_paused": true,
- "is_maintenance": true,
- "groups": [
- {
- "id": 0,
- "name": "string"
}
]
}
], - "metadata": {
- "total": 0
}
}Create a new security monitor
Creates a new security monitor for the specified URL. Each scan checks the URL with Google Web Risk and runs the enabled check groups (see security_checks).
Authorizations:
Request Body schema: application/json
| url required | string URL to monitor for malware and security issues |
| project_name | string Monitor display name |
| monitoring_interval_slider | integer Monitoring interval slider value (determines check frequency, minimum 3600s) |
| report_down_slider | integer Report down delay slider value |
| contacts | Array of integers Contact IDs to notify on incidents |
| groups | Array of integers Group IDs to assign the monitor to |
| report_up | integer Send notification when monitor recovers (0=no, 1=yes) |
| branding_id | integer Branding template ID for notifications |
| security_checks | Array of strings Items Enum: "software" "headers" "tls" "email" "page" "dns" "mail" "mta_sts" Check groups to run besides the malware check, which always runs ( |
| security_alert_level | string Enum: "critical" "high" Lowest finding severity that opens an incident and alerts. Default |
Responses
Request samples
- Payload
{- "project_name": "My Website Security",
- "monitoring_interval_slider": 5,
- "report_down_slider": 1,
- "contacts": [
- 0
], - "groups": [
- 0
], - "report_up": 1,
- "branding_id": 0,
- "security_checks": [
- "software",
- "headers",
- "tls",
- "email",
- "page",
- "dns",
- "mail",
- "mta_sts"
], - "security_alert_level": "critical"
}Response samples
- 201
- 400
- 401
{- "data": {
- "id": 0
}
}Get a single security monitor
Returns full details of a specific security monitor including configuration, status, and assigned contacts.
Authorizations:
path Parameters
| id required | integer\d+ Monitor ID |
Responses
Response samples
- 200
- 401
- 404
{- "data": {
- "id": 0,
- "name": "string",
- "is_up": true,
- "is_critical": true,
- "is_trouble": true,
- "is_suspended": true,
- "is_paused": true,
- "is_maintenance": true,
- "groups": [
- { }
], - "interval": 0,
- "report_down": 0,
- "report_up": true,
- "url": "string",
- "incident_id": 0,
- "branding_id": 0,
- "contacts": [
- {
- "id": 0,
- "is_team": true,
- "is_user": true,
- "is_operator": true,
- "is_individual_contact": true
}
], - "security_checks": [
- "software",
- "headers",
- "tls",
- "email",
- "page",
- "dns",
- "mail",
- "mta_sts"
], - "security_alert_level": "critical",
- "findings": [
- {
- "id": 0,
- "check": "malware",
- "code": "EOL_SOFTWARE",
- "severity": "critical",
- "title": "PHP 7.4.33 is end-of-life",
- "alerts": true,
- "quiet": true,
- "baseline": true,
- "can_accept": true,
- "evidence": {
- "header": "X-Powered-By",
- "value": "PHP/7.4.33",
- "product": "php",
- "version": "7.4.33",
- "branch": "7.4"
}, - "fix": "string",
- "first_seen": 0,
- "last_seen": 0
}
], - "checks_state": {
- "malware": {
- "enabled": true,
- "last_run": 1761980400,
- "ok": true,
- "reason": null,
- "note": null,
- "next_run": 1762045200
}, - "tls": {
- "enabled": true,
- "last_run": 1761958800,
- "ok": false,
- "reason": "unreachable",
- "note": null,
- "next_run": 1761984000
}
}, - "next_scan": 1761984000,
- "owner_verification": {
- "domain": "example.com",
- "status": "pending",
- "verified_at": 0
}, - "owner_checks": {
- "eligible": true,
- "plan": true,
- "verified": true,
- "exposed_files_active": true
}, - "accepted_findings": [
- {
- "id": 0,
- "check": "malware",
- "code": "NEW_EXTERNAL_SCRIPT",
- "subject": "string",
- "severity": "critical",
- "title": "string",
- "accepted_at": 0,
- "last_seen": 0
}
], - "unscannable_url": false,
- "manual_check": {
- "requested_at": 1761984000,
- "running": true
}
}
}Delete a security monitor
Permanently deletes a security monitor and its associated reporting configuration. Incident and log data may be retained.
Authorizations:
path Parameters
| id required | integer\d+ Monitor ID |
Responses
Response samples
- 200
- 400
- 401
- 404
{- "status": "success"
}Update an existing security monitor
Updates an existing security monitor. Only include fields you want to change. Omitted fields retain their current values.
Authorizations:
path Parameters
| id required | integer\d+ Monitor ID |
Request Body schema: application/json
| is_paused | boolean Pause (true) or resume (false) the monitor. May be sent alone or together with configuration fields — both are then applied, and the pause only after the rest of the payload has validated. |
| url | string URL to monitor for malware and security issues |
| project_name | string Monitor display name |
| monitoring_interval_slider | integer Monitoring interval slider value |
| report_down_slider | integer Report down delay slider value |
| contacts | Array of integers Contact IDs to notify |
| groups | Array of integers Group IDs |
| report_up | integer Send notification when monitor recovers (0=no, 1=yes) |
| branding_id | integer Branding template ID |
| security_checks | Array of strings Items Enum: "software" "headers" "tls" "email" "page" "dns" "mail" "mta_sts" Check groups to run besides the malware check ( |
| security_alert_level | string Enum: "critical" "high" Lowest finding severity that opens an incident and alerts; omit to keep it. |
Responses
Request samples
- Payload
{- "is_paused": true,
- "url": "string",
- "project_name": "string",
- "monitoring_interval_slider": 0,
- "report_down_slider": 0,
- "contacts": [
- 0
], - "groups": [
- 0
], - "report_up": 0,
- "branding_id": 0,
- "security_checks": [
- "software"
], - "security_alert_level": "critical"
}Response samples
- 200
- 400
- 401
- 404
- 422
{- "data": {
- "id": 0
}
}List incidents for a security monitor
Returns a list of security incidents for the specified security monitor within the given date range. The incident code is the highest-ranked open finding code, e.g. MALWARE, SOCIAL_ENGINEERING, UNWANTED_SOFTWARE, EOL_SOFTWARE or TLS_LEGACY. Defaults to the last 30 days.
Authorizations:
path Parameters
| id required | integer\d+ Monitor ID |
query Parameters
| date_start | integer Start date as Unix timestamp (defaults to 30 days ago) |
| date_end | integer End date as Unix timestamp (defaults to now) |
| limit | integer Maximum number of incidents to return |
| offset | integer Number of incidents to skip |
Responses
Response samples
- 200
- 401
- 404
{- "data": [
- {
- "error_id": 0,
- "impact": { },
- "error_display": "string",
- "time_started": "string",
- "date_ended": "string",
- "time_elapsed": "string"
}
], - "metadata": {
- "total": 0
}
}Get a single security incident
Returns the full detail of a specific security incident for the given security monitor. The malware check is one server-side Google Web Risk lookup, so the shared incident keys that describe a probe fan-out and its HTTP exchange (servers, response headers/body, traceroute, screenshot) are always empty for this family.
Authorizations:
path Parameters
| id required | integer\d+ Monitor ID |
| incidentId required | integer\d+ Incident ID |
Responses
Response samples
- 200
- 401
- 404
{- "data": {
- "id": 0,
- "impact": { },
- "name": "string",
- "monitor_id": 0,
- "type": "Security",
- "type_code": "virus",
- "url": "string",
- "port": 0,
- "time_started": "string",
- "time_started_unix": 0,
- "time_ended": "string",
- "time_ended_unix": 0,
- "error": "string",
- "duration": "string",
- "errors": [
- {
- "error": "string",
- "count": 0
}
], - "servers": [
- {
- "name": "string",
- "flag": "string",
- "ip": "string",
- "date": "string",
- "error": "string"
}
], - "response_headers": "string",
- "response_body": "string",
- "traceroute": "string",
- "screenshot": "string",
- "is_critical": true,
- "is_trouble": true
}
}Delete a security incident
Permanently deletes a specific CLOSED security incident and its associated status page threads. The incident must belong to the monitor in the path; otherwise 404 incident_not_found. The monitor's currently open incident cannot be deleted (409).
Authorizations:
path Parameters
| id required | integer\d+ Monitor ID |
| incidentId required | integer\d+ Incident ID |
Responses
Response samples
- 200
- 400
- 401
- 404
- 409
{- "status": "success"
}List security scan logs
Returns security scan check logs for the specified monitor, newest first. Defaults to the last 90 days; use limit/offset to page through the window. date_start is raised to the account plan's history retention floor.
Authorizations:
path Parameters
| id required | integer\d+ Monitor ID |
query Parameters
| date_start | integer Start date as Unix timestamp (defaults to 90 days ago) |
| date_end | integer End date as Unix timestamp (defaults to now) |
| limit | integer Maximum number of log entries to return |
| offset | integer Number of log entries to skip |
Responses
Response samples
- 200
- 401
- 404
{- "data": [
- {
- "id": 0,
- "flag": "string",
- "location": "string",
- "time_elapsed": "string",
- "speed": "string",
- "response_time": 0,
- "status_icon": "string",
- "status_alt": "string",
- "security_summary": {
- "ran": {
- "software": true,
- "headers": true,
- "email": true,
- "tls": true
}, - "deferred": [ ],
- "open": {
- "high": 1,
- "medium": 1,
- "low": 2
}
}
}
], - "metadata": {
- "total": 0
}
}Mark a finding as expected
Closes an open finding and keeps it out of counts and alerts on this monitor until undone. The same code and subject stays accepted across scans; another subject is a new finding, and a report at a higher severity brings it back. Malware findings cannot be accepted.
Authorizations:
path Parameters
| id required | integer\d+ Monitor ID |
| findingId required | integer\d+ Open finding ID |
Responses
Response samples
- 200
- 400
- 401
- 404
{- "status": "success",
- "data": { }
}Undo a finding marked as expected
Removes the acceptance and reopens the finding it closed. For new script sources and new certificate issuers the next scan reports the value again.
Authorizations:
path Parameters
| id required | integer\d+ Monitor ID |
| acceptedId required | integer\d+ ID from |
Responses
Response samples
- 200
- 401
- 404
{- "status": "success"
}Check now
Asks for a full scan on the next scan tick, usually within a minute: the Google Web Risk lookup, every switched-on check group and, when the monitor qualifies for them, the owner-only groups, whatever their usual once-a-day cadence or a Web Risk retry wait. It does NOT scan synchronously: poll GET /api/v2/virus/{id} and read manual_check.running, checks_state and findings. Groups that do not fit one tick continue in the next ones; running stays true until they have all run. The regular scan schedule does not move. Results go through the usual alerting: a fixed issue closes its incident and a new finding alerts. While a request is still running, another one answers 202 with that request and changes nothing, so a double click is harmless. Rate-limited to once per 5 minutes per monitor and 20 per hour per ACCOUNT. Refused outright for monitors the scanner skips anyway.
Authorizations:
path Parameters
| id required | integer\d+ Example: 4211 Security monitor id. |
Responses
Response samples
- 202
- 401
- 403
- 404
- 409
- 429
{- "status": "success",
- "data": {
- "requested_at": 1756100400,
- "running": true,
- "already_requested": false
}
}Get timeline segments for a security monitor
Returns up, down, maintenance and unknown timeline segments for the security monitor over the requested date range, suitable for rendering an availability timeline strip.
Authorizations:
path Parameters
| id required | integer\d+ Monitor ID |
query Parameters
| date_start | integer Start date as Unix timestamp (defaults to 7 days ago) |
| date_end | integer End date as Unix timestamp (defaults to now) |
Responses
List all RUM monitors
Returns a paginated list of all RUM monitors for the authenticated account. Supports filtering by monitor status.
Authorizations:
query Parameters
| limit | integer Example: limit=25 Maximum number of monitors to return (0 = no limit) |
| offset | integer Example: offset=0 Number of monitors to skip |
| show_is_up | boolean Default: true Include monitors that are up |
| show_is_critical | boolean Default: true Include monitors with critical incidents |
| show_is_trouble | boolean Default: true Include monitors with trouble incidents |
| show_is_suspended | boolean Default: true Include suspended monitors |
| show_is_paused | boolean Default: true Include paused monitors |
| show_is_maintenance | boolean Default: true Include monitors in maintenance |
Responses
Response samples
- 200
- 401
{- "data": [
- {
- "id": 0,
- "name": "string",
- "is_up": true,
- "is_critical": true,
- "is_trouble": true,
- "is_suspended": true,
- "is_paused": true,
- "is_maintenance": true,
- "groups": [
- {
- "id": 0,
- "name": "string"
}
]
}
], - "metadata": {
- "total": 0
}
}Create a new RUM monitor
Creates a new Real User Monitoring site with the specified configuration.
Authorizations:
Request Body schema: application/json
| url required | string URL of the website to monitor |
| project_name required | string Monitor display name |
| apdex_score | integer Apdex threshold in seconds (1-20, default 4) |
| is_alert_traffic_stops | integer Enable traffic stop alerts (0=no, 1=yes) |
| alert_traffic_stops_minutes | integer Minutes without traffic before alerting |
| is_alert_speed_drops | integer Enable speed drop alerts (0=no, 1=yes) |
| alert_speed_drops_percent | integer Speed drop percentage threshold |
| alert_speed_drops_time | integer Minutes of sustained speed drop before alerting |
| is_alert_traffic_drops | integer Enable traffic drop alerts (0=no, 1=yes) |
| alert_traffic_drops_percent | integer Traffic drop percentage threshold |
| alert_traffic_drops_time | integer Minutes of sustained traffic drop before alerting |
| is_alert_js_errors | integer Enable JS error alerts (0=no, 1=yes) |
| alert_js_errors_percent | integer JS error percentage threshold |
| alert_js_errors_time | integer Minutes of sustained JS errors before alerting |
| contacts | Array of integers Contact IDs to notify on incidents |
integer or Array of integers Group assignment: a single group ID or an array of group IDs. | |
| branding_id | integer Branding template ID |
Responses
Request samples
- Payload
{- "project_name": "My Website",
- "apdex_score": 4,
- "is_alert_traffic_stops": 0,
- "alert_traffic_stops_minutes": 30,
- "is_alert_speed_drops": 0,
- "alert_speed_drops_percent": 50,
- "alert_speed_drops_time": 30,
- "is_alert_traffic_drops": 0,
- "alert_traffic_drops_percent": 50,
- "alert_traffic_drops_time": 30,
- "is_alert_js_errors": 0,
- "alert_js_errors_percent": 10,
- "alert_js_errors_time": 30,
- "contacts": [
- 0
], - "groups": 0,
- "branding_id": 0
}Response samples
- 201
- 400
- 401
{- "data": {
- "id": 0,
- "script_hash": "string"
}
}Get a single RUM monitor
Returns full details of a specific RUM monitor including configuration, tracking code, alert settings, and assigned contacts.
Authorizations:
path Parameters
| id required | integer\d+ Monitor ID |
Responses
Response samples
- 200
- 401
- 404
{- "data": {
- "id": 0,
- "name": "string",
- "url": "string",
- "is_up": true,
- "is_critical": true,
- "is_trouble": true,
- "is_suspended": true,
- "is_paused": true,
- "is_maintenance": true,
- "groups": [
- { }
], - "tracking_code": "string",
- "script_found": true,
- "apdex_score": 0,
- "is_alert_traffic_stops": true,
- "alert_traffic_stops_minutes": 0,
- "is_alert_speed_drops": true,
- "alert_speed_drops_percent": 0,
- "alert_speed_drops_time": 0,
- "is_alert_traffic_drops": true,
- "alert_traffic_drops_percent": 0,
- "alert_traffic_drops_time": 0,
- "is_alert_js_errors": true,
- "alert_js_errors_percent": 0,
- "alert_js_errors_time": 0,
- "branding_id": 0,
- "contacts": [
- {
- "id": 0,
- "is_team": true,
- "is_user": true,
- "is_operator": true,
- "is_individual_contact": true
}
]
}
}Update an existing RUM monitor
Updates an existing RUM monitor. Only include fields you want to change. Omitted fields retain their current values.
Authorizations:
path Parameters
| id required | integer\d+ Monitor ID |
Request Body schema: application/json
| url | string URL of the website to monitor |
| project_name | string Monitor display name |
| apdex_score | integer Apdex threshold in seconds (1-20) |
| is_alert_traffic_stops | integer Enable traffic stop alerts (0=no, 1=yes) |
| alert_traffic_stops_minutes | integer Minutes without traffic before alerting |
| is_alert_speed_drops | integer Enable speed drop alerts (0=no, 1=yes) |
| alert_speed_drops_percent | integer Speed drop percentage threshold |
| alert_speed_drops_time | integer Minutes of sustained speed drop before alerting |
| is_alert_traffic_drops | integer Enable traffic drop alerts (0=no, 1=yes) |
| alert_traffic_drops_percent | integer Traffic drop percentage threshold |
| alert_traffic_drops_time | integer Minutes of sustained traffic drop before alerting |
| is_alert_js_errors | integer Enable JS error alerts (0=no, 1=yes) |
| alert_js_errors_percent | integer JS error percentage threshold |
| alert_js_errors_time | integer Minutes of sustained JS errors before alerting |
| contacts | Array of integers Contact IDs to notify |
| is_paused | boolean Pause (true) or resume (false) the monitor. When present, only the pause/resume action is performed and other fields are ignored. |
integer or Array of integers Group assignment: a single group ID or an array of group IDs. | |
| branding_id | integer Branding template ID |
Responses
Request samples
- Payload
{- "url": "string",
- "project_name": "string",
- "apdex_score": 0,
- "is_alert_traffic_stops": 0,
- "alert_traffic_stops_minutes": 0,
- "is_alert_speed_drops": 0,
- "alert_speed_drops_percent": 0,
- "alert_speed_drops_time": 0,
- "is_alert_traffic_drops": 0,
- "alert_traffic_drops_percent": 0,
- "alert_traffic_drops_time": 0,
- "is_alert_js_errors": 0,
- "alert_js_errors_percent": 0,
- "alert_js_errors_time": 0,
- "contacts": [
- 0
], - "is_paused": true,
- "groups": 0,
- "branding_id": 0
}Response samples
- 200
- 400
- 401
- 404
{- "data": {
- "id": 0
}
}List incidents for a RUM monitor
Returns a list of incidents for the specified RUM monitor within the given date range. Defaults to the last 30 days. Incidents that overlap the range (including ongoing incidents that started before it) are included, and the result set is paginated with the limit/offset parameters.
Authorizations:
path Parameters
| id required | integer\d+ Monitor ID |
query Parameters
| date_start | integer Start date as Unix timestamp (defaults to 30 days ago) |
| date_end | integer End date as Unix timestamp (defaults to now) |
| limit | integer Maximum number of incidents to return |
| offset | integer Number of incidents to skip |
Responses
Response samples
- 200
- 401
- 404
{- "data": [
- {
- "error_id": 0,
- "impact": { },
- "error_short": "string",
- "error_long": "string",
- "severity": "string",
- "time_started": "string",
- "date_ended": "string",
- "time_elapsed": "string"
}
], - "metadata": {
- "total": 0
}
}Delete a RUM incident
Permanently deletes a specific RUM incident and its associated status page threads. The incident must belong to the monitor in the path; otherwise 404 incident_not_found.
Authorizations:
path Parameters
| id required | integer\d+ Monitor ID |
| incidentId required | integer\d+ Incident ID |
Responses
Response samples
- 200
- 400
- 401
- 404
{- "status": "success"
}Verify tracking script installation
Checks whether the RUM tracking script has been correctly installed on the monitored website by fetching the page and searching for the script hash.
Authorizations:
path Parameters
| id required | integer\d+ Monitor ID |
Responses
Response samples
- 200
- 401
- 404
{- "data": {
- "script_found": true
}
}Get RUM summary analytics
Returns summary analytics for a RUM monitor: average load time, pageviews, JS errors, and user satisfaction breakdown.
Authorizations:
path Parameters
| id required | integer\d+ Monitor ID |
query Parameters
| date_start | string <date> Start date (YYYY-MM-DD), defaults to 30 days ago |
| date_end | string <date> End date (YYYY-MM-DD), defaults to today |
Responses
Response samples
- 200
- 401
- 404
{- "data": {
- "avg_load_time": 0,
- "pageviews": 0,
- "js_errors": 0,
- "satisfaction": {
- "satisfied_pct": 0,
- "tolerating_pct": 0,
- "frustrated_pct": 0
}
}
}Get per-page performance breakdown
Returns per-page performance data including pageviews and average load time for each tracked URL.
Authorizations:
path Parameters
| id required | integer\d+ Monitor ID |
query Parameters
| date_start | string <date> Start date (YYYY-MM-DD), defaults to 30 days ago |
| date_end | string <date> End date (YYYY-MM-DD), defaults to today |
Responses
Response samples
- 200
- 401
- 404
{- "data": [
- {
- "url": "string",
- "pageviews": 0,
- "avg_load_time": 0
}
]
}Get JavaScript error analytics
Returns JavaScript error data including totals, error breakdown by name and by script.
Authorizations:
path Parameters
| id required | integer\d+ Monitor ID |
query Parameters
| date_start | string <date> Start date (YYYY-MM-DD), defaults to 30 days ago |
| date_end | string <date> End date (YYYY-MM-DD), defaults to today |
Responses
Response samples
- 200
- 401
- 404
{- "data": {
- "total": 0,
- "unique": 0,
- "affected_pages": 0,
- "error_rate": 0,
- "by_name": [
- {
- "name": "string",
- "count": 0,
- "pages": 0,
- "rate": 0,
- "last_seen": "string",
- "stack_trace": "string"
}
], - "by_script": [
- {
- "script": "string",
- "count": 0,
- "last_seen": "string"
}
]
}
}Get geographic distribution
Returns visitor distribution by country with load counts and average load times.
Authorizations:
path Parameters
| id required | integer\d+ Monitor ID |
query Parameters
| date_start | string <date> Start date (YYYY-MM-DD), defaults to 30 days ago |
| date_end | string <date> End date (YYYY-MM-DD), defaults to today |
Responses
Response samples
- 200
- 401
- 404
{- "data": [
- {
- "country": "string",
- "country_code": "string",
- "loads": 0,
- "avg_load_time": 0
}
]
}Get browser distribution
Returns visitor distribution by browser with share percentage and load counts.
Authorizations:
path Parameters
| id required | integer\d+ Monitor ID |
query Parameters
| date_start | string <date> Start date (YYYY-MM-DD), defaults to 30 days ago |
| date_end | string <date> End date (YYYY-MM-DD), defaults to today |
Responses
Response samples
- 200
- 401
- 404
{- "data": [
- {
- "name": "string",
- "share_pct": 0,
- "loads": 0
}
]
}Get device type distribution
Returns visitor distribution by device type (Desktop, Mobile, Tablet) with share percentage and load counts.
Authorizations:
path Parameters
| id required | integer\d+ Monitor ID |
query Parameters
| date_start | string <date> Start date (YYYY-MM-DD), defaults to 30 days ago |
| date_end | string <date> End date (YYYY-MM-DD), defaults to today |
Responses
Response samples
- 200
- 401
- 404
{- "data": [
- {
- "type": "string",
- "share_pct": 0,
- "loads": 0
}
]
}Get operating system distribution
Returns visitor distribution by operating system with share percentage and load counts.
Authorizations:
path Parameters
| id required | integer\d+ Monitor ID |
query Parameters
| date_start | string <date> Start date (YYYY-MM-DD), defaults to 30 days ago |
| date_end | string <date> End date (YYYY-MM-DD), defaults to today |
Responses
Response samples
- 200
- 401
- 404
{- "data": [
- {
- "name": "string",
- "share_pct": 0,
- "loads": 0
}
]
}Get tracking JavaScript snippet
Returns the JavaScript tracking code snippet that should be embedded in the monitored website to collect Real User Monitoring data.
Authorizations:
path Parameters
| id required | integer\d+ Monitor ID |
Responses
Response samples
- 200
{- "data": {
- "snippet": "string",
- "script_hash": "string"
}
}List alert logs for a RUM monitor
Returns recent alert/incident logs for the specified RUM monitor. RUM monitors collect client-side data, so logs reflect alert events rather than server-side checks.
Authorizations:
path Parameters
| id required | integer\d+ Monitor ID |
query Parameters
| date_start | integer Start date as Unix timestamp (defaults to 30 days ago) |
| date_end | integer End date as Unix timestamp (defaults to now) |
| limit | integer Maximum number of log entries to return |
| offset | integer Number of log entries to skip |
Responses
Response samples
- 200
{- "data": [
- {
- "id": 0,
- "error_type": "string",
- "error_short": "string",
- "severity": "string",
- "time_started": "string",
- "date_ended": "string",
- "time_elapsed": "string"
}
], - "metadata": {
- "total": 0
}
}Get timeline segments for a RUM monitor
Returns up, down, maintenance and unknown timeline segments for the RUM monitor over the requested date range, suitable for rendering an availability timeline strip.
Authorizations:
path Parameters
| id required | integer\d+ Monitor ID |
query Parameters
| date_start | integer Start date as Unix timestamp (defaults to 7 days ago) |
| date_end | integer End date as Unix timestamp (defaults to now) |
Responses
List all API monitors
Returns every API monitor of the authenticated account, sorted by name ascending, with the derived status decorations the monitor list renders from: is_up (0 while a critical or trouble state is open), is_paused, is_maintenance and steps_count. The list is not paginated — metadata.total is simply the number of rows in data. A monitor-group-scoped operator only ever sees monitors inside their scope groups, so total is what that actor may see rather than the account total. The list row deliberately omits the variables blob and the last_run payload: only GET /{id} returns those, and even there secret values are masked.
Authorizations:
query Parameters
| search | string Example: search=checkout Case-insensitive substring match on the monitor name. LIKE wildcards (% and _) inside the term are escaped and matched literally. |
Responses
Response samples
- 200
- 401
- 403
{- "data": [
- {
- "id": 4821,
- "user": 0,
- "name": "Checkout API",
- "report_interval": 300,
- "last_check": 1756100000,
- "status": 1,
- "incident_id": 0,
- "is_critical": 0,
- "is_trouble": 0,
- "suspended": 0,
- "maintenance": 0,
- "paused": 0,
- "report_down": 0,
- "report_up": 1,
- "confirmations_required": 3,
- "last_error": "",
- "freeze_reason": "",
- "escalation_policy_id": 0,
- "branding_id": 0,
- "notes": "",
- "is_updated": 0,
- "added_date": 0,
- "is_up": 1,
- "is_paused": 0,
- "is_maintenance": 0,
- "steps_count": 4
}
], - "metadata": {
- "total": 12
}
}Create an API monitor
Creates a multi-step API monitor. The plan pool is checked first (403 plan_exceeded), then ownership of the escalation policy, groups, branding template and contacts (400), then the manager validates the name, the variables, the whole step chain and the probe selection before writing the list row and the steps in one transaction — so a 422 leaves nothing behind. Steps are re-numbered 1..N on save, so a client-sent step number is ignored. Omitting contacts entirely seeds every contact on the account; sending an explicitly empty list is refused one gate earlier, as a 400 invalid_contacts rather than a 422. The check cadence can be given as monitoring_interval_slider, monitoring_interval or report_interval (that precedence); whichever is used the result is clamped to the account plan floor and never goes below 60 seconds, because the sweep runs per minute.
Authorizations:
Request Body schema: application/json
| name required | string Monitor name, trimmed. Required, at most 190 characters, and unique within the account. |
required | Array of objects The ordered HTTP chain: at least 1 step and at most 15. Numbers are re-sequenced 1..N on save. |
Array of objects Monitor-level variables available to every step as {{name}}. At most 30. | |
string or Array of integers Probe selection. "all" (the default), null, "" or [] mean every capable probe; an explicit array of location IDs must carry at least 6 distinct entries. | |
| contacts | Array of integers Alert recipients: bare contact IDs, or {id, is_team, is_user, is_operator, is_individual_contact} objects. Every ID must belong to the account. Omit the key (or send null) to auto-seed every contact on the account; an explicitly empty list is rejected with 400 invalid_contacts ("No contacts provided") before anything is written. |
| groups | Array of integers Monitor group IDs (a single bare integer is also accepted); every ID must belong to the account. For a group-scoped editor the list is rewritten so the new monitor always lands inside their scope. |
| branding_id | integer Branding template used for alert emails and status pages. 0 (default) = the default theme; a non-zero ID must belong to the account. |
| escalation_policy_id | integer Escalation policy that governs alert routing. 0 (default) = notify all recipients at once; a non-zero ID must belong to the account. |
| notes | string Free-text operator note (plain text), trimmed and capped at 500 characters. Shown on the incident page and included in alert emails and webhook payloads. |
| confirmations_required | integer How many independent probe confirmations must agree before a failure opens an incident. Clamped to 1-3; defaults to 3. |
| report_up | integer Send the recovery (UP) alert. 0 disables it; any other value — and an absent key — means 1. |
| report_down | integer Delay in SECONDS between the confirmed failure and the DOWN alert. Negative values are floored to 0. Default 0 (alert immediately). Ignored when report_down_slider is present. |
| report_down_slider | integer The same delay expressed in MINUTES (what the editor slider sends); converted to seconds and taking precedence over report_down. |
| monitoring_interval_slider | integer Check cadence as the shared editor slider code (1 = 60s, 5 = 300s, 61 = 2h, 65 = 24h, 71+ = whole days), clamped to the account plan floor. Highest-precedence interval key. |
| monitoring_interval | integer Check cadence in raw seconds, clamped up to the account plan floor. Used when monitoring_interval_slider is absent. |
| report_interval | integer Check cadence in seconds under the name GET returns it as, so a GET → edit → PUT round-trip works. Lowest-precedence interval key; the resolved value is floored to 60 seconds and defaults to 300. |
(string or null) or (integer or null) Canonical ref of the Location Profile to record against this monitor: a built-in's synthetic id ("builtin: |
Responses
Request samples
- Payload
{- "name": "Checkout API",
- "steps": [
- {
- "name": "Create session",
- "method": "POST",
- "headers": [
- [
- "string"
]
], - "body": "{\"user\":\"{{login}}\"}",
- "timeout_s": 10,
- "wait_ms": 0,
- "optional": false,
- "follow_redirects": true,
- "asserts": [
- {
- "type": "status",
- "op": "is",
- "path": "$.data.id",
- "val": "200"
}
], - "extracts": [
- {
- "name": "token",
- "src": "jsonpath",
- "expr": "$.access_token"
}
]
}
], - "variables": [
- {
- "name": "api_key",
- "value": "sk_live_123",
- "secret": false
}
], - "probes": "all",
- "contacts": [
- 0
], - "groups": [
- 0
], - "branding_id": 0,
- "escalation_policy_id": 0,
- "notes": "",
- "confirmations_required": 3,
- "report_up": 1,
- "report_down": 0,
- "report_down_slider": 0,
- "monitoring_interval_slider": 5,
- "monitoring_interval": 300,
- "report_interval": 300,
- "location_profile": "builtin:europe"
}Response samples
- 201
- 400
- 401
- 403
- 422
{- "data": {
- "id": 4821,
- "user": 0,
- "name": "Checkout API",
- "report_interval": 300,
- "last_check": 0,
- "last_pass": 0,
- "status": 0,
- "incident_id": 0,
- "is_critical": 0,
- "is_trouble": 0,
- "suspended": 0,
- "maintenance": 0,
- "paused": 0,
- "report_down": 0,
- "report_up": 1,
- "confirmations_required": 3,
- "last_error": "",
- "freeze_reason": "",
- "escalation_policy_id": 0,
- "branding_id": 0,
- "unique_id": "string",
- "notes": "",
- "is_updated": 0,
- "added_date": 0,
- "is_up": 1,
- "is_paused": 0,
- "is_maintenance": 0,
- "steps": [
- {
- "id": 0,
- "number": 1,
- "name": "string",
- "method": "POST",
- "url": "string",
- "headers": [
- [
- "string"
]
], - "body": "string",
- "timeout_s": 10,
- "wait_ms": 0,
- "optional": true,
- "follow_redirects": true,
- "asserts": [
- {
- "type": "string",
- "op": "string",
- "path": "string",
- "val": "string"
}
], - "extracts": [
- {
- "name": "string",
- "src": "string",
- "expr": "string"
}
]
}
], - "steps_count": 4,
- "variables": [
- {
- "name": "string",
- "value": "string",
- "secret": true,
- "has_value": true
}
], - "last_run": { },
- "probes": "string",
- "locations": [
- 0
], - "contacts": [
- {
- "id": 0,
- "is_team": 0,
- "is_user": 0,
- "is_operator": 0,
- "is_individual_contact": 1
}
], - "groups": [
- 0
]
}
}Get an API monitor
Returns the full editable detail of one monitor: every list-row field plus the decoded step chain, the variable list, the last run blob, the probe/location selection, the alert contacts, the group memberships and the applied Location Profile. This is the round-trip shape — what you read here can be edited and sent straight back to PATCH /{id}. Secrets are write-only: a variable marked secret comes back with value "" and has_value true, and any literal occurrence of a secret value inside a step URL, header or body is rewritten to its {{name}} placeholder before it leaves the API. An out-of-scope monitor is indistinguishable from a non-existent one, so a group-scoped actor cannot probe for foreign IDs.
Authorizations:
path Parameters
| id required | integer\d+ Example: 4821 Monitor ID |
Responses
Response samples
- 200
- 401
- 403
- 404
{- "data": {
- "id": 4821,
- "user": 0,
- "name": "Checkout API",
- "report_interval": 300,
- "last_check": 0,
- "last_pass": 0,
- "status": 1,
- "incident_id": 0,
- "is_critical": 0,
- "is_trouble": 0,
- "suspended": 0,
- "maintenance": 0,
- "paused": 0,
- "report_down": 0,
- "report_up": 1,
- "confirmations_required": 3,
- "last_error": "",
- "freeze_reason": "",
- "escalation_policy_id": 0,
- "branding_id": 0,
- "unique_id": "string",
- "notes": "",
- "is_updated": 0,
- "added_date": 0,
- "is_up": 1,
- "is_paused": 0,
- "is_maintenance": 0,
- "steps": [
- {
- "id": 0,
- "number": 1,
- "name": "Create session",
- "method": "POST",
- "headers": [
- [
- "string"
]
], - "body": "{\"user\":\"{{login}}\"}",
- "timeout_s": 10,
- "wait_ms": 0,
- "optional": true,
- "follow_redirects": true,
- "asserts": [
- {
- "type": "status",
- "op": "is",
- "path": "$.data.id",
- "val": "200"
}
], - "extracts": [
- {
- "name": "token",
- "src": "jsonpath",
- "expr": "$.access_token"
}
]
}
], - "steps_count": 4,
- "variables": [
- {
- "name": "api_key",
- "value": "",
- "secret": true,
- "has_value": true
}
], - "last_run": { },
- "probes": "all",
- "locations": [
- 0
], - "contacts": [
- {
- "id": 0,
- "is_team": 0,
- "is_user": 0,
- "is_operator": 0,
- "is_individual_contact": 1
}
], - "groups": [
- 0
], - "applied_location_profile": "builtin:europe"
}
}Update an API monitor
Alias of PATCH /{id}, kept for client compatibility — the route declares both verbs and they resolve to the same handler, so the request contract, the response and the error set are identical and the semantics are a partial update either way. Omitted keys keep their stored values, sending steps or variables replaces that list wholesale, and a secret variable sent with an empty value keeps the stored secret. Sending is_paused short-circuits everything else and only pauses or resumes the monitor.
Authorizations:
path Parameters
| id required | integer\d+ Example: 4821 Monitor ID |
Request Body schema: application/json
| is_paused | boolean Pause (truthy) or resume (falsy) the monitor. Present with any value other than a literal null this SHORT-CIRCUITS the whole update: no other field in the body is read, no validation runs, and the response is just {data:{id}}. Send it on its own. |
| name | string New monitor name. Same rules as create — non-empty, at most 190 characters, unique within the account excluding this monitor. Absent keeps the stored name. |
Array of objects Full replacement of the step chain: the stored steps are deleted and re-inserted, re-numbered 1..N. Same per-step contract and limits as create (1-15 steps). Absent leaves the stored chain alone — but sending variables WITHOUT steps re-validates the stored chain against the new variable set, so removing a variable that a step still references fails with undefined_variable instead of silently breaking the monitor. Replacing the chain marks the definition changed, which clears the in-flight pull lease and drops any outstanding confirmation round. | |
Array of objects Full replacement of the variable list, at most 30. Masked round-trip: an incoming variable whose value is "" and whose name matches a STORED SECRET keeps the stored value, whatever the incoming secret flag — so an untouched secret can be re-saved, and un-secreted, without retyping it. | |
string or Array of integers Replacement probe selection: "all", or at least 6 distinct location IDs. Absent keeps the stored selection. Changing it marks the definition changed. | |
| contacts | Array of integers Replacement recipient list: bare contact IDs, or {id, is_team, is_user, is_operator, is_individual_contact} objects. Every ID must belong to the account. A null value, the "no_changes" sentinel and an absent key all leave the stored list untouched; an explicitly empty array is NOT a way to clear the recipients — it is rejected with 400 invalid_contacts ("No contacts provided"), because a monitor with no recipients alerts nobody. |
| groups | Array of integers Replacement group membership. An empty array DELETES all memberships; "no_changes" or an absent key leaves them alone. A group-scoped editor's write is confined to their scope groups, and owner-assigned memberships outside that scope are preserved. |
| branding_id | integer Branding template ID; 0 = default theme, a non-zero ID must belong to the account. Absent keeps the stored value. |
| escalation_policy_id | integer Escalation policy ID; 0 = notify all recipients at once, a non-zero ID must belong to the account. Absent keeps the stored value. |
| notes | string Free-text operator note, trimmed and capped at 500 characters. An absent key preserves the stored value; an explicit empty string clears it. |
| confirmations_required | integer Independent probe confirmations before an incident opens, clamped 1-3. Absent keeps the stored value. |
| report_up | integer Send the recovery alert (0 = off, anything else = on). Absent keeps the stored value. |
| report_down | integer DOWN-alert delay in SECONDS, floored at 0. Absent keeps the stored value. |
| report_down_slider | integer DOWN-alert delay in MINUTES (the editor slider); converted to seconds and taking precedence over report_down. |
| monitoring_interval_slider | integer Check cadence as the shared editor slider code, clamped to the account plan floor. Highest-precedence interval key. |
| monitoring_interval | integer Check cadence in raw seconds, clamped to the account plan floor. Used when the slider key is absent. |
| report_interval | integer Check cadence in seconds under the read-side name, so a GET → edit → PUT round-trip is honoured. Lowest precedence; the resolved value is floored to 60 seconds, and with no interval key at all the stored cadence is kept. |
(string or null) or (integer or null) Canonical ref of the applied Location Profile: a built-in's synthetic id ("builtin: |
Responses
Request samples
- Payload
{- "is_paused": true,
- "name": "Checkout API",
- "steps": [
- {
- "name": "Create session",
- "method": "POST",
- "headers": [
- [
- "string"
]
], - "body": "{\"user\":\"{{login}}\"}",
- "timeout_s": 10,
- "wait_ms": 0,
- "optional": false,
- "follow_redirects": true,
- "asserts": [
- {
- "type": "json",
- "op": "equals",
- "path": "$.status",
- "val": "ok"
}
], - "extracts": [
- {
- "name": "token",
- "src": "jsonpath",
- "expr": "$.access_token"
}
]
}
], - "variables": [
- {
- "name": "api_key",
- "value": "",
- "secret": true
}
], - "probes": "all",
- "contacts": [
- 0
], - "groups": [
- 0
], - "branding_id": 0,
- "escalation_policy_id": 0,
- "notes": "",
- "confirmations_required": 3,
- "report_up": 1,
- "report_down": 0,
- "report_down_slider": 0,
- "monitoring_interval_slider": 5,
- "monitoring_interval": 300,
- "report_interval": 300,
- "location_profile": "builtin:europe"
}Response samples
- 200
- 400
- 401
- 403
- 404
- 422
{- "data": {
- "id": 4821,
- "user": 0,
- "name": "Checkout API",
- "report_interval": 300,
- "last_check": 0,
- "last_pass": 0,
- "status": 0,
- "incident_id": 0,
- "is_critical": 0,
- "is_trouble": 0,
- "suspended": 0,
- "maintenance": 0,
- "paused": 0,
- "report_down": 0,
- "report_up": 1,
- "confirmations_required": 3,
- "last_error": "",
- "freeze_reason": "",
- "escalation_policy_id": 0,
- "branding_id": 0,
- "unique_id": "string",
- "notes": "",
- "is_updated": 0,
- "added_date": 0,
- "is_up": 1,
- "is_paused": 0,
- "is_maintenance": 0,
- "steps": [
- {
- "id": 0,
- "number": 1,
- "name": "string",
- "method": "POST",
- "url": "string",
- "headers": [
- [
- "string"
]
], - "body": "string",
- "timeout_s": 10,
- "wait_ms": 0,
- "optional": true,
- "follow_redirects": true,
- "asserts": [
- {
- "type": "string",
- "op": "string",
- "path": "string",
- "val": "string"
}
], - "extracts": [
- {
- "name": "string",
- "src": "string",
- "expr": "string"
}
]
}
], - "steps_count": 4,
- "variables": [
- {
- "name": "string",
- "value": "string",
- "secret": true,
- "has_value": true
}
], - "last_run": { },
- "probes": "string",
- "locations": [
- 0
], - "contacts": [
- {
- "id": 0,
- "is_team": 0,
- "is_user": 0,
- "is_operator": 0,
- "is_individual_contact": 1
}
], - "groups": [
- 0
], - "applied_location_profile": "builtin:europe"
}
}Delete an API monitor
Permanently deletes the monitor and cascades: the step chain, the legacy result rows, the reporting rows, every incident, the maintenance and pause audit rows, the Location Profile binding, the group memberships, the pull-queue and confirmation rows, and the ClickHouse per-run and per-step history. There is no soft delete and no undo — pause the monitor instead if you only want to stop checks. A monitor outside the caller's group scope answers 404 exactly like a non-existent one.
Authorizations:
path Parameters
| id required | integer\d+ Example: 4821 Monitor ID |
Responses
Response samples
- 200
- 401
- 403
- 404
{- "data": {
- "deleted": true
}
}Update an API monitor
True partial update: only the keys you send are written, everything else keeps its stored value. steps and variables are whole-list replacements rather than merges, and a secret variable sent with an empty value keeps the stored secret, so a GET → edit → PATCH round-trip never wipes credentials. Sending is_paused short-circuits the whole update — no other field is read, no validation runs, and the response is just {data:{id}}. Changing steps, variables or probes marks the definition changed, which clears the in-flight pull lease and drops any outstanding confirmation round.
Authorizations:
path Parameters
| id required | integer\d+ Example: 4821 Monitor ID |
Request Body schema: application/json
| is_paused | boolean Pause (truthy) or resume (falsy) the monitor. Present with any value other than a literal null this SHORT-CIRCUITS the whole update: no other field in the body is read, no validation runs, and the response is just {data:{id}}. Send it on its own. |
| name | string New monitor name. Same rules as create — non-empty, at most 190 characters, unique within the account excluding this monitor. Absent keeps the stored name. |
Array of objects Full replacement of the step chain: the stored steps are deleted and re-inserted, re-numbered 1..N. Same per-step contract and limits as create (1-15 steps). Absent leaves the stored chain alone — but sending variables WITHOUT steps re-validates the stored chain against the new variable set, so removing a variable that a step still references fails with undefined_variable instead of silently breaking the monitor. Replacing the chain marks the definition changed, which clears the in-flight pull lease and drops any outstanding confirmation round. | |
Array of objects Full replacement of the variable list, at most 30. Masked round-trip: an incoming variable whose value is "" and whose name matches a STORED SECRET keeps the stored value, whatever the incoming secret flag — so an untouched secret can be re-saved, and un-secreted, without retyping it. | |
string or Array of integers Replacement probe selection: "all", or at least 6 distinct location IDs. Absent keeps the stored selection. Changing it marks the definition changed. | |
| contacts | Array of integers Replacement recipient list: bare contact IDs, or {id, is_team, is_user, is_operator, is_individual_contact} objects. Every ID must belong to the account. A null value, the "no_changes" sentinel and an absent key all leave the stored list untouched; an explicitly empty array is NOT a way to clear the recipients — it is rejected with 400 invalid_contacts ("No contacts provided"), because a monitor with no recipients alerts nobody. |
| groups | Array of integers Replacement group membership. An empty array DELETES all memberships; "no_changes" or an absent key leaves them alone. A group-scoped editor's write is confined to their scope groups, and owner-assigned memberships outside that scope are preserved. |
| branding_id | integer Branding template ID; 0 = default theme, a non-zero ID must belong to the account. Absent keeps the stored value. |
| escalation_policy_id | integer Escalation policy ID; 0 = notify all recipients at once, a non-zero ID must belong to the account. Absent keeps the stored value. |
| notes | string Free-text operator note, trimmed and capped at 500 characters. An absent key preserves the stored value; an explicit empty string clears it. |
| confirmations_required | integer Independent probe confirmations before an incident opens, clamped 1-3. Absent keeps the stored value. |
| report_up | integer Send the recovery alert (0 = off, anything else = on). Absent keeps the stored value. |
| report_down | integer DOWN-alert delay in SECONDS, floored at 0. Absent keeps the stored value. |
| report_down_slider | integer DOWN-alert delay in MINUTES (the editor slider); converted to seconds and taking precedence over report_down. |
| monitoring_interval_slider | integer Check cadence as the shared editor slider code, clamped to the account plan floor. Highest-precedence interval key. |
| monitoring_interval | integer Check cadence in raw seconds, clamped to the account plan floor. Used when the slider key is absent. |
| report_interval | integer Check cadence in seconds under the read-side name, so a GET → edit → PUT round-trip is honoured. Lowest precedence; the resolved value is floored to 60 seconds, and with no interval key at all the stored cadence is kept. |
(string or null) or (integer or null) Canonical ref of the applied Location Profile: a built-in's synthetic id ("builtin: |
Responses
Request samples
- Payload
{- "is_paused": true,
- "name": "Checkout API",
- "steps": [
- {
- "name": "Create session",
- "method": "POST",
- "headers": [
- [
- "string"
]
], - "body": "{\"user\":\"{{login}}\"}",
- "timeout_s": 10,
- "wait_ms": 0,
- "optional": false,
- "follow_redirects": true,
- "asserts": [
- {
- "type": "json",
- "op": "equals",
- "path": "$.status",
- "val": "ok"
}
], - "extracts": [
- {
- "name": "token",
- "src": "jsonpath",
- "expr": "$.access_token"
}
]
}
], - "variables": [
- {
- "name": "api_key",
- "value": "",
- "secret": true
}
], - "probes": "all",
- "contacts": [
- 0
], - "groups": [
- 0
], - "branding_id": 0,
- "escalation_policy_id": 0,
- "notes": "",
- "confirmations_required": 3,
- "report_up": 1,
- "report_down": 0,
- "report_down_slider": 0,
- "monitoring_interval_slider": 5,
- "monitoring_interval": 300,
- "report_interval": 300,
- "location_profile": "builtin:europe"
}Response samples
- 200
- 400
- 401
- 403
- 404
- 422
{- "data": {
- "id": 4821,
- "user": 0,
- "name": "Checkout API",
- "report_interval": 300,
- "last_check": 0,
- "last_pass": 0,
- "status": 0,
- "incident_id": 0,
- "is_critical": 0,
- "is_trouble": 0,
- "suspended": 0,
- "maintenance": 0,
- "paused": 0,
- "report_down": 0,
- "report_up": 1,
- "confirmations_required": 3,
- "last_error": "",
- "freeze_reason": "",
- "escalation_policy_id": 0,
- "branding_id": 0,
- "unique_id": "string",
- "notes": "",
- "is_updated": 0,
- "added_date": 0,
- "is_up": 1,
- "is_paused": 0,
- "is_maintenance": 0,
- "steps": [
- {
- "id": 0,
- "number": 1,
- "name": "string",
- "method": "POST",
- "url": "string",
- "headers": [
- [
- "string"
]
], - "body": "string",
- "timeout_s": 10,
- "wait_ms": 0,
- "optional": true,
- "follow_redirects": true,
- "asserts": [
- {
- "type": "string",
- "op": "string",
- "path": "string",
- "val": "string"
}
], - "extracts": [
- {
- "name": "string",
- "src": "string",
- "expr": "string"
}
]
}
], - "steps_count": 4,
- "variables": [
- {
- "name": "string",
- "value": "string",
- "secret": true,
- "has_value": true
}
], - "last_run": { },
- "probes": "string",
- "locations": [
- 0
], - "contacts": [
- {
- "id": 0,
- "is_team": 0,
- "is_user": 0,
- "is_operator": 0,
- "is_individual_contact": 1
}
], - "groups": [
- 0
], - "applied_location_profile": "builtin:europe"
}
}Incidents for an API monitor
Newest-first incident history for one monitor, formatted into the shared Incidents-card shape used by every monitor family: pre-formatted display strings rather than raw timestamps. The window is a half-open overlap — an incident is in range when it started before date_end AND was still open after date_start — so a long outage spanning the window is included rather than dropped. metadata.total is the real count for the window and ignores limit/offset, so it can be used to drive paging.
Authorizations:
path Parameters
| id required | integer\d+ Example: 4821 Monitor ID |
query Parameters
| limit | integer Default: 50 Example: limit=50 Page size, clamped to 1-200 |
| offset | integer Default: 0 Example: offset=0 Row offset into the newest-first list; negatives are floored to 0 |
| date_start | integer Default: 0 Window lower bound as a Unix timestamp; 0 (the default) means unbounded |
| date_end | integer Default: 0 Window upper bound as a Unix timestamp; 0 (the default) means unbounded |
Responses
Response samples
- 200
- 401
- 403
- 404
{- "data": [
- {
- "error_id": 90211,
- "impact": { },
- "error_display": "Step 2 assertion failed",
- "time_started": "August 15, 2026 14:30",
- "time_ended": "",
- "date_ended": "18 minutes",
- "time_elapsed": "2 hours ago"
}
], - "metadata": {
- "total": 7,
- "monitor_id": 4821
}
}Per-run check log
Newest-first page of the append-only run log: one row per run of the step chain with its timestamp, total duration, pass/fail, the failing step + error, the pass count and the probe. Kept per plan retention, like every other monitor family's check history. The row's time is the run's identity — pass it to GET /{id}/logs/{time} for the per-step breakdown. The window is half-open: date_start is inclusive, date_end exclusive. The log lives in ClickHouse and degrades to an empty page with total 0 during a ClickHouse outage rather than returning an error, so an unexpectedly empty page is not proof that nothing ran.
Authorizations:
path Parameters
| id required | integer\d+ Example: 4821 Monitor ID |
query Parameters
| limit | integer Default: 20 Example: limit=20 Page size, clamped to 1-100 |
| offset | integer Default: 0 Example: offset=0 Row offset; negatives are floored to 0 |
| date_start | integer Default: 0 Inclusive lower bound on the run time as a Unix timestamp (time >= date_start); 0 = unbounded |
| date_end | integer Default: 0 Exclusive upper bound on the run time as a Unix timestamp (time < date_end); 0 = unbounded |
Responses
Response samples
- 200
- 401
- 403
- 404
{- "data": [
- {
- "time": 1756100400,
- "duration": 842,
- "status": 1,
- "server_location": 12,
- "error_step": 0,
- "error_code": "",
- "error_text": "",
- "steps_total": 4,
- "steps_passed": 4
}
], - "metadata": {
- "total": 288,
- "monitor_id": 4821
}
}One run in full
The summary of a single run plus every step of the chain as it was recorded: pass/fail/optional-failed/not-run, HTTP status, total and phase timings (DNS, connect, TLS, wait, receive), response size, the assertion tally and any error. time is the exact Unix second returned by GET /{id}/logs — there is no nearest-match, an unmatched timestamp is a 404. Step names, methods and paths come from the monitor's CURRENT definition, because the per-step storage keeps only the step number: a step renamed since the run shows today's name, and a step deleted since is labelled by its number alone.
Authorizations:
path Parameters
| id required | integer\d+ Example: 4821 Monitor ID |
| time required | integer\d+ Example: 1756100400 Unix timestamp of the run, exactly as returned in the log row's time field |
Responses
Response samples
- 200
- 401
- 403
- 404
{- "data": {
- "time": 1756100400,
- "duration": 842,
- "status": 1,
- "server_location": 12,
- "location": "London",
- "flag": "gb",
- "error_step": 0,
- "error_code": "",
- "error_text": "",
- "steps_total": 4,
- "steps_passed": 4,
- "steps": [
- {
- "number": 2,
- "name": "Fetch cart",
- "method": "GET",
- "status": 1,
- "http_code": 200,
- "duration": 121,
- "phases": {
- "dns": 4,
- "connect": 11,
- "tls": 32,
- "wait": 68,
- "receive": 6
}, - "resp_size": 1420,
- "assert_total": 3,
- "assert_failed": 0,
- "error_code": "",
- "error_text": ""
}
]
}, - "metadata": {
- "monitor_id": 4821
}
}Delete an API monitor incident
Deletes one CLOSED incident record of this monitor. The monitor's currently open incident is refused with 409 incident_open, because deleting it would leave the monitor pointing at a row that no longer exists — resolve or wait for the recovery first. The incident must belong to this monitor: an ID from another monitor answers 404 incident_not_found rather than deleting anything.
Authorizations:
path Parameters
| id required | integer\d+ Example: 4821 Monitor ID |
| incidentId required | integer\d+ Example: 90211 Incident ID, as returned in error_id by GET /{id}/incidents |
Responses
Response samples
- 200
- 401
- 403
- 404
- 409
{- "data": {
- "deleted": true
}
}Detail-page summary
One payload for the detail page: the decorated monitor and its steps, availability derived from incident downtime overlapping the window, the in-window incidents, run counts, per-step average/p95/share/delta, the bucketed stacked series and the last run. Pass an explicit from/to window in Unix seconds, or a rolling days window — days is only consulted when neither bound is given. The window is capped at 92 days and never ends later than now, and the series bucket width adapts (hourly up to 7 days, then 3h/6h/12h, daily beyond 30 days) so the series stays at 180 buckets or fewer. Availability comes from MariaDB incident rows, while the run statistics come from ClickHouse: during a ClickHouse outage runs_total, runs_failed, avg_run_ms, p95_run_ms, per_step and hourly degrade to 0 and empty arrays instead of erroring, so a zeroed run section next to a healthy availability figure means "stats unavailable", not "nothing ran".
Authorizations:
path Parameters
| id required | integer\d+ Example: 4821 Monitor ID |
query Parameters
| from | integer Example: from=1755500000 Explicit window start as a Unix timestamp. Consulted only when from or to is greater than 0, and replaced by (to − 7 days) when it is not strictly earlier than the resolved to. |
| to | integer Example: to=1756100000 Explicit window end as a Unix timestamp, clamped to no later than now |
| days | integer Default: 7 Example: days=7 Rolling-window length in days, used ONLY when neither from nor to is greater than 0. Minimum 1; the resulting window is still capped at 92 days. |
Responses
Response samples
- 200
- 401
- 403
- 404
{- "data": {
- "now": 1756100000,
- "window_start": 1755495200,
- "window_end": 1756100000,
- "measured_start": 1755495200,
- "measured_seconds": 604800,
- "maintenance_seconds": 0,
- "days": 7,
- "bucket_seconds": 3600,
- "monitor": { },
- "steps": [
- { }
], - "availability": 99.87,
- "down_seconds": 780,
- "incidents": [
- {
- "impact": { },
- "id": 0,
- "site_id": 0,
- "time_started": 0,
- "time_ended": 0,
- "down_report_sent": 0,
- "down_report_code": 0,
- "up_report_sent": 0,
- "up_report_code": 0,
- "error_code": "string",
- "error_type": "string",
- "error_step": 0,
- "reason": "string",
- "reason_full": "string",
- "is_critical": 0,
- "is_trouble": 0,
- "servers": "string"
}
], - "runs_total": 2016,
- "runs_failed": 3,
- "avg_run_ms": 812,
- "p95_run_ms": 1340,
- "per_step": [
- {
- "number": 2,
- "name": "Fetch cart",
- "method": "GET",
- "avg_ms": 121.4,
- "p95_ms": 210,
- "share": 0.1832,
- "delta_ms": -4.2,
- "runs": 2016
}
], - "hourly": [
- {
- "time": 1756096800,
- "steps": [
- 0
]
}
], - "last_run": { }
}
}Run a builder test (unsaved editor state)
Runs an UNSAVED step chain synchronously on a monitoring probe and returns the full per-step result: timings, HTTP codes, redacted response headers and body snippets, and the outcome of every assertion and extraction. Nothing is persisted and no incident can be opened by it. The call blocks for as long as the chain takes, up to the executor budget. Two gates apply before anything runs: the account plan must include API monitoring, and the account may run at most 10 tests per sliding minute. There is no stored monitor to merge secrets from, so a secret variable must carry its real value here; every secret with a non-empty value is passed to the probe as a redaction target so it never appears in an echoed URL, header, body snippet or error text.
Authorizations:
Request Body schema: application/json
required | Array of objects The chain to execute, in exactly the wire shape create and update accept. Validated with the same rules and the same error codes as a save — 1 to 15 steps, at most 20 headers, 15 asserts and 10 extracts per step, a body of at most 65536 bytes, a timeout of 1-60 seconds, a wait of 0-30000 ms, and {{placeholder}} names that are defined before the step that uses them — but with no name check and no plan pool check. A non-array value is treated as an empty chain and fails with steps_required. |
Array of objects Variables available to every step as {{name}}, at most 30, with identifier-shaped names and values of at most 4096 CR/LF-free characters. Used verbatim: a secret must carry its real value because there is no stored monitor to fill it from. | |
| probe | integer Force a single probe location ID to run the test. Absent or falsy lets the transport walk its default candidate list, i.e. the whole capable fleet. |
| run_to | integer "Run to step N" — stop the chain after that step number; later steps come back with ran false. Clamped to 0-15; 0 (the default) runs the whole chain. |
Responses
Request samples
- Payload
{- "steps": [
- {
- "name": "Create session",
- "method": "POST",
- "headers": [
- [
- "string"
]
], - "body": "{\"user\":\"{{login}}\"}",
- "timeout_s": 10,
- "wait_ms": 0,
- "optional": false,
- "follow_redirects": true,
- "asserts": [
- {
- "type": "status",
- "op": "is",
- "path": "$.data.id",
- "val": "200"
}
], - "extracts": [
- {
- "name": "token",
- "src": "jsonpath",
- "expr": "$.access_token"
}
]
}
], - "variables": [
- {
- "name": "api_key",
- "value": "sk_live_123",
- "secret": true
}
], - "probe": 12,
- "run_to": 0
}Response samples
- 200
- 401
- 403
- 422
- 429
- 502
{- "data": {
- "ok": true,
- "probe": 12,
- "total_ms": 842,
- "failed_step": 0,
- "steps": [
- {
- "number": 1,
- "name": "Create session",
- "method": "POST",
- "ran": true,
- "passed": true,
- "http_code": 201,
- "total_ms": 121,
- "timing": {
- "dns": 4,
- "connect": 11,
- "tls": 32,
- "wait": 68,
- "receive": 6
}, - "resp_size": 1420,
- "resp_headers": "",
- "body_snippet": "",
- "asserts": [
- {
- "ok": true,
- "got": "201"
}
], - "extracts": [
- {
- "name": "token",
- "ok": true,
- "value": "eyJhbGciOi…"
}
], - "error_code": "",
- "error_text": ""
}
]
}
}Run a builder test against a saved monitor
The same synchronous builder test as POST /test, but against a SAVED monitor: masked secrets are filled in from storage, the monitor's own probe selection supplies the candidate list, and the result is persisted as the monitor's last run, marked as builder-sourced so the receiver never reasons an incident from a hand-triggered test. It does NOT advance last_check and never opens or closes an incident. The steps you send are executed as sent — the stored chain is not a fallback, so an absent or empty steps array fails with steps_required. The plan gate and the 10-per-minute rate limit are both applied BEFORE the ownership lookup, so a request for a foreign monitor ID still burns a rate-limit token.
Authorizations:
path Parameters
| id required | integer\d+ Example: 4821 Monitor ID |
Request Body schema: application/json
required | Array of objects The chain to execute, in exactly the wire shape create and update accept. Validated with the same rules and the same error codes as a save — 1 to 15 steps, at most 20 headers, 15 asserts and 10 extracts per step, a body of at most 65536 bytes, a timeout of 1-60 seconds, a wait of 0-30000 ms, and {{placeholder}} names that are defined before the step that uses them — but with no name check and no plan pool check. A non-array value is treated as an empty chain and fails with steps_required. |
Array of objects Variables available to every step as {{name}}, at most 30. Validated first, then merged against the monitor's STORED variables: an incoming variable whose value is "" and whose stored twin is a secret takes the stored secret value, so the editor can re-run a test without retyping credentials, while a non-empty incoming value always wins. Every secret with a resolved non-empty value is passed to the probe as a redaction target. | |
| probe | integer Force a single probe location ID. Absent, the candidate list is the monitor's own saved locations; when that is empty too (an "all probes" selection) the transport's default candidate list is used. |
| run_to | integer Stop the chain after this step number; clamped to 0-15, 0 (the default) runs the whole chain. |
Responses
Request samples
- Payload
{- "steps": [
- {
- "name": "Create session",
- "method": "POST",
- "headers": [
- [
- "string"
]
], - "body": "{\"user\":\"{{login}}\"}",
- "timeout_s": 10,
- "wait_ms": 0,
- "optional": false,
- "follow_redirects": true,
- "asserts": [
- {
- "type": "status",
- "op": "is",
- "path": "$.data.id",
- "val": "200"
}
], - "extracts": [
- {
- "name": "token",
- "src": "jsonpath",
- "expr": "$.access_token"
}
]
}
], - "variables": [
- {
- "name": "api_key",
- "value": "",
- "secret": true
}
], - "probe": 12,
- "run_to": 0
}Response samples
- 200
- 401
- 403
- 404
- 422
- 429
- 502
{- "data": {
- "ok": true,
- "probe": 12,
- "total_ms": 842,
- "failed_step": 0,
- "steps": [
- {
- "number": 1,
- "name": "Create session",
- "method": "POST",
- "ran": true,
- "passed": true,
- "http_code": 201,
- "total_ms": 121,
- "timing": {
- "dns": 4,
- "connect": 11,
- "tls": 32,
- "wait": 68,
- "receive": 6
}, - "resp_size": 1420,
- "resp_headers": "",
- "body_snippet": "",
- "asserts": [
- {
- "ok": true,
- "got": "201"
}
], - "extracts": [
- {
- "name": "token",
- "ok": true,
- "value": "eyJhbGciOi…"
}
], - "error_code": "",
- "error_text": ""
}
]
}
}List all DNS monitors
Returns every DNS monitor on the authenticated account, ordered by name ascending. There is no pagination — metadata.total is simply the number of rows in data. Each row is decorated: the JSON columns are decoded, the derived flags (is_up, is_paused, snooze_active, records_watched, locations) are added, and the sweep-engine internals (probes, vantage cursor, pull lease, last transcript, nameserver statistics, baseline) are stripped — fetch one monitor's summary for those. A scoped operator sees only the monitors in their monitor groups.
Authorizations:
query Parameters
| search | string Example: search=example.com Case-insensitive substring filter matched against BOTH the monitor name and the zone. LIKE wildcards (%, _, ) in the term are escaped, so they match literally. Omit for no filter. |
Responses
Response samples
- 200
- 401
- 403
{- "data": [
- {
- "id": 4211,
- "user": 1,
- "name": "example.com zone",
- "zone": "example.com",
- "watch_names": [
- "www"
], - "group_settings": {
- "addresses": {
- "enabled": true,
- "severity": "critical"
}, - "mail": {
- "enabled": true,
- "severity": "critical"
}, - "ns": {
- "enabled": true,
- "severity": "critical"
}, - "policy": {
- "enabled": true,
- "severity": "trouble"
}
}, - "dynamic_names": [
- "www|A"
], - "verify_sweeps": 3,
- "confirmations_required": 3,
- "serial_drift_seconds": 1800,
- "snooze_until": 0,
- "sweep_interval": 300,
- "nameservers": [
- {
- "host": "ns1.example.com",
- "ip": "203.0.113.10"
}
], - "serial_drift_since": 0,
- "delegation_checked_at": 1756100000,
- "active_changes": 0,
- "health_issues": 0,
- "check_errors": 0,
- "freeze_reason": "",
- "freeze_at": 0,
- "status": 1,
- "last_error": 0,
- "last_check": 1756100300,
- "next_check": 1756100600,
- "incident_id": 0,
- "is_critical": 0,
- "is_trouble": 0,
- "suspended": 0,
- "maintenance": 0,
- "paused": 0,
- "is_updated": 0,
- "report_down": 0,
- "report_up": 1,
- "escalation_policy_id": 0,
- "branding_id": 0,
- "notes": "",
- "added_date": 1756000000,
- "is_up": 1,
- "is_paused": 0,
- "is_maintenance": 0,
- "snooze_active": false,
- "records_watched": 11,
- "locations": [
- 12
]
}
], - "metadata": {
- "total": 12
}
}Create a DNS monitor
Creates a DNS monitor for one zone and queues its baseline sweep for the next per-minute cron tick, so the first results land within a minute. The sweep interval is fixed at 300 seconds and is not configurable. dynamic_names is ignored here — a new monitor always starts with an empty dynamic list, and records are marked dynamic later from a change episode. Creation is refused with 403 plan_exceeded once the account is at its DNS pool allowance.
Authorizations:
Request Body schema: application/json
| name required | string Monitor display name. Trimmed; 1-255 characters, otherwise the request is refused with |
| zone required | string The DNS zone to watch, as a naked domain. It is normalized before storage: lowercased, with any scheme, path, query, port, leading "www." and trailing dot stripped, and IDN converted to punycode. It must then match a hostname of at most 253 characters with a 2-63 letter TLD; anything that parses as an IPv4 or IPv6 literal is refused with |
| watch_names | Array of strings Relative record names swept for A, AAAA and CNAME. Entries are lowercased, the trailing dot is trimmed and duplicates are removed. A name ending in the zone is reduced to its relative form: |
object Per record-group alerting. Any group you omit is filled in with its default (enabled, and severity critical for addresses / mail / ns, trouble for policy), and an unrecognised severity falls back to that default — this field can never fail validation. A disabled group still records its changes, but at | |
| verify_sweeps | integer Enum: 1 3 5 Consecutive clean sweeps required before a change episode auto-closes as restored. Must be 1, 3 or 5 ( |
| serial_drift_seconds | integer Enum: 1800 7200 21600 How long SOA serials may disagree between the zone's authoritative nameservers before a serial-drift health episode opens, in seconds. Must be 1800 (30 min), 7200 (2 h) or 21600 (6 h) — |
| confirmations_required | integer Enum: 1 2 3 Outage-confirmation depth for the pull model: how many vantages must independently observe the same finding before it is acted on. Must be 1, 2 or 3 ( |
| snooze | integer Enum: 0 14400 86400 604800 A snooze DURATION in seconds, not a timestamp — the monitor stores |
(string or null) or (Array of integers or null) Vantage selection. The literal string "all" (and an absent key, null or an empty array) sweeps from the whole DNS-capable fleet. An explicit list of checkpoint ids must name at least 6 DNS-capable vantages, otherwise the request is refused with | |
| report_up | integer Whether recovery ("restored") notifications are sent. Any non-zero value stores 1. Defaults to 1 on create. |
| escalation_policy_id | integer Escalation policy that routes this monitor's alerts. 0 (the default) alerts every recipient at once; a value above 0 must reference a policy owned by the account or the request is refused with |
| branding_id | integer Branding template applied to this monitor's alert emails and reports. 0 (the default) uses the default theme; non-numeric junk, or an id not owned by the account, is refused with |
| notes | string Free-text operator note stored on the monitor. Trimmed and truncated to 500 characters; a non-string value stores an empty string. Written only when the key is present, so an omitted note is never clobbered. |
(Array of integers or objects or null) or (string or null) Alert recipients: bare contact ids, or objects naming what each id refers to. Every id must belong to the account. The literal string "all" selects every account contact and "no_changes" leaves the stored bindings alone. On create, omitting the key (or sending null) seeds every account contact; sending the key with an empty value is refused, because a monitor that can alert nobody is not worth running. | |
Array of integers or integer Monitor-group ids this monitor belongs to; a single bare integer is accepted too. Every id must belong to the account. A scoped operator's submitted list is rewritten so the monitor always stays in at least one of their scope groups — it can never be created or edited into invisibility — while owner-assigned memberships outside that scope are preserved. | |
(string or null) or (integer or null) Records WHICH saved Location Profile the vantage selection came from, so the editor's Locations tab can show the applied profile. Either a built-in synthetic id ("builtin: |
Responses
Request samples
- Payload
{- "name": "example.com zone",
- "zone": "example.com",
- "watch_names": [
- "www"
], - "group_settings": {
- "addresses": {
- "enabled": true,
- "severity": "critical"
}, - "mail": {
- "enabled": true,
- "severity": "critical"
}, - "ns": {
- "enabled": true,
- "severity": "critical"
}, - "policy": {
- "enabled": true,
- "severity": "trouble"
}
}, - "verify_sweeps": 3,
- "serial_drift_seconds": 1800,
- "confirmations_required": 3,
- "snooze": 0,
- "probes": "all",
- "report_up": 1,
- "escalation_policy_id": 0,
- "branding_id": 0,
- "notes": "Zone is managed by the platform team.",
- "contacts": [
- 42
], - "groups": [
- 7
], - "location_profile": "builtin:europe"
}Response samples
- 201
- 400
- 401
- 403
- 422
- 500
{- "data": {
- "id": 4211,
- "user": 1,
- "name": "example.com zone",
- "zone": "example.com",
- "watch_names": [
- "www"
], - "group_settings": {
- "addresses": {
- "enabled": true,
- "severity": "critical"
}, - "mail": {
- "enabled": true,
- "severity": "critical"
}, - "ns": {
- "enabled": true,
- "severity": "critical"
}, - "policy": {
- "enabled": true,
- "severity": "trouble"
}
}, - "dynamic_names": [
- "www|A"
], - "verify_sweeps": 3,
- "confirmations_required": 3,
- "serial_drift_seconds": 1800,
- "snooze_until": 0,
- "sweep_interval": 300,
- "nameservers": [
- {
- "host": "ns1.example.com",
- "ip": "203.0.113.10"
}
], - "serial_drift_since": 0,
- "delegation_checked_at": 1756100000,
- "active_changes": 0,
- "health_issues": 0,
- "check_errors": 0,
- "freeze_reason": "",
- "freeze_at": 0,
- "status": 1,
- "last_error": 0,
- "last_check": 1756100300,
- "next_check": 1756100600,
- "incident_id": 0,
- "is_critical": 0,
- "is_trouble": 0,
- "suspended": 0,
- "maintenance": 0,
- "paused": 0,
- "is_updated": 0,
- "report_down": 0,
- "report_up": 1,
- "escalation_policy_id": 0,
- "branding_id": 0,
- "notes": "",
- "added_date": 1756000000,
- "is_up": 1,
- "is_paused": 0,
- "is_maintenance": 0,
- "snooze_active": false,
- "records_watched": 11,
- "locations": [
- 12
], - "contacts": [
- {
- "id": 42,
- "is_team": 0,
- "is_user": 0,
- "is_operator": 0,
- "is_individual_contact": 1
}
], - "groups": [
- 7
]
}
}Get a DNS monitor
Returns one DNS monitor with its alert recipients, monitor-group memberships and the Location Profile its vantage selection was applied from — everything the editor needs to prefill. This is the configuration read; for the baseline, episodes, watch matrix and last sweep, use GET /api/v2/dns/{id}/summary instead. A monitor outside a scoped operator's groups answers exactly like a missing one.
Authorizations:
path Parameters
| id required | integer\d+ Example: 4211 DNS monitor id. |
Responses
Response samples
- 200
- 401
- 403
- 404
{- "data": {
- "id": 4211,
- "user": 1,
- "name": "example.com zone",
- "zone": "example.com",
- "watch_names": [
- "www"
], - "group_settings": {
- "addresses": {
- "enabled": true,
- "severity": "critical"
}, - "mail": {
- "enabled": true,
- "severity": "critical"
}, - "ns": {
- "enabled": true,
- "severity": "critical"
}, - "policy": {
- "enabled": true,
- "severity": "trouble"
}
}, - "dynamic_names": [
- "www|A"
], - "verify_sweeps": 3,
- "confirmations_required": 3,
- "serial_drift_seconds": 1800,
- "snooze_until": 0,
- "sweep_interval": 300,
- "nameservers": [
- {
- "host": "ns1.example.com",
- "ip": "203.0.113.10"
}
], - "serial_drift_since": 0,
- "delegation_checked_at": 1756100000,
- "active_changes": 0,
- "health_issues": 0,
- "check_errors": 0,
- "freeze_reason": "",
- "freeze_at": 0,
- "status": 1,
- "last_error": 0,
- "last_check": 1756100300,
- "next_check": 1756100600,
- "incident_id": 0,
- "is_critical": 0,
- "is_trouble": 0,
- "suspended": 0,
- "maintenance": 0,
- "paused": 0,
- "is_updated": 0,
- "report_down": 0,
- "report_up": 1,
- "escalation_policy_id": 0,
- "branding_id": 0,
- "notes": "",
- "added_date": 1756000000,
- "is_up": 1,
- "is_paused": 0,
- "is_maintenance": 0,
- "snooze_active": false,
- "records_watched": 11,
- "locations": [
- 12
], - "contacts": [
- {
- "id": 42,
- "is_team": 0,
- "is_user": 0,
- "is_operator": 0,
- "is_individual_contact": 1
}
], - "groups": [
- 7
], - "applied_location_profile": "builtin:europe"
}
}Replace a DNS monitor (alias of PATCH)
An alias of PATCH /api/v2/dns/{id}, kept for client compatibility: the route declares both methods and they resolve to the same handler, so PUT here is NOT a full replacement — the semantics are partial exactly as for PATCH. Partial update: every omitted field keeps its stored value. is_paused may travel alone or alongside a configuration edit, and the pause is applied only after the whole request validates, so a 422 means nothing was written. A watch-config change (zone, watch_names, group_settings, dynamic_names) pulls the next sweep forward to within a minute and invalidates any in-flight pull lease; a ZONE change additionally wipes the baseline, discovered nameservers, last transcript, nameserver statistics, serial-drift timer and delegation timestamp so the next sweep re-baselines from scratch. Config edits also reconcile open episodes: episodes whose watch key is no longer watched (or all of them, on a zone change) close silently, survivors take the NEW group severity, and the incident and counters are recomputed. Two narrower response shapes exist: sending only is_paused answers with the bare {id}, and so does a write whose read-back came back empty.
Authorizations:
path Parameters
| id required | integer\d+ Example: 4211 DNS monitor id. |
Request Body schema: application/json
| name | string Monitor display name. Trimmed; 1-255 characters, otherwise the request is refused with |
| zone | string The DNS zone to watch, as a naked domain. It is normalized before storage: lowercased, with any scheme, path, query, port, leading "www." and trailing dot stripped, and IDN converted to punycode. It must then match a hostname of at most 253 characters with a 2-63 letter TLD; anything that parses as an IPv4 or IPv6 literal is refused with |
| watch_names | Array of strings Relative record names swept for A, AAAA and CNAME. Entries are lowercased, the trailing dot is trimmed and duplicates are removed. A name ending in the zone is reduced to its relative form: |
object Per record-group alerting. Any group you omit is filled in with its default (enabled, and severity critical for addresses / mail / ns, trouble for policy), and an unrecognised severity falls back to that default — this field can never fail validation. A disabled group still records its changes, but at | |
| dynamic_names | Array of strings Watch keys ("name|TYPE") whose VALUES are allowed to rotate: presence and record type are still compared, the answer itself is not. Entries that are not in the current watch matrix are dropped. Normally managed through POST /api/v2/dns/{id}/changes/{changeId}/dynamic rather than here, and omitting the key preserves the stored list. Ignored entirely on create, where the list always starts empty. |
| verify_sweeps | integer Enum: 1 3 5 Consecutive clean sweeps required before a change episode auto-closes as restored. Must be 1, 3 or 5 ( |
| serial_drift_seconds | integer Enum: 1800 7200 21600 How long SOA serials may disagree between the zone's authoritative nameservers before a serial-drift health episode opens, in seconds. Must be 1800 (30 min), 7200 (2 h) or 21600 (6 h) — |
| confirmations_required | integer Enum: 1 2 3 Outage-confirmation depth for the pull model: how many vantages must independently observe the same finding before it is acted on. Must be 1, 2 or 3 ( |
| snooze | integer Enum: 0 14400 86400 604800 A snooze DURATION in seconds, not a timestamp — the monitor stores |
(string or null) or (Array of integers or null) Vantage selection. The literal string "all" (and an absent key, null or an empty array) sweeps from the whole DNS-capable fleet. An explicit list of checkpoint ids must name at least 6 DNS-capable vantages, otherwise the request is refused with | |
| report_up | integer Whether recovery ("restored") notifications are sent. Any non-zero value stores 1. Omit to keep the stored value. |
| escalation_policy_id | integer Escalation policy that routes this monitor's alerts. 0 (the default) alerts every recipient at once; a value above 0 must reference a policy owned by the account or the request is refused with |
| branding_id | integer Branding template applied to this monitor's alert emails and reports. 0 (the default) uses the default theme; non-numeric junk, or an id not owned by the account, is refused with |
| notes | string Free-text operator note stored on the monitor. Trimmed and truncated to 500 characters; a non-string value stores an empty string. Written only when the key is present, so an omitted note is never clobbered. |
(Array of integers or objects or null) or (string or null) Alert recipients: bare contact ids, or objects naming what each id refers to. Every id must belong to the account. The literal string "all" selects every account contact and "no_changes" leaves the stored bindings alone. Omitting the key, or sending null or "no_changes", keeps the stored list and skips the ownership check entirely. An empty array reaches the guard and is refused with | |
Array of integers or integer Monitor-group ids this monitor belongs to; a single bare integer is accepted too. Every id must belong to the account. A scoped operator's submitted list is rewritten so the monitor always stays in at least one of their scope groups — it can never be created or edited into invisibility — while owner-assigned memberships outside that scope are preserved. | |
(boolean or null) or (integer or null) Pause (truthy) or resume (falsy, including an explicit null) the monitor. It may travel alone or alongside a configuration edit; either way the pause is applied only after the whole request has validated, so a 422 means nothing was written. Sent on its own the response is the bare {id}. | |
(string or null) or (integer or null) Records WHICH saved Location Profile the vantage selection came from, so the editor's Locations tab can show the applied profile. Either a built-in synthetic id ("builtin: |
Responses
Request samples
- Payload
{- "name": "example.com zone",
- "zone": "example.com",
- "watch_names": [
- "www"
], - "group_settings": {
- "addresses": {
- "enabled": true,
- "severity": "critical"
}, - "mail": {
- "enabled": true,
- "severity": "critical"
}, - "ns": {
- "enabled": true,
- "severity": "critical"
}, - "policy": {
- "enabled": true,
- "severity": "trouble"
}
}, - "dynamic_names": [
- "www|A"
], - "verify_sweeps": 3,
- "serial_drift_seconds": 1800,
- "confirmations_required": 3,
- "snooze": 0,
- "probes": "all",
- "report_up": 1,
- "escalation_policy_id": 0,
- "branding_id": 0,
- "notes": "Zone is managed by the platform team.",
- "contacts": [
- 42
], - "groups": [
- 7
], - "is_paused": true,
- "location_profile": "builtin:europe"
}Response samples
- 200
- 400
- 401
- 403
- 404
- 422
{- "data": {
- "id": 4211,
- "user": 1,
- "name": "example.com zone",
- "zone": "example.com",
- "watch_names": [
- "www"
], - "group_settings": {
- "addresses": {
- "enabled": true,
- "severity": "critical"
}, - "mail": {
- "enabled": true,
- "severity": "critical"
}, - "ns": {
- "enabled": true,
- "severity": "critical"
}, - "policy": {
- "enabled": true,
- "severity": "trouble"
}
}, - "dynamic_names": [
- "www|A"
], - "verify_sweeps": 3,
- "confirmations_required": 3,
- "serial_drift_seconds": 1800,
- "snooze_until": 0,
- "sweep_interval": 300,
- "nameservers": [
- {
- "host": "ns1.example.com",
- "ip": "203.0.113.10"
}
], - "serial_drift_since": 0,
- "delegation_checked_at": 1756100000,
- "active_changes": 0,
- "health_issues": 0,
- "check_errors": 0,
- "freeze_reason": "",
- "freeze_at": 0,
- "status": 1,
- "last_error": 0,
- "last_check": 1756100300,
- "next_check": 1756100600,
- "incident_id": 0,
- "is_critical": 0,
- "is_trouble": 0,
- "suspended": 0,
- "maintenance": 0,
- "paused": 0,
- "is_updated": 0,
- "report_down": 0,
- "report_up": 1,
- "escalation_policy_id": 0,
- "branding_id": 0,
- "notes": "",
- "added_date": 1756000000,
- "is_up": 1,
- "is_paused": 0,
- "is_maintenance": 0,
- "snooze_active": false,
- "records_watched": 11,
- "locations": [
- 12
], - "contacts": [
- {
- "id": 42,
- "is_team": 0,
- "is_user": 0,
- "is_operator": 0,
- "is_individual_contact": 1
}
], - "groups": [
- 7
], - "applied_location_profile": "builtin:europe"
}
}Delete a DNS monitor
Permanently deletes the monitor and cascades synchronously — change episodes, recipient bindings, incidents, maintenance windows and pauses, group memberships, the pending pull-queue rows, and the ClickHouse sweep history all go with it. An account-activity entry is recorded. There is no undo and no soft-delete tombstone: nothing here is recoverable afterwards.
Authorizations:
path Parameters
| id required | integer\d+ Example: 4211 DNS monitor id. |
Responses
Response samples
- 200
- 401
- 403
- 404
{- "data": {
- "deleted": true
}
}Update a DNS monitor
Partial update: every omitted field keeps its stored value. is_paused may travel alone or alongside a configuration edit, and the pause is applied only after the whole request validates, so a 422 means nothing was written. A watch-config change (zone, watch_names, group_settings, dynamic_names) pulls the next sweep forward to within a minute and invalidates any in-flight pull lease; a ZONE change additionally wipes the baseline, discovered nameservers, last transcript, nameserver statistics, serial-drift timer and delegation timestamp so the next sweep re-baselines from scratch. Config edits also reconcile open episodes: episodes whose watch key is no longer watched (or all of them, on a zone change) close silently, survivors take the NEW group severity, and the incident and counters are recomputed. Two narrower response shapes exist: sending only is_paused answers with the bare {id}, and so does a write whose read-back came back empty.
Authorizations:
path Parameters
| id required | integer\d+ Example: 4211 DNS monitor id. |
Request Body schema: application/json
| name | string Monitor display name. Trimmed; 1-255 characters, otherwise the request is refused with |
| zone | string The DNS zone to watch, as a naked domain. It is normalized before storage: lowercased, with any scheme, path, query, port, leading "www." and trailing dot stripped, and IDN converted to punycode. It must then match a hostname of at most 253 characters with a 2-63 letter TLD; anything that parses as an IPv4 or IPv6 literal is refused with |
| watch_names | Array of strings Relative record names swept for A, AAAA and CNAME. Entries are lowercased, the trailing dot is trimmed and duplicates are removed. A name ending in the zone is reduced to its relative form: |
object Per record-group alerting. Any group you omit is filled in with its default (enabled, and severity critical for addresses / mail / ns, trouble for policy), and an unrecognised severity falls back to that default — this field can never fail validation. A disabled group still records its changes, but at | |
| dynamic_names | Array of strings Watch keys ("name|TYPE") whose VALUES are allowed to rotate: presence and record type are still compared, the answer itself is not. Entries that are not in the current watch matrix are dropped. Normally managed through POST /api/v2/dns/{id}/changes/{changeId}/dynamic rather than here, and omitting the key preserves the stored list. Ignored entirely on create, where the list always starts empty. |
| verify_sweeps | integer Enum: 1 3 5 Consecutive clean sweeps required before a change episode auto-closes as restored. Must be 1, 3 or 5 ( |
| serial_drift_seconds | integer Enum: 1800 7200 21600 How long SOA serials may disagree between the zone's authoritative nameservers before a serial-drift health episode opens, in seconds. Must be 1800 (30 min), 7200 (2 h) or 21600 (6 h) — |
| confirmations_required | integer Enum: 1 2 3 Outage-confirmation depth for the pull model: how many vantages must independently observe the same finding before it is acted on. Must be 1, 2 or 3 ( |
| snooze | integer Enum: 0 14400 86400 604800 A snooze DURATION in seconds, not a timestamp — the monitor stores |
(string or null) or (Array of integers or null) Vantage selection. The literal string "all" (and an absent key, null or an empty array) sweeps from the whole DNS-capable fleet. An explicit list of checkpoint ids must name at least 6 DNS-capable vantages, otherwise the request is refused with | |
| report_up | integer Whether recovery ("restored") notifications are sent. Any non-zero value stores 1. Omit to keep the stored value. |
| escalation_policy_id | integer Escalation policy that routes this monitor's alerts. 0 (the default) alerts every recipient at once; a value above 0 must reference a policy owned by the account or the request is refused with |
| branding_id | integer Branding template applied to this monitor's alert emails and reports. 0 (the default) uses the default theme; non-numeric junk, or an id not owned by the account, is refused with |
| notes | string Free-text operator note stored on the monitor. Trimmed and truncated to 500 characters; a non-string value stores an empty string. Written only when the key is present, so an omitted note is never clobbered. |
(Array of integers or objects or null) or (string or null) Alert recipients: bare contact ids, or objects naming what each id refers to. Every id must belong to the account. The literal string "all" selects every account contact and "no_changes" leaves the stored bindings alone. Omitting the key, or sending null or "no_changes", keeps the stored list and skips the ownership check entirely. An empty array reaches the guard and is refused with | |
Array of integers or integer Monitor-group ids this monitor belongs to; a single bare integer is accepted too. Every id must belong to the account. A scoped operator's submitted list is rewritten so the monitor always stays in at least one of their scope groups — it can never be created or edited into invisibility — while owner-assigned memberships outside that scope are preserved. | |
(boolean or null) or (integer or null) Pause (truthy) or resume (falsy, including an explicit null) the monitor. It may travel alone or alongside a configuration edit; either way the pause is applied only after the whole request has validated, so a 422 means nothing was written. Sent on its own the response is the bare {id}. | |
(string or null) or (integer or null) Records WHICH saved Location Profile the vantage selection came from, so the editor's Locations tab can show the applied profile. Either a built-in synthetic id ("builtin: |
Responses
Request samples
- Payload
{- "name": "example.com zone",
- "zone": "example.com",
- "watch_names": [
- "www"
], - "group_settings": {
- "addresses": {
- "enabled": true,
- "severity": "critical"
}, - "mail": {
- "enabled": true,
- "severity": "critical"
}, - "ns": {
- "enabled": true,
- "severity": "critical"
}, - "policy": {
- "enabled": true,
- "severity": "trouble"
}
}, - "dynamic_names": [
- "www|A"
], - "verify_sweeps": 3,
- "serial_drift_seconds": 1800,
- "confirmations_required": 3,
- "snooze": 0,
- "probes": "all",
- "report_up": 1,
- "escalation_policy_id": 0,
- "branding_id": 0,
- "notes": "Zone is managed by the platform team.",
- "contacts": [
- 42
], - "groups": [
- 7
], - "is_paused": true,
- "location_profile": "builtin:europe"
}Response samples
- 200
- 400
- 401
- 403
- 404
- 422
{- "data": {
- "id": 4211,
- "user": 1,
- "name": "example.com zone",
- "zone": "example.com",
- "watch_names": [
- "www"
], - "group_settings": {
- "addresses": {
- "enabled": true,
- "severity": "critical"
}, - "mail": {
- "enabled": true,
- "severity": "critical"
}, - "ns": {
- "enabled": true,
- "severity": "critical"
}, - "policy": {
- "enabled": true,
- "severity": "trouble"
}
}, - "dynamic_names": [
- "www|A"
], - "verify_sweeps": 3,
- "confirmations_required": 3,
- "serial_drift_seconds": 1800,
- "snooze_until": 0,
- "sweep_interval": 300,
- "nameservers": [
- {
- "host": "ns1.example.com",
- "ip": "203.0.113.10"
}
], - "serial_drift_since": 0,
- "delegation_checked_at": 1756100000,
- "active_changes": 0,
- "health_issues": 0,
- "check_errors": 0,
- "freeze_reason": "",
- "freeze_at": 0,
- "status": 1,
- "last_error": 0,
- "last_check": 1756100300,
- "next_check": 1756100600,
- "incident_id": 0,
- "is_critical": 0,
- "is_trouble": 0,
- "suspended": 0,
- "maintenance": 0,
- "paused": 0,
- "is_updated": 0,
- "report_down": 0,
- "report_up": 1,
- "escalation_policy_id": 0,
- "branding_id": 0,
- "notes": "",
- "added_date": 1756000000,
- "is_up": 1,
- "is_paused": 0,
- "is_maintenance": 0,
- "snooze_active": false,
- "records_watched": 11,
- "locations": [
- 12
], - "contacts": [
- {
- "id": 42,
- "is_team": 0,
- "is_user": 0,
- "is_operator": 0,
- "is_individual_contact": 1
}
], - "groups": [
- 7
], - "applied_location_profile": "builtin:europe"
}
}Get the detail-page summary
The entire DNS detail page in one payload: the decorated monitor with its baseline, the newest change and health episodes, the last sweep's raw transcript, the rolling 24-hour per-nameserver roll-up, recent incidents, the effective watch matrix and the SOA serial state. The episode feed is capped at 200 rows but is unioned with the newest change episode per watch key however old, so a long-running change never loses its ack affordance. Incidents here are RAW rows with Unix timestamps — the incidents endpoint returns pre-formatted strings instead.
Authorizations:
path Parameters
| id required | integer\d+ Example: 4211 DNS monitor id. |
Responses
Response samples
- 200
- 401
- 403
- 404
{- "data": {
- "now": 1756100400,
- "monitor": {
- "id": 4211,
- "user": 1,
- "name": "example.com zone",
- "zone": "example.com",
- "watch_names": [
- "www"
], - "group_settings": {
- "addresses": {
- "enabled": true,
- "severity": "critical"
}, - "mail": {
- "enabled": true,
- "severity": "critical"
}, - "ns": {
- "enabled": true,
- "severity": "critical"
}, - "policy": {
- "enabled": true,
- "severity": "trouble"
}
}, - "dynamic_names": [
- "www|A"
], - "verify_sweeps": 3,
- "confirmations_required": 3,
- "serial_drift_seconds": 1800,
- "snooze_until": 0,
- "sweep_interval": 300,
- "nameservers": [
- {
- "host": "ns1.example.com",
- "ip": "203.0.113.10"
}
], - "serial_drift_since": 0,
- "delegation_checked_at": 1756100000,
- "active_changes": 0,
- "health_issues": 0,
- "check_errors": 0,
- "freeze_reason": "",
- "freeze_at": 0,
- "status": 1,
- "last_error": 0,
- "last_check": 1756100300,
- "next_check": 1756100600,
- "incident_id": 0,
- "is_critical": 0,
- "is_trouble": 0,
- "suspended": 0,
- "maintenance": 0,
- "paused": 0,
- "is_updated": 0,
- "report_down": 0,
- "report_up": 1,
- "escalation_policy_id": 0,
- "branding_id": 0,
- "notes": "",
- "added_date": 1756000000,
- "is_up": 1,
- "is_paused": 0,
- "is_maintenance": 0,
- "snooze_active": false,
- "records_watched": 11,
- "locations": [
- 12
], - "baseline": { }
}, - "episodes": [
- {
- "id": 90211,
- "monitor": 4211,
- "kind": "change",
- "watch_key": "www|A",
- "grp": "addresses",
- "severity": "critical",
- "before_json": "string",
- "after_json": "string",
- "prev_clean_at": 1756099000,
- "first_seen": 1756099300,
- "last_seen": 1756099600,
- "clean_streak": 0,
- "restored_at": 0,
- "verified_at": 0,
- "acked_at": 0,
- "acked_by": "",
- "ack_note": "",
- "status": "active",
- "label": "A www",
- "group_label": "Addresses",
- "before": { },
- "after": { }
}
], - "last_sweep": {
- "at": 1756100300,
- "servers": [
- { }
], - "observed": { },
- "delegation": { },
- "zone_cname": "string",
- "errors": 0,
- "probe": "amsterdam.uptimia.net",
- "probe_location": 5,
- "confirm_used": true
}, - "ns_stats": [
- {
- "host": "ns1.example.com",
- "answered": 288,
- "total": 288,
- "median_ms": 24
}
], - "incidents": [
- {
- "id": 55120,
- "site_id": 4211,
- "time_started": 1756099300,
- "time_ended": 0,
- "down_report_sent": 1,
- "down_report_code": 0,
- "up_report_sent": 0,
- "up_report_code": 0,
- "close_reason": "restored",
- "health_alerted": 0,
- "error_code": "dns_changed",
- "error_type": "dns",
- "reason": "string",
- "reason_full": "string",
- "is_critical": 1,
- "is_trouble": 0,
- "servers": "string"
}
], - "watches": [
- {
- "key": "www|A",
- "name": "www",
- "type": "A",
- "grp": "addresses",
- "fqdn": "www.example.com",
- "baseline_values": [
- "93.184.216.34"
], - "baseline_ttl": 300,
- "set_at": 1756000000,
- "source": "baseline",
- "current_values": [
- "93.184.216.34"
], - "current_ttl": 300,
- "current_unverified": false,
- "dynamic": false,
- "state": "baseline",
- "changed_at": 1756000000,
- "episode_id": 90211
}
], - "serial": {
- "in_sync": true,
- "serials": { },
- "drift_since": 0
}
}
}Queue a sweep now
Queues an on-demand sweep by making the monitor due immediately and clearing any in-flight pull lease, so the per-minute sweep cron picks it up on its next tick. It does NOT sweep synchronously — poll the logs or the summary for the result. Rate-limited to 10 requests per sliding minute per ACCOUNT, not per IP, because teams share NAT addresses; each recheck also drops any in-flight result, so hammering it prevents the monitor from ever completing a sweep. Refused outright for monitors the sweep engine skips anyway.
Authorizations:
path Parameters
| id required | integer\d+ Example: 4211 DNS monitor id. |
Responses
Response samples
- 200
- 401
- 403
- 404
- 409
- 429
{- "data": {
- "queued": true,
- "next_check": 1756100400
}
}Acknowledge a change episode
Accepts a changed record as the new truth: the episode's after-values become the baseline for that watch key (recorded with source ack), the episode closes as acked with the acting operator and an optional note, and the monitor's counters and open incident are recomputed. When nothing alertable remains open, the incident closes through the recovery path: everyone who received the alert is told who accepted the change, and report_up is honoured. The request is refused while the value moved since it was shown or while a nameserver still serves another value; neither check applies to a dynamic record. Acknowledge only changes you intend to keep; use mark-dynamic instead when the record legitimately rotates. Health findings and already-closed episodes cannot be acknowledged.
Authorizations:
path Parameters
| id required | integer\d+ Example: 4211 DNS monitor id. |
| changeId required | integer\d+ Example: 90211 Change-episode id, as returned in |
Request Body schema: application/json
| note | string Optional free-text reason recorded on the episode as |
| after_digest | string The episode's |
Responses
Request samples
- Payload
{- "note": "DNS migration to the new provider, expected.",
- "after_digest": "string"
}Response samples
- 200
- 401
- 403
- 404
- 409
{- "data": {
- "acked": true
}
}Accept several open record changes in one decision
Checks every listed change, then accepts them all or none. An id listed twice counts once. Each new answer becomes that record's baseline. When nothing alertable remains open the incident closes and everyone who received the warning is told who accepted it. 409 value_moved means a record changed again since it was shown; 409 servers_disagree means a nameserver still serves something else.
Authorizations:
path Parameters
| id required | integer\d+ |
Request Body schema: application/json
required | Array of objects |
| note | string Optional reason, at most 500 characters. |
Responses
Request samples
- Payload
{- "changes": [
- {
- "id": 1387,
- "after_digest": "string"
}
], - "note": "string"
}Mark a changed record as dynamic
"This record legitimately rotates." Adds the episode's watch key to the monitor's dynamic list — from then on only the record's presence and type are compared and its values are ignored — AND acknowledges the episode in the same call, with the fixed note "marked dynamic — values are allowed to rotate". Use it for round-robin CDN answers and similar churn that would otherwise page on every rotation. The key is re-validated against the current watch matrix, so a key that is no longer watched is dropped. Health findings and non-active episodes are refused.
Authorizations:
path Parameters
| id required | integer\d+ Example: 4211 DNS monitor id. |
| changeId required | integer\d+ Example: 90211 Change-episode id, as returned in |
Responses
Response samples
- 200
- 401
- 403
- 404
- 409
{- "data": {
- "dynamic": true
}
}List incidents for a DNS monitor
Newest-first incident history for one DNS monitor — change and health alerts and their recoveries — pre-formatted for the shared Incidents card. Beware the timestamps: time_started and time_ended are human strings in the account timezone, not Unix integers; read GET /api/v2/dns/{id}/summary if you need raw incident rows. metadata.total is a real count over the same date window and is unaffected by limit and offset.
Authorizations:
path Parameters
| id required | integer\d+ Example: 4211 DNS monitor id. |
query Parameters
| limit | integer [ 1 .. 200 ] Default: 50 Example: limit=50 Page size. Defaults to 50 and is clamped into 1-200, so an absurd value cannot pull the whole table and a negative one cannot yield an empty page. |
| offset | integer Default: 0 Example: offset=0 Zero-based row offset into the newest-first list. Negatives are floored to 0. |
| date_start | integer Default: 0 Example: date_start=0 Unix timestamp lower bound; 0 (the default) means unbounded. The window is half-open on overlap: an incident counts when it was still open after this moment (time_ended = 0 or time_ended > date_start), so a long outage that began earlier is still included. |
| date_end | integer Default: 0 Example: date_end=0 Unix timestamp upper bound; 0 (the default) means unbounded. An incident counts when it started before this moment. Applies to the page and to metadata.total alike. |
Responses
Response samples
- 200
- 401
- 403
- 404
{- "data": [
- {
- "error_id": 55120,
- "impact": { },
- "error_display": "DNS records changed",
- "time_started": "August 25, 2026 14:03",
- "time_ended": "August 25, 2026 15:11",
- "time_started_unix": 1787666580,
- "time_ended_unix": 1787670660,
- "date_ended": "1 hour 8 minutes",
- "time_elapsed": "2 hours ago"
}
], - "metadata": {
- "total": 3,
- "monitor_id": 4211
}
}List the per-sweep check log
Newest-first page of the append-only sweep log — one row per sweep, read from the check-history store and retained for the account's plan retention like every other family's check history. Note that duration is the SLOWEST nameserver's mean answer time for that sweep, not an average across servers, and that changes and health_issues are the episode counts as they stood at sweep time rather than now. Take time from here to open a single sweep in full.
Authorizations:
path Parameters
| id required | integer\d+ Example: 4211 DNS monitor id. |
query Parameters
| limit | integer [ 1 .. 100 ] Default: 20 Example: limit=20 Page size. Defaults to 20 and is clamped into 1-100. |
| offset | integer Default: 0 Example: offset=0 Zero-based row offset into the newest-first log. Negatives are floored to 0. |
Responses
Response samples
- 200
- 401
- 403
- 404
{- "data": [
- {
- "time": 1756100300,
- "duration": 42,
- "status": 1,
- "server_location": 12,
- "queries": 44,
- "servers_total": 4,
- "servers_ok": 4,
- "changes": 0,
- "warning_changes": 0,
- "health_issues": 0,
- "errors": 0
}
], - "metadata": {
- "total": 4321,
- "monitor_id": 4211
}
}Get one sweep in full
One sweep's counters plus the per-nameserver answers behind them: each authoritative server's reachability, mean answer time, SOA serial, AA bit and TCP/recursion probes, and every watched record exactly as THAT server answered it. servers and episodes are mutually exclusive and cells_source says which you are looking at — sweeps older than per-cell storage have no stored answers, so the episodes open at that moment are rebuilt instead, a view that can name what was wrong and never what was right. The {time} segment must be an exact time value from the logs listing.
Authorizations:
path Parameters
| id required | integer\d+ Example: 4211 DNS monitor id. |
| time required | integer\d+ Example: 1756100300 Unix timestamp of the sweep, taken verbatim from a |
Responses
Response samples
- 200
- 401
- 403
- 404
{- "data": {
- "found": true,
- "time": 1756100300,
- "duration": 42,
- "status": 1,
- "server_location": 12,
- "location": "Frankfurt",
- "flag": "de",
- "queries": 44,
- "servers_total": 4,
- "servers_ok": 4,
- "changes": 0,
- "warning_changes": 0,
- "health_issues": 0,
- "errors": 0,
- "cells_source": "log",
- "servers": [
- {
- "host": "ns1.example.com",
- "ip": "203.0.113.10",
- "state": "ok",
- "ms": 21,
- "soa_serial": 2026082501,
- "aa_ok": true,
- "tcp_ok": true,
- "recursion_open": false,
- "queries": [
- {
- "key": "www|A",
- "label": "A www",
- "name": "www",
- "type": "A",
- "rcode": "NOERROR",
- "ttl": 300,
- "answer": "93.184.216.34",
- "ms": 19
}
]
}
], - "episodes": [
- {
- "id": 90211,
- "monitor": 4211,
- "kind": "change",
- "watch_key": "www|A",
- "grp": "addresses",
- "severity": "critical",
- "before_json": "string",
- "after_json": "string",
- "prev_clean_at": 1756099000,
- "first_seen": 1756099300,
- "last_seen": 1756099600,
- "clean_streak": 0,
- "restored_at": 0,
- "verified_at": 0,
- "acked_at": 0,
- "acked_by": "",
- "ack_note": "",
- "status": "active",
- "label": "A www",
- "group_label": "Addresses",
- "before": { },
- "after": { }
}
]
}, - "metadata": {
- "monitor_id": 4211
}
}Delete a DNS incident
Deletes one CLOSED incident record belonging to this monitor. The currently open incident can never be deleted — that is guarded twice, once against the monitor's own open-incident pointer and once against the incident's missing end time — because removing it would strand the monitor in an alerting state with nothing to recover from. Deleting an incident does not touch the sweep log or the change episodes behind it.
Authorizations:
path Parameters
| id required | integer\d+ Example: 4211 DNS monitor id. |
| incidentId required | integer\d+ Example: 55120 Incident id, as returned in |
Responses
Response samples
- 200
- 401
- 403
- 404
- 409
{- "data": {
- "deleted": true
}
}List all heartbeat monitors
Returns every heartbeat monitor on the account, ordered by name ascending, each decorated with the ping URL its job should call. Rows carry the whole monitor record but NOT the contacts/groups relations — fetch a single monitor for those. Monitor-group scoping is applied inside the read, so a scoped team member sees only the monitors in their scope groups and a member with an empty scope gets an empty list with total 0.
Authorizations:
query Parameters
| search | string Example: search=invoice Case-insensitive substring match on the monitor name (SQL LIKE %search%). An empty string is treated as no filter. |
Responses
Response samples
- 200
- 401
- 403
{- "data": [
- {
- "id": "4127",
- "user": "1",
- "name": "Nightly invoice export",
- "watches": "cron",
- "schedule_type": "cron",
- "interval_seconds": "300",
- "cron_expression": "0 3 * * *",
- "timezone": "0",
- "grace_seconds": "120",
- "late_threshold_seconds": "10",
- "runtime_limit_seconds": "0",
- "fail_alerts": "1",
- "ping_token": "hb_5c8f1e0a4d7b2396af10c53e6b8d24f7",
- "armed": "0",
- "last_ping_at": "1756100000",
- "last_ping_signal": "success",
- "last_ping_ip": "203.0.113.42",
- "last_start_at": "0",
- "expected_next_at": "1756186400",
- "missed_deadline": "1756186520",
- "status": "1",
- "last_error": "0",
- "last_check": "0",
- "next_check": "0",
- "incident_id": "0",
- "last_incident_at": "0",
- "is_critical": "0",
- "is_trouble": "0",
- "suspended": "0",
- "maintenance": "0",
- "paused": "0",
- "is_updated": "0",
- "report_down": "0",
- "report_up": "1",
- "escalation_policy_id": "0",
- "branding_id": "0",
- "notes": "",
- "added_date": "1755000000",
- "is_up": 1,
- "is_paused": 0,
- "is_maintenance": 0,
}
], - "metadata": {
- "total": 3
}
}Create a heartbeat monitor
Creates an inbound (dead-man switch) monitor and mints its unique ping token — the literal hb_ followed by 32 hexadecimal characters. The monitor is written unarmed and arms itself on its first real ping, so nothing can alert until the job has checked in once; a manual test ping deliberately does not arm it. Omitting contacts entirely auto-seeds every account contact, while sending an empty contacts array is refused outright, so a monitor can never exist with nobody to alert. Creation is refused with 403 plan_exceeded once the account non-suspended heartbeat monitors reach the plan heartbeat allowance.
Authorizations:
Request Body schema: application/json
| name required | string Monitor display name. Trimmed; must be non-empty and at most 255 characters (error code name_invalid). |
| watches | string Enum: "cron" "worker" "queue" "backup" "iot" "other" What the job is. Labelling only — it never changes the schedule maths. Defaults to cron (error code watches_invalid). |
| schedule_type | string Enum: "interval" "cron" interval expects a ping every interval_seconds after the last one (self-referential, so timezone is inert); cron expects a ping at each fire time of cron_expression, evaluated in the monitor timezone. Defaults to interval (error code schedule_type_invalid). |
| interval_seconds | integer Expected seconds between pings, 30 to 7776000 (90 days); defaults to 300. Both schedule columns are always persisted, so a value sent in cron mode is still range-checked and stored as the dormant value (error code interval_invalid). |
| cron_expression | string 5-field cron expression. Required in cron mode; in interval mode it is optional but, when non-empty, still validated and stored as the dormant value. Macros (@hourly, @daily, @midnight, @weekly, @monthly, @yearly, @annually) and three-letter month/day names are accepted. Validation walks 3100 consecutive days from a fixed 2001-01-01 reference, so an expression that parses but can never match a real date (0 0 30 2 *) is rejected (error code cron_invalid). |
| timezone | integer config/time_zone.php id (1-128) the cron schedule is evaluated in — the zone the customer cron daemon runs in, not where the account sits. 0, the default, inherits the account timezone (UTC when the account has none). Only consulted in cron mode. The shape is checked strictly: an integer or an all-digit string, never a cast of a non-numeric value (error code timezone_invalid). |
| grace_seconds | integer How long after the expected ping time the sweep waits before opening a missed-ping incident, 0 to 2592000 (30 days); defaults to 120 (error code grace_invalid). |
| late_threshold_seconds | integer Lower bound of the late-but-within-grace warning band, 0 to 2592000; defaults to 10. A ping later than expected by less than this is treated as on time. A value above grace_seconds is refused with late_threshold_exceeds_grace because it would leave the warning band empty and the late metric inert (error code late_threshold_invalid). |
| runtime_limit_seconds | integer Maximum allowed job runtime — the gap between a /start ping and its matching success ping — up to 604800 (7 days). 0, the default, disables the check (error code runtime_limit_invalid). |
| fail_alerts | boolean Whether an explicit /fail ping pages the recipients. Defaults to true when absent, and is parsed permissively, so the string "false" disables paging rather than enabling it. |
| report_up | integer Whether the recovery notification is sent when the monitor pings again. Stored as 0 only when the value casts to integer 0; anything else, including absence, stores 1. |
| escalation_policy_id | integer Escalation policy that routes this monitor alerts. 0, the default, alerts every recipient at once; a value above 0 must reference a policy owned by this account. |
| branding_id | integer Branding theme applied to this monitor alert emails and reports. 0, the default, is the standard theme; a value above 0 must belong to this account. |
| notes | string Free-text operator note shown on the incident page and included in alert emails and webhook payloads. Non-string input becomes an empty string; the value is trimmed and hard-truncated to 500 characters. |
(Array of integers or objects) or string Alert recipients: an array of bare contact ids, an array of typed recipient objects, or one of the sentinels "all" and "no_changes". OMITTING the key (or sending null, or the no_changes sentinel, which has no stored list to preserve on a create) auto-seeds every contact on the account so the new monitor always alerts somebody; sending a PRESENT but EMPTY array is refused with no_contacts_provided. Every id must belong to this account. | |
| groups | Array of integers Monitor-group ids this monitor joins; every id must belong to this account. An absent key assigns no groups. For a scoped team member the submitted list is rewritten so the new monitor always lands in at least one of their scope groups and can never be created invisible to its author; unrestricted actors keep the list byte for byte. |
Responses
Request samples
- Payload
{- "name": "Nightly invoice export",
- "watches": "cron",
- "schedule_type": "interval",
- "interval_seconds": 300,
- "cron_expression": "0 3 * * 1-5",
- "timezone": 0,
- "grace_seconds": 120,
- "late_threshold_seconds": 10,
- "runtime_limit_seconds": 0,
- "fail_alerts": true,
- "report_up": 1,
- "escalation_policy_id": 0,
- "branding_id": 0,
- "notes": "Runs on cron-box-2; owner: billing team",
- "contacts": [
- 65
], - "groups": [
- 12
]
}Response samples
- 201
- 400
- 401
- 403
- 422
{- "data": {
- "id": "4127",
- "user": "1",
- "name": "Nightly invoice export",
- "watches": "cron",
- "schedule_type": "cron",
- "interval_seconds": "300",
- "cron_expression": "0 3 * * *",
- "timezone": "0",
- "grace_seconds": "120",
- "late_threshold_seconds": "10",
- "runtime_limit_seconds": "0",
- "fail_alerts": "1",
- "ping_token": "hb_5c8f1e0a4d7b2396af10c53e6b8d24f7",
- "armed": "0",
- "last_ping_at": "0",
- "last_ping_signal": "",
- "last_ping_ip": "",
- "last_start_at": "0",
- "expected_next_at": "0",
- "missed_deadline": "120",
- "status": "1",
- "last_error": "0",
- "last_check": "0",
- "next_check": "0",
- "incident_id": "0",
- "last_incident_at": "0",
- "is_critical": "0",
- "is_trouble": "0",
- "suspended": "0",
- "maintenance": "0",
- "paused": "0",
- "is_updated": "0",
- "report_down": "0",
- "report_up": "1",
- "escalation_policy_id": "0",
- "branding_id": "0",
- "notes": "",
- "added_date": "1755000000",
- "is_up": 1,
- "is_paused": 0,
- "is_maintenance": 0,
- "contacts": [
- {
- "id": "string",
- "is_team": "string",
- "is_user": "string",
- "is_operator": "string",
- "is_individual_contact": "string"
}
], - "groups": [
- "12"
]
}
}Validate a cron expression and preview its next runs
Validates a cron expression that has not been saved yet and previews its next three fire times, so the editor can show the customer what the schedule will do before they commit to it. Validation is stricter than a parse: the expression is also walked across 3100 consecutive days from a FIXED 2001-01-01 reference date to prove it can match a real calendar day at all, so a parseable-but-impossible expression such as 0 0 30 2 * is reported invalid. That satisfiability walk does not depend on the current date or on the timezone. The runs are previewed in the timezone the caller is about to save; 0 or an absent timezone previews them in the account timezone, and an unresolvable timezone id is silently clamped to that same account default rather than rejected.
Authorizations:
Request Body schema: application/json
| expression | string The cron expression to test: five fields (minute hour day-of-month month day-of-week), or one of the macros @hourly, @daily, @midnight, @weekly, @monthly, @yearly and @annually. Three-letter month names (jan-dec) and day names (sun-sat) are accepted, as are the step, range and list forms; day-of-week 7 means Sunday. Trimmed and lowercased before use; an empty or absent value simply answers valid=false rather than erroring. |
| timezone | integer config/time_zone.php id (1-128) to preview the runs in. 0 or absent uses the account timezone, which resolves to UTC when the account has none. |
Responses
Request samples
- Payload
{- "expression": "0 3 * * 1-5",
- "timezone": 128
}Response samples
- 200
- 400
- 401
- 403
{- "data": {
- "valid": true,
- "description": "At 03:00, Monday through Friday",
- "next_runs": [
- 1756186200
]
}
}Get a heartbeat monitor
Returns one heartbeat monitor as the fully decorated record — every stored column, the derived up/paused/maintenance flags, the alert recipients, the monitor-group memberships and the ping URL. This is the read the monitor editor prefills from. A monitor that does not exist, belongs to another account, or sits outside the acting member monitor-group scope answers the same 404 in all three cases, so ids cannot be probed.
Authorizations:
path Parameters
| id required | integer\d+ Example: 4127 Monitor id |
Responses
Response samples
- 200
- 401
- 403
- 404
{- "data": {
- "id": "4127",
- "user": "1",
- "name": "Nightly invoice export",
- "watches": "cron",
- "schedule_type": "cron",
- "interval_seconds": "300",
- "cron_expression": "0 3 * * *",
- "timezone": "0",
- "grace_seconds": "120",
- "late_threshold_seconds": "10",
- "runtime_limit_seconds": "0",
- "fail_alerts": "1",
- "ping_token": "hb_5c8f1e0a4d7b2396af10c53e6b8d24f7",
- "armed": "0",
- "last_ping_at": "1756100000",
- "last_ping_signal": "success",
- "last_ping_ip": "203.0.113.42",
- "last_start_at": "0",
- "expected_next_at": "1756186400",
- "missed_deadline": "1756186520",
- "status": "1",
- "last_error": "0",
- "last_check": "0",
- "next_check": "0",
- "incident_id": "0",
- "last_incident_at": "0",
- "is_critical": "0",
- "is_trouble": "0",
- "suspended": "0",
- "maintenance": "0",
- "paused": "0",
- "is_updated": "0",
- "report_down": "0",
- "report_up": "1",
- "escalation_policy_id": "0",
- "branding_id": "0",
- "notes": "",
- "added_date": "1755000000",
- "is_up": 1,
- "is_paused": 0,
- "is_maintenance": 0,
- "contacts": [
- {
- "id": "string",
- "is_team": "string",
- "is_user": "string",
- "is_operator": "string",
- "is_individual_contact": "string"
}
], - "groups": [
- "12"
]
}
}Update a heartbeat monitor
Partial update: omitted fields keep their stored values. The nine identity, schedule and threshold fields (name, watches, schedule_type, interval_seconds, cron_expression, timezone, grace_seconds, late_threshold_seconds, runtime_limit_seconds) are merged from the stored row before validation, so a payload that changes one of them is still validated against the rest of the saved configuration — raising late_threshold_seconds alone can therefore fail against the stored grace_seconds. The guard order is load-bearing: ownership first, then the payload parse, then every validation, and only then the pause write, so a request that ends in a 422 can never have silently paused the dead-man switch. Sending is_paused on its own returns { data: { id } } without re-reading the monitor; resuming grants one fresh schedule window so a stale pre-pause deadline cannot alert immediately. PUT is an alias of PATCH — both verbs resolve to the same handler and behave identically, so PUT is NOT a full replace; it is kept for client compatibility.
Authorizations:
path Parameters
| id required | integer\d+ Example: 4127 Monitor id |
Request Body schema: application/json
| is_paused | boolean Pause (true) or resume (false) the monitor. Parsed as a real boolean, so the string "false" RESUMES; a value that cannot be parsed is a 422. Resuming grants one fresh schedule window so a stale pre-pause deadline cannot alert immediately. When is_paused is the ONLY key in the body the pause is applied and the response is just { data: { id } } with no monitor re-read; sent alongside other fields it is applied only after every sibling field has validated, so a request that ends in a 422 can never have silently paused the dead-man switch. |
| name | string Monitor display name; trimmed, 1 to 255 characters. Omitted means unchanged. |
| watches | string Enum: "cron" "worker" "queue" "backup" "iot" "other" What the job is. Labelling only. Omitted means unchanged. |
| schedule_type | string Enum: "interval" "cron" interval or cron. Changing it in either direction counts as a schedule change and recomputes the next expected ping for an armed monitor. |
| interval_seconds | integer Expected seconds between pings, 30 to 7776000. A change recomputes expected_next_at for an armed monitor from the later of its last ping and now. |
| cron_expression | string 5-field cron expression (or one of the @hourly/@daily/@midnight/@weekly/@monthly/@yearly/@annually macros); validated whenever non-empty and mandatory in cron mode. A change recomputes expected_next_at for an armed monitor. |
| timezone | integer config/time_zone.php id; 0 inherits the account timezone. Counts as a schedule change ONLY in cron mode — an interval schedule is self-referential, so recomputing there would push a dead job missed-ping incident out by a whole interval. In cron mode the next expected ping is recomputed immediately so an armed monitor does not keep the previous zone deadline. Same strict integer / all-digit-string shape rule as on create. |
| grace_seconds | integer Seconds before a missed ping opens an incident, 0 to 2592000. Must be greater than or equal to late_threshold_seconds, compared against the MERGED values — so raising the threshold alone can fail against the stored grace. |
| late_threshold_seconds | integer Lower bound of the in-grace late band, 0 to 2592000. Must not exceed grace_seconds. |
| runtime_limit_seconds | integer Maximum permitted /start-to-success runtime in seconds, up to 604800. 0 turns the check off. |
| fail_alerts | boolean Whether a /fail ping pages the recipients. Absent preserves the stored flag; the string "false" disables paging. |
| report_up | integer Send the recovery notification. Stored as 0 only when the value casts to integer 0; absent preserves the stored flag. |
| escalation_policy_id | integer Escalation policy to alert through; 0 alerts every recipient at once and a value above 0 must belong to this account. Absent preserves the stored binding. |
| branding_id | integer Branding template; 0 is the standard theme and a value above 0 must belong to this account. Absent preserves the stored value. |
| notes | string Operator note, trimmed and capped at 500 characters. Normalized only when the key is actually present; an absent key preserves the stored note. |
(Array of integers or objects) or string Replaces the alert recipients. Bare ids, typed recipient objects, or the sentinels "all" and "no_changes". An ABSENT key — or an explicit null — leaves the stored bindings untouched and skips the ownership check entirely. | |
| groups | Array of integers Replaces the monitor group memberships; every id must belong to this account. Absent leaves memberships unchanged. For a scoped team member the list is rewritten so their edit stays confined to their scope groups, while owner-assigned memberships outside that scope are preserved rather than orphaned. |
Responses
Request samples
- Payload
{- "is_paused": false,
- "name": "Nightly invoice export",
- "watches": "cron",
- "schedule_type": "cron",
- "interval_seconds": 300,
- "cron_expression": "0 3 * * 1-5",
- "timezone": 0,
- "grace_seconds": 120,
- "late_threshold_seconds": 10,
- "runtime_limit_seconds": 0,
- "fail_alerts": true,
- "report_up": 1,
- "escalation_policy_id": 0,
- "branding_id": 0,
- "notes": "Runs on cron-box-2; owner: billing team",
- "contacts": [
- 65
], - "groups": [
- 12
]
}Response samples
- 200
- 400
- 401
- 403
- 404
- 422
{- "data": {
- "id": "4127",
- "user": "1",
- "name": "Nightly invoice export",
- "watches": "cron",
- "schedule_type": "cron",
- "interval_seconds": "300",
- "cron_expression": "0 3 * * *",
- "timezone": "0",
- "grace_seconds": "120",
- "late_threshold_seconds": "10",
- "runtime_limit_seconds": "0",
- "fail_alerts": "1",
- "ping_token": "hb_5c8f1e0a4d7b2396af10c53e6b8d24f7",
- "armed": "0",
- "last_ping_at": "1756100000",
- "last_ping_signal": "success",
- "last_ping_ip": "203.0.113.42",
- "last_start_at": "0",
- "expected_next_at": "1756186400",
- "missed_deadline": "1756186520",
- "status": "1",
- "last_error": "0",
- "last_check": "0",
- "next_check": "0",
- "incident_id": "0",
- "last_incident_at": "0",
- "is_critical": "0",
- "is_trouble": "0",
- "suspended": "0",
- "maintenance": "0",
- "paused": "0",
- "is_updated": "0",
- "report_down": "0",
- "report_up": "1",
- "escalation_policy_id": "0",
- "branding_id": "0",
- "notes": "",
- "added_date": "1755000000",
- "is_up": 1,
- "is_paused": 0,
- "is_maintenance": 0,
- "contacts": [
- {
- "id": "string",
- "is_team": "string",
- "is_user": "string",
- "is_operator": "string",
- "is_individual_contact": "string"
}
], - "groups": [
- "12"
]
}
}Delete a heartbeat monitor
Permanently deletes the monitor and everything hanging off it: the monitor record, its recipient bindings, its incidents, its frozen ping/maintenance/pause rows, its monitor-group memberships and its ClickHouse ping history. The ping history purge is best-effort — a ClickHouse outage is logged and swallowed so it cannot block the erasure of the relational data. The deletion is irreversible and frees a slot in the plan heartbeat allowance.
Authorizations:
path Parameters
| id required | integer\d+ Example: 4127 Monitor id |
Responses
Response samples
- 200
- 401
- 403
- 404
{- "data": {
- "deleted": true
}
}Update a heartbeat monitor
Partial update: omitted fields keep their stored values. The nine identity, schedule and threshold fields (name, watches, schedule_type, interval_seconds, cron_expression, timezone, grace_seconds, late_threshold_seconds, runtime_limit_seconds) are merged from the stored row before validation, so a payload that changes one of them is still validated against the rest of the saved configuration — raising late_threshold_seconds alone can therefore fail against the stored grace_seconds. The guard order is load-bearing: ownership first, then the payload parse, then every validation, and only then the pause write, so a request that ends in a 422 can never have silently paused the dead-man switch. Sending is_paused on its own returns { data: { id } } without re-reading the monitor; resuming grants one fresh schedule window so a stale pre-pause deadline cannot alert immediately. The same route serves PUT with identical semantics.
Authorizations:
path Parameters
| id required | integer\d+ Example: 4127 Monitor id |
Request Body schema: application/json
| is_paused | boolean Pause (true) or resume (false) the monitor. Parsed as a real boolean, so the string "false" RESUMES; a value that cannot be parsed is a 422. Resuming grants one fresh schedule window so a stale pre-pause deadline cannot alert immediately. When is_paused is the ONLY key in the body the pause is applied and the response is just { data: { id } } with no monitor re-read; sent alongside other fields it is applied only after every sibling field has validated, so a request that ends in a 422 can never have silently paused the dead-man switch. |
| name | string Monitor display name; trimmed, 1 to 255 characters. Omitted means unchanged. |
| watches | string Enum: "cron" "worker" "queue" "backup" "iot" "other" What the job is. Labelling only. Omitted means unchanged. |
| schedule_type | string Enum: "interval" "cron" interval or cron. Changing it in either direction counts as a schedule change and recomputes the next expected ping for an armed monitor. |
| interval_seconds | integer Expected seconds between pings, 30 to 7776000. A change recomputes expected_next_at for an armed monitor from the later of its last ping and now. |
| cron_expression | string 5-field cron expression (or one of the @hourly/@daily/@midnight/@weekly/@monthly/@yearly/@annually macros); validated whenever non-empty and mandatory in cron mode. A change recomputes expected_next_at for an armed monitor. |
| timezone | integer config/time_zone.php id; 0 inherits the account timezone. Counts as a schedule change ONLY in cron mode — an interval schedule is self-referential, so recomputing there would push a dead job missed-ping incident out by a whole interval. In cron mode the next expected ping is recomputed immediately so an armed monitor does not keep the previous zone deadline. Same strict integer / all-digit-string shape rule as on create. |
| grace_seconds | integer Seconds before a missed ping opens an incident, 0 to 2592000. Must be greater than or equal to late_threshold_seconds, compared against the MERGED values — so raising the threshold alone can fail against the stored grace. |
| late_threshold_seconds | integer Lower bound of the in-grace late band, 0 to 2592000. Must not exceed grace_seconds. |
| runtime_limit_seconds | integer Maximum permitted /start-to-success runtime in seconds, up to 604800. 0 turns the check off. |
| fail_alerts | boolean Whether a /fail ping pages the recipients. Absent preserves the stored flag; the string "false" disables paging. |
| report_up | integer Send the recovery notification. Stored as 0 only when the value casts to integer 0; absent preserves the stored flag. |
| escalation_policy_id | integer Escalation policy to alert through; 0 alerts every recipient at once and a value above 0 must belong to this account. Absent preserves the stored binding. |
| branding_id | integer Branding template; 0 is the standard theme and a value above 0 must belong to this account. Absent preserves the stored value. |
| notes | string Operator note, trimmed and capped at 500 characters. Normalized only when the key is actually present; an absent key preserves the stored note. |
(Array of integers or objects) or string Replaces the alert recipients. Bare ids, typed recipient objects, or the sentinels "all" and "no_changes". An ABSENT key — or an explicit null — leaves the stored bindings untouched and skips the ownership check entirely. | |
| groups | Array of integers Replaces the monitor group memberships; every id must belong to this account. Absent leaves memberships unchanged. For a scoped team member the list is rewritten so their edit stays confined to their scope groups, while owner-assigned memberships outside that scope are preserved rather than orphaned. |
Responses
Request samples
- Payload
{- "is_paused": false,
- "name": "Nightly invoice export",
- "watches": "cron",
- "schedule_type": "cron",
- "interval_seconds": 300,
- "cron_expression": "0 3 * * 1-5",
- "timezone": 0,
- "grace_seconds": 120,
- "late_threshold_seconds": 10,
- "runtime_limit_seconds": 0,
- "fail_alerts": true,
- "report_up": 1,
- "escalation_policy_id": 0,
- "branding_id": 0,
- "notes": "Runs on cron-box-2; owner: billing team",
- "contacts": [
- 65
], - "groups": [
- 12
]
}Response samples
- 200
- 400
- 401
- 403
- 404
- 422
{- "data": {
- "id": "4127",
- "user": "1",
- "name": "Nightly invoice export",
- "watches": "cron",
- "schedule_type": "cron",
- "interval_seconds": "300",
- "cron_expression": "0 3 * * *",
- "timezone": "0",
- "grace_seconds": "120",
- "late_threshold_seconds": "10",
- "runtime_limit_seconds": "0",
- "fail_alerts": "1",
- "ping_token": "hb_5c8f1e0a4d7b2396af10c53e6b8d24f7",
- "armed": "0",
- "last_ping_at": "1756100000",
- "last_ping_signal": "success",
- "last_ping_ip": "203.0.113.42",
- "last_start_at": "0",
- "expected_next_at": "1756186400",
- "missed_deadline": "1756186520",
- "status": "1",
- "last_error": "0",
- "last_check": "0",
- "next_check": "0",
- "incident_id": "0",
- "last_incident_at": "0",
- "is_critical": "0",
- "is_trouble": "0",
- "suspended": "0",
- "maintenance": "0",
- "paused": "0",
- "is_updated": "0",
- "report_down": "0",
- "report_up": "1",
- "escalation_policy_id": "0",
- "branding_id": "0",
- "notes": "",
- "added_date": "1755000000",
- "is_up": 1,
- "is_paused": 0,
- "is_maintenance": 0,
- "contacts": [
- {
- "id": "string",
- "is_team": "string",
- "is_user": "string",
- "is_operator": "string",
- "is_individual_contact": "string"
}
], - "groups": [
- "12"
]
}
}Regenerate the ping token
Mints a new ping token for the monitor and returns it with its ping URL. The old URL stops working the instant the write lands — the public ingest looks a monitor up by its token — so update the job before rotating, or the next ping will be lost and the monitor will alert. No request body is read.
Authorizations:
path Parameters
| id required | integer\d+ Example: 4127 Monitor id |
Responses
Response samples
- 200
- 401
- 403
- 404
{- "data": {
- "ping_token": "hb_5c8f1e0a4d7b2396af10c53e6b8d24f7",
}
}Send a test ping
Records a manual ping against the monitor so an integrator can confirm the plumbing without waiting for the real job. The signal type is fixed to manual and the user agent to a fixed dashboard label — nothing is read from the request body, and the caller IP is recorded as the source. A manual ping is deliberately non-arming: it shows up in the ping log and the live feed but never arms the monitor and never moves the next expected ping, so it cannot mask a job that has actually stopped running.
Authorizations:
path Parameters
| id required | integer\d+ Example: 4127 Monitor id |
Responses
Response samples
- 200
- 401
- 403
- 404
{- "data": {
- "status": "ok"
}
}List the ping log for a heartbeat monitor
Newest-first ping log for one monitor, read from the ClickHouse ping history and built for live-feed polling: pass back the newest id you hold as since_id to fetch only what is new. That filter is INCLUSIVE, so that a second ping landing in the same second is never lost — which means the newest row you already hold comes back again and an incremental poller MUST de-duplicate on id. A ClickHouse failure degrades to an empty rows array rather than a 5xx, so an empty page is not proof that no pings arrived.
Authorizations:
path Parameters
| id required | integer\d+ Example: 4127 Monitor id |
query Parameters
| since_id | integer Default: 0 Example: since_id=41270001756100000 Return only pings at or after the time part of this encoded ping id. Default 0 (no lower bound); negative values are floored to 0. De-duplicate on id — the boundary row is returned again. |
| limit | integer Default: 50 Example: limit=50 Maximum rows to return. Default 50, clamped server-side to 1-200. |
| date_start | integer Default: 0 Example: date_start=1755500000 Unix timestamp lower bound, inclusive. Default 0 = unbounded; negative values floored to 0. |
| date_end | integer Default: 0 Example: date_end=1756100000 Unix timestamp upper bound, inclusive. Default 0 = unbounded; negative values floored to 0. |
Responses
Response samples
- 200
- 401
- 403
- 404
{- "data": [
- {
- "id": 41270001756100000,
- "time": 1756100000,
- "signal_type": "success",
- "ip": "203.0.113.42",
- "user_agent": "curl/8.4.0",
- "run_ms": 0,
- "late_seconds": 0
}
], - "metadata": {
- "latest_id": 41270001756100000,
- "monitor": {
- "last_ping_at": 1756100000,
- "expected_next_at": 1756186400,
- "armed": 1
}
}
}Get the detail-page summary for a heartbeat monitor
Everything the monitor detail page needs in one payload: the stat-strip numbers, the 12-hour pulse strip, per-bucket received counts, the in-window incidents and the monitor itself. Give an explicit from/to window in unix seconds, or omit both and pass days for a rolling window; the window is clamped to now and capped at 92 days, and a longer request is silently trimmed to the most recent 92 days rather than rejected. Availability is derived from clipped incident downtime over the whole window and does NOT subtract maintenance. The late_pings key is ALWAYS an empty array here — this endpoint is polled every three seconds while a ping is overdue and skips the dedicated late-ping read — while the pings_late counter is unaffected.
Authorizations:
path Parameters
| id required | integer\d+ Example: 4127 Monitor id |
query Parameters
| from | integer Default: 0 Example: from=1755500000 Window start as a unix timestamp. Default 0. When both from and to are 0 or less the rolling days window is used instead. An out-of-order or non-positive from is replaced by seven days before the window end. |
| to | integer Default: 0 Example: to=1756100000 Window end as a unix timestamp. Default 0. Clamped to now — a future value is pulled back to the current time. |
| days | integer Default: 7 Example: days=7 Rolling window size in days, used ONLY when neither from nor to is given: the window becomes the last days*86400 seconds. Default 7; floored to a minimum of 1. |
Responses
Response samples
- 200
- 401
- 403
- 404
{- "data": {
- "now": 1756100000,
- "window_start": 1755495200,
- "window_end": 1756100000,
- "window_seconds": 604800,
- "measured_start": 1755495200,
- "measured_seconds": 604800,
- "maintenance_seconds": 0,
- "paused_since": 0,
- "bucket_seconds": 3600,
- "availability": 99.87,
- "down_seconds": 780,
- "pings_total": 168,
- "pings_late": 2,
- "per_bucket": {
- "1756090800": 1,
- "1756094400": 1
}, - "pulse": [
- {
- "time": 1756096400,
- "signal_type": "success"
}
], - "incidents": [
- {
- "impact": { },
- "id": "90211",
- "site_id": "4127",
- "time_started": "1755900000",
- "time_ended": "1755900780",
- "down_report_sent": "1",
- "down_report_code": "0",
- "up_report_sent": "1",
- "up_report_code": "0",
- "error_code": "missed_ping",
- "error_type": "heartbeat",
- "reason": "No ping received — expected Aug 23, 01:18 (UTC), grace period of 2 minutes ran out.",
- "reason_full": "",
- "is_critical": "1",
- "is_trouble": "0",
- "servers": ""
}
], - "incidents_total": 3,
- "late_pings": [
- { }
], - "monitor": {
- "id": "4127",
- "user": "1",
- "name": "Nightly invoice export",
- "watches": "cron",
- "schedule_type": "cron",
- "interval_seconds": "300",
- "cron_expression": "0 3 * * *",
- "timezone": "0",
- "grace_seconds": "120",
- "late_threshold_seconds": "10",
- "runtime_limit_seconds": "0",
- "fail_alerts": "1",
- "ping_token": "hb_5c8f1e0a4d7b2396af10c53e6b8d24f7",
- "armed": "0",
- "last_ping_at": "1756100000",
- "last_ping_signal": "success",
- "last_ping_ip": "203.0.113.42",
- "last_start_at": "0",
- "expected_next_at": "1756186400",
- "missed_deadline": "1756186520",
- "status": "1",
- "last_error": "0",
- "last_check": "0",
- "next_check": "0",
- "incident_id": "0",
- "last_incident_at": "0",
- "is_critical": "0",
- "is_trouble": "0",
- "suspended": "0",
- "maintenance": "0",
- "paused": "0",
- "is_updated": "0",
- "report_down": "0",
- "report_up": "1",
- "escalation_policy_id": "0",
- "branding_id": "0",
- "notes": "",
- "added_date": "1755000000",
- "is_up": 1,
- "is_paused": 0,
- "is_maintenance": 0,
- "contacts": [
- {
- "id": "string",
- "is_team": "string",
- "is_user": "string",
- "is_operator": "string",
- "is_individual_contact": "string"
}
], - "groups": [
- "12"
]
}
}
}List incidents for a heartbeat monitor
Newest-first incident history for one monitor — missed pings, explicitly reported /fail runs and runtime overruns — in the formatted display shape shared by every monitor family per-monitor incident endpoint. Every time field here is a rendered STRING, not a timestamp. Window membership is half-open: an incident belongs to the window when it started before date_end AND was still open after date_start, so an incident that is still open always qualifies. metadata.total is the real uncapped count over the same window, independent of limit and offset, so it can drive pagination.
Authorizations:
path Parameters
| id required | integer\d+ Example: 4127 Monitor id |
query Parameters
| limit | integer Default: 50 Example: limit=50 Maximum incidents to return. Default 50, clamped server-side to 1-200. |
| offset | integer Default: 0 Example: offset=0 Rows to skip, for pagination. Default 0; negative values are floored to 0. |
| date_start | integer Default: 0 Example: date_start=1755500000 Unix timestamp: keep incidents that were still open after this instant. Default 0 = unbounded; negative values floored to 0. |
| date_end | integer Default: 0 Example: date_end=1756100000 Unix timestamp: keep incidents that started strictly before this instant. Default 0 = unbounded; negative values floored to 0. |
Responses
Response samples
- 200
- 401
- 403
- 404
{- "data": [
- {
- "error_id": 90211,
- "impact": { },
- "error_display": "The expected ping never arrived — the schedule plus grace period ran out.",
- "time_started": "August 23, 2026 01:20",
- "time_ended": "August 23, 2026 01:33",
- "date_ended": "13 minutes",
- "time_elapsed": "2 hours ago"
}
], - "metadata": {
- "total": 3,
- "monitor_id": 4127
}
}Delete a heartbeat incident
Permanently deletes one CLOSED incident record from the monitor history. The monitor currently open incident is refused with 409 incident_open so live alerting state cannot be erased out from under the missed-ping sweep — close it first by letting the job ping again. The lookup is bound to the monitor in the path, so an incident belonging to a different monitor answers 404 incident_not_found rather than being deleted.
Authorizations:
path Parameters
| id required | integer\d+ Example: 4127 Monitor id |
| incidentId required | integer\d+ Example: 90211 Incident id, as returned in error_id by GET /api/v2/heartbeat/{id}/incidents |
Responses
Response samples
- 200
- 401
- 403
- 404
- 409
{- "data": {
- "deleted": true
}
}List all blacklist monitors
Returns every blacklist monitor on the account, decorated exactly as the editor reads it but WITHOUT the contacts and groups keys — fetch a single monitor for those. The endpoint is unpaged: it reads no limit or offset, and metadata.total is simply the number of rows returned. Rows are ordered by name ascending, and an operator whose role is confined to monitor groups sees only the monitors inside that scope (an empty scope returns an empty list, not a 403).
Authorizations:
query Parameters
| search | string Example: search=example.com Case-insensitive substring filter applied to the monitor name and target. LIKE wildcards in the term are escaped, so "a_b" and "100%" match literally. Applied only when the key is present; there is no default. |
Responses
Response samples
- 200
- 401
- 403
{- "data": [
- {
- "id": 4102,
- "user": 1,
- "name": "Corporate mail",
- "target": "example.com",
- "is_ip": 0,
- "extra_ips": [
- "93.184.216.40"
], - "sweep_interval": 900,
- "verify_checks": 3,
- "watch_web_ip": 1,
- "tier_settings": {
- "major": {
- "enabled": true,
- "severity": "critical"
}, - "standard": {
- "enabled": true,
- "severity": "trouble"
}, - "info": {
- "enabled": true
}
}, - "lists_total": 0,
- "is_up": 1,
- "is_critical": 0,
- "is_trouble": 0,
- "is_paused": 0,
- "paused": 0,
- "is_maintenance": 0,
- "maintenance": 0,
- "suspended": 0,
- "status": 1,
- "active_listings": 0,
- "info_listings": 1,
- "check_errors": 0,
- "error_streak": 0,
- "freeze_reason": "",
- "freeze_at": 0,
- "last_probe_host": "london.uptimia.net",
- "web_ip": "93.184.216.34",
- "web_ipv6": "",
- "web_ips": "[\"93.184.216.34\"]",
- "web_gone_since": 0,
- "mail_host": "mail.example.com",
- "mail_ip": "93.184.216.44",
- "mail_ipv6": "",
- "mail_ips": "[\"93.184.216.44\"]",
- "mail_gone_since": 0,
- "last_check": 1756100000,
- "last_attempt": 1756100000,
- "last_error": 0,
- "next_check": 1756100900,
- "incident_id": 0,
- "is_updated": 0,
- "report_down": 0,
- "report_up": 1,
- "escalation_policy_id": 0,
- "branding_id": 0,
- "notes": "",
- "added_date": 1756000000
}
], - "metadata": {
- "total": 7
}
}Create a blacklist monitor
Creates a blacklist monitor and queues its baseline sweep immediately, so the first result lands within a minute rather than after a full sweep interval. Guards run in a fixed order — field validation, then the plan pool, then escalation policy, groups, branding and contact ownership, then the monitor-group scope rewrite — and nothing is written until all of them pass. The response is the whole created monitor read back through the same shape as GET /api/v2/blacklist/{id}, not a bare id. Note the recipient rule that catches integrators out: omitting contacts entirely binds every contact already on the account, but sending an empty list is an error.
Authorizations:
Request Body schema: application/json
| name required | string Monitor display name. Trimmed, then required to be non-empty and at most 255 characters (name_invalid otherwise). |
| target required | string What to watch: a registrable domain or a public IPv4 address. It is normalized before it is stored (scheme, userinfo, port, path, query, fragment, a leading "www." and a trailing root dot are stripped; an IDN is lowered to punycode) and the normalized form is what later reads return. IPv6 literals are refused because no registry zone answers on IPv6, and an IP target must be publicly routable — private and reserved ranges, CGNAT 100.64/10, 192.0.0/24, TEST-NET-1/2/3, 198.18/15 and multicast 224/4 all give target_private. |
| extra_ips | Array of strings Up to 5 dedicated sending IPv4 addresses swept alongside the target. Must be an array (extra_ips_invalid), at most 5 entries after de-duplication (extra_ips_too_many), each publicly routable IPv4 (extra_ips_invalid). On write the entries are trimmed and de-duplicated, any entry equal to the target is dropped, and the list is capped at 5. Omitting the field means an empty list. |
| sweep_interval | integer Enum: 900 1800 3600 Seconds between sweeps. Only 900, 1800 and 3600 are accepted; anything else is sweep_interval_invalid. Defaults to 900 when omitted. |
| verify_checks | integer Enum: 1 3 5 How many consecutive clean answers a listing needs before it counts as delisted. Only 1, 3 and 5 are accepted; anything else is verify_checks_invalid. Defaults to 3. |
| watch_web_ip | boolean Whether a domain target checks its website IP (the A record) on the IP lists. Turn it off for a site behind a CDN or on shared hosting, whose IP is listed for other customers' traffic; the domain lists and the mail IP stay watched. Ignored for an IP target. Defaults to true. |
object Per-impact-tier configuration. At least one tier must stay enabled or the request is refused with tier_settings_all_disabled. Unrecognised input is normalized against the defaults (major enabled/critical, standard enabled/trouble, info enabled) and an unrecognised severity falls back to that tier default. | |
| report_up | integer Send the recovery (delisting) notification. 0 disables it; any other value, and omitting the field, means 1. There is deliberately no report_down field in this family — a listing is already a delayed signal, so DOWN alerts are queued due immediately and report_down is stored as 0. |
| escalation_policy_id | integer Escalation policy the monitor alerts through; 0 (the default) alerts every recipient at once. A non-zero id must belong to the account or the request is refused with invalid_escalation_policy. |
| branding_id | integer Branding template used for this monitor notification emails; 0 (the default) is the standard theme. A non-zero id must belong to the account, and a non-numeric value is refused with invalid_branding before the ownership comparison. |
| notes | string Free-text operator note, plain text, trimmed and truncated to 500 characters. Shown on the incident page and included in alert emails and webhook payloads. Written only when the key is present. |
(Array of integers or objects) or string Alert recipients. Create-only semantics: an ABSENT or null key seeds the monitor with every contact already stored on the account, each bound as an individual contact, and the "no_changes" sentinel seeds the same way (a create has no stored list to preserve); an account that holds no contacts yet is seeded with nobody and the create still succeeds. A key that is present and EMPTY is refused instead — a monitor with no recipients alerts nobody. Every team, operator, user or contact referenced must belong to the account. | |
Array of integers or integer Monitor-group ids this monitor belongs to; every id must belong to the account. Written only when the key is present. For an operator whose role is confined to monitor groups the submitted list is rewritten so the new monitor always lands in one of their scope groups and can never be created invisible to its author; an unrestricted actor keeps the submitted ids byte for byte. |
Responses
Request samples
- Payload
{- "name": "Corporate mail",
- "target": "example.com",
- "extra_ips": [
- "93.184.216.40"
], - "sweep_interval": 900,
- "verify_checks": 3,
- "watch_web_ip": true,
- "tier_settings": {
- "major": {
- "enabled": true,
- "severity": "critical"
}, - "standard": {
- "enabled": true,
- "severity": "trouble"
}, - "info": {
- "enabled": true
}
}, - "report_up": 1,
- "escalation_policy_id": 0,
- "branding_id": 0,
- "notes": "Shared IP with the marketing mailer",
- "contacts": [
- 12
], - "groups": [
- 3
]
}Response samples
- 201
- 400
- 401
- 403
- 422
- 500
{- "data": {
- "id": 4102,
- "user": 1,
- "name": "Corporate mail",
- "target": "example.com",
- "is_ip": 0,
- "extra_ips": [
- "93.184.216.40"
], - "sweep_interval": 900,
- "verify_checks": 3,
- "watch_web_ip": 1,
- "tier_settings": {
- "major": {
- "enabled": true,
- "severity": "critical"
}, - "standard": {
- "enabled": true,
- "severity": "trouble"
}, - "info": {
- "enabled": true
}
}, - "lists_total": 0,
- "is_up": 1,
- "is_critical": 0,
- "is_trouble": 0,
- "is_paused": 0,
- "paused": 0,
- "is_maintenance": 0,
- "maintenance": 0,
- "suspended": 0,
- "status": 1,
- "active_listings": 0,
- "info_listings": 1,
- "check_errors": 0,
- "error_streak": 0,
- "freeze_reason": "",
- "freeze_at": 0,
- "last_probe_host": "london.uptimia.net",
- "web_ip": "93.184.216.34",
- "web_ipv6": "",
- "web_ips": "[\"93.184.216.34\"]",
- "web_gone_since": 0,
- "mail_host": "mail.example.com",
- "mail_ip": "93.184.216.44",
- "mail_ipv6": "",
- "mail_ips": "[\"93.184.216.44\"]",
- "mail_gone_since": 0,
- "last_check": 1756100000,
- "last_attempt": 1756100000,
- "last_error": 0,
- "next_check": 1756100900,
- "incident_id": 0,
- "is_updated": 0,
- "report_down": 0,
- "report_up": 1,
- "escalation_policy_id": 0,
- "branding_id": 0,
- "notes": "",
- "added_date": 1756000000,
- "contacts": [
- {
- "id": 12,
- "is_team": 0,
- "is_user": 0,
- "is_operator": 0,
- "is_individual_contact": 1
}
], - "groups": [
- 3
]
}
}Get a blacklist monitor
Returns one blacklist monitor in the shape the editor prefills from: the decorated row plus its contact bindings and the ids of the monitor groups it belongs to. There is no metadata key on this response. A monitor that does not exist, belongs to another account, or falls outside the acting operator monitor-group scope all answer with the same 404.
Authorizations:
path Parameters
| id required | integer\d+ Example: 4102 Blacklist monitor id |
Responses
Response samples
- 200
- 401
- 403
- 404
{- "data": {
- "id": 4102,
- "user": 1,
- "name": "Corporate mail",
- "target": "example.com",
- "is_ip": 0,
- "extra_ips": [
- "93.184.216.40"
], - "sweep_interval": 900,
- "verify_checks": 3,
- "watch_web_ip": 1,
- "tier_settings": {
- "major": {
- "enabled": true,
- "severity": "critical"
}, - "standard": {
- "enabled": true,
- "severity": "trouble"
}, - "info": {
- "enabled": true
}
}, - "lists_total": 0,
- "is_up": 1,
- "is_critical": 0,
- "is_trouble": 0,
- "is_paused": 0,
- "paused": 0,
- "is_maintenance": 0,
- "maintenance": 0,
- "suspended": 0,
- "status": 1,
- "active_listings": 0,
- "info_listings": 1,
- "check_errors": 0,
- "error_streak": 0,
- "freeze_reason": "",
- "freeze_at": 0,
- "last_probe_host": "london.uptimia.net",
- "web_ip": "93.184.216.34",
- "web_ipv6": "",
- "web_ips": "[\"93.184.216.34\"]",
- "web_gone_since": 0,
- "mail_host": "mail.example.com",
- "mail_ip": "93.184.216.44",
- "mail_ipv6": "",
- "mail_ips": "[\"93.184.216.44\"]",
- "mail_gone_since": 0,
- "last_check": 1756100000,
- "last_attempt": 1756100000,
- "last_error": 0,
- "next_check": 1756100900,
- "incident_id": 0,
- "is_updated": 0,
- "report_down": 0,
- "report_up": 1,
- "escalation_policy_id": 0,
- "branding_id": 0,
- "notes": "",
- "added_date": 1756000000,
- "contacts": [
- {
- "id": 12,
- "is_team": 0,
- "is_user": 0,
- "is_operator": 0,
- "is_individual_contact": 1
}
], - "groups": [
- 3
]
}
}Update a blacklist monitor (alias of PATCH)
Alias of PATCH /api/v2/blacklist/{id}, kept for client compatibility — one route declares both verbs and they resolve to the same handler. Despite the PUT verb the semantics are PARTIAL, not replace: omitted fields keep their stored values and no field is reset to a create default. Every field is optional and an omitted field keeps its stored value. is_paused may be sent on its own for a bare pause/resume, which answers with just the monitor id, or combined with configuration fields — in that case the whole request is validated before the pause is applied, so a 422 leaves the monitor completely untouched. Changing the watch configuration (target, dedicated IPs or tiers) pulls the next sweep forward to now so the change takes effect within a minute; changing the target additionally clears the discovered web and mail addresses and silently force-closes the listing episodes that belonged to the old target, recomputing the counters and closing the open incident without a recovery alert when nothing alertable is left.
Authorizations:
path Parameters
| id required | integer\d+ Example: 4102 Blacklist monitor id |
Request Body schema: application/json
boolean or integer Truthy pauses the monitor, falsy — including an explicit null — resumes it. Sent ALONE it short-circuits and answers {"data":{"id":…}} without touching anything else; sent alongside other fields it is applied only after every other guard has passed, so a 422 leaves the monitor completely untouched. Update-only: the create endpoint does not read it. | |
| name | string Monitor display name; trimmed, non-empty, at most 255 characters. Absent keeps the stored name. |
| target | string Domain or public IPv4 address to watch, with the same normalization and public-address rule as on create. Absent keeps the stored target. Changing it clears the discovered web IP, MX host, mail IP and the last raw sweep — the IPv6 and full-RRset copies are left for the next sweep to overwrite — and force-closes every active listing episode silently (no recovery alert), because those episodes belonged to the old target. |
| extra_ips | Array of strings Replaces the stored dedicated-IP list wholesale — it is not merged. Same rules as on create: an array of at most 5 publicly routable IPv4 addresses, de-duplicated, with any entry equal to the target dropped. Absent leaves the stored list unchanged. |
| sweep_interval | integer Enum: 900 1800 3600 Seconds between sweeps — 900, 1800 or 3600. Absent leaves it unchanged. |
| verify_checks | integer Enum: 1 3 5 Consecutive clean answers before a listing counts as delisted — 1, 3 or 5. Absent leaves it unchanged. |
| watch_web_ip | boolean Whether a domain target checks its website IP on the IP lists. Turning it off closes any open website-IP listing without a delisting alert. Ignored for an IP target. Absent leaves it unchanged. |
object Replaces the stored tier configuration wholesale. At least one tier must stay enabled or the request is refused with tier_settings_all_disabled. Unrecognised input is normalized against the defaults (major enabled/critical, standard enabled/trouble, info enabled) and an unrecognised severity falls back to that tier default. Disabling a tier silently closes that tier active listing episodes; a surviving listing severity may only rank up, so an unrelated edit never quietly downgrades a critical listing, and PBL answers are always forced back to the informational tier. | |
| report_up | integer Send the recovery (delisting) notification: 0 disables it, any other value enables it. Absent leaves the stored value in place. There is no report_down field in this family. |
| escalation_policy_id | integer Escalation policy to alert through; 0 alerts every recipient at once. A non-zero id must belong to the account. Absent leaves it unchanged. |
| branding_id | integer Branding template id; 0 is the standard theme. A non-zero id must belong to the account and a non-numeric value is refused. Absent leaves it unchanged. |
| notes | string Free-text operator note, trimmed and capped at 500 characters. Written only when the key is present, so an absent key preserves the stored note while an empty string clears it. |
(Array of integers or objects) or string Replaces the monitor alert recipients. Same entry shapes as on create, and "all" / "no_changes" are accepted sentinels. An absent key, or an explicit null, leaves the stored recipients alone — both the ownership guard and the write are skipped. Unlike create, an empty array here is rejected rather than auto-seeded. | |
Array of integers or integer Replaces the monitor group memberships; every id must belong to the account. Absent leaves them unchanged. For an operator whose role is confined to monitor groups the list is rewritten so their edits stay inside their scope groups while owner-assigned memberships outside that scope are preserved rather than orphaned. |
Responses
Request samples
- Payload
{- "is_paused": true,
- "name": "Corporate mail",
- "target": "example.com",
- "extra_ips": [
- "93.184.216.40"
], - "sweep_interval": 1800,
- "verify_checks": 3,
- "watch_web_ip": false,
- "tier_settings": {
- "major": {
- "enabled": true,
- "severity": "critical"
}, - "standard": {
- "enabled": true,
- "severity": "trouble"
}, - "info": {
- "enabled": true
}
}, - "report_up": 1,
- "escalation_policy_id": 0,
- "branding_id": 0,
- "notes": "Shared IP with the marketing mailer",
- "contacts": [
- 12
], - "groups": [
- 3
]
}Response samples
- 200
- 400
- 401
- 403
- 404
- 422
{- "data": {
- "id": 4102,
- "user": 1,
- "name": "Corporate mail",
- "target": "example.com",
- "is_ip": 0,
- "extra_ips": [
- "93.184.216.40"
], - "sweep_interval": 900,
- "verify_checks": 3,
- "watch_web_ip": 1,
- "tier_settings": {
- "major": {
- "enabled": true,
- "severity": "critical"
}, - "standard": {
- "enabled": true,
- "severity": "trouble"
}, - "info": {
- "enabled": true
}
}, - "lists_total": 0,
- "is_up": 1,
- "is_critical": 0,
- "is_trouble": 0,
- "is_paused": 0,
- "paused": 0,
- "is_maintenance": 0,
- "maintenance": 0,
- "suspended": 0,
- "status": 1,
- "active_listings": 0,
- "info_listings": 1,
- "check_errors": 0,
- "error_streak": 0,
- "freeze_reason": "",
- "freeze_at": 0,
- "last_probe_host": "london.uptimia.net",
- "web_ip": "93.184.216.34",
- "web_ipv6": "",
- "web_ips": "[\"93.184.216.34\"]",
- "web_gone_since": 0,
- "mail_host": "mail.example.com",
- "mail_ip": "93.184.216.44",
- "mail_ipv6": "",
- "mail_ips": "[\"93.184.216.44\"]",
- "mail_gone_since": 0,
- "last_check": 1756100000,
- "last_attempt": 1756100000,
- "last_error": 0,
- "next_check": 1756100900,
- "incident_id": 0,
- "is_updated": 0,
- "report_down": 0,
- "report_up": 1,
- "escalation_policy_id": 0,
- "branding_id": 0,
- "notes": "",
- "added_date": 1756000000,
- "contacts": [
- {
- "id": 12,
- "is_team": 0,
- "is_user": 0,
- "is_operator": 0,
- "is_individual_contact": 1
}
], - "groups": [
- 3
]
}
}Delete a blacklist monitor
Permanently deletes the monitor and everything hanging off it: the monitor row, its listing episodes, contact bindings, incidents, maintenance and pause audit rows, group memberships and the per-sweep history in ClickHouse. The deletion is recorded in the account activity log. This answers 200 with a body, not 204, and there is no undo.
Authorizations:
path Parameters
| id required | integer\d+ Example: 4102 Blacklist monitor id |
Responses
Response samples
- 200
- 401
- 403
- 404
{- "data": {
- "deleted": true
}
}Update a blacklist monitor
Partial update: every field is optional and an omitted field keeps its stored value. is_paused may be sent on its own for a bare pause/resume, which answers with just the monitor id, or combined with configuration fields — in that case the whole request is validated before the pause is applied, so a 422 leaves the monitor completely untouched. Changing the watch configuration (target, dedicated IPs or tiers) pulls the next sweep forward to now so the change takes effect within a minute; changing the target additionally clears the discovered web and mail addresses and silently force-closes the listing episodes that belonged to the old target, recomputing the counters and closing the open incident without a recovery alert when nothing alertable is left.
Authorizations:
path Parameters
| id required | integer\d+ Example: 4102 Blacklist monitor id |
Request Body schema: application/json
boolean or integer Truthy pauses the monitor, falsy — including an explicit null — resumes it. Sent ALONE it short-circuits and answers {"data":{"id":…}} without touching anything else; sent alongside other fields it is applied only after every other guard has passed, so a 422 leaves the monitor completely untouched. Update-only: the create endpoint does not read it. | |
| name | string Monitor display name; trimmed, non-empty, at most 255 characters. Absent keeps the stored name. |
| target | string Domain or public IPv4 address to watch, with the same normalization and public-address rule as on create. Absent keeps the stored target. Changing it clears the discovered web IP, MX host, mail IP and the last raw sweep — the IPv6 and full-RRset copies are left for the next sweep to overwrite — and force-closes every active listing episode silently (no recovery alert), because those episodes belonged to the old target. |
| extra_ips | Array of strings Replaces the stored dedicated-IP list wholesale — it is not merged. Same rules as on create: an array of at most 5 publicly routable IPv4 addresses, de-duplicated, with any entry equal to the target dropped. Absent leaves the stored list unchanged. |
| sweep_interval | integer Enum: 900 1800 3600 Seconds between sweeps — 900, 1800 or 3600. Absent leaves it unchanged. |
| verify_checks | integer Enum: 1 3 5 Consecutive clean answers before a listing counts as delisted — 1, 3 or 5. Absent leaves it unchanged. |
| watch_web_ip | boolean Whether a domain target checks its website IP on the IP lists. Turning it off closes any open website-IP listing without a delisting alert. Ignored for an IP target. Absent leaves it unchanged. |
object Replaces the stored tier configuration wholesale. At least one tier must stay enabled or the request is refused with tier_settings_all_disabled. Unrecognised input is normalized against the defaults (major enabled/critical, standard enabled/trouble, info enabled) and an unrecognised severity falls back to that tier default. Disabling a tier silently closes that tier active listing episodes; a surviving listing severity may only rank up, so an unrelated edit never quietly downgrades a critical listing, and PBL answers are always forced back to the informational tier. | |
| report_up | integer Send the recovery (delisting) notification: 0 disables it, any other value enables it. Absent leaves the stored value in place. There is no report_down field in this family. |
| escalation_policy_id | integer Escalation policy to alert through; 0 alerts every recipient at once. A non-zero id must belong to the account. Absent leaves it unchanged. |
| branding_id | integer Branding template id; 0 is the standard theme. A non-zero id must belong to the account and a non-numeric value is refused. Absent leaves it unchanged. |
| notes | string Free-text operator note, trimmed and capped at 500 characters. Written only when the key is present, so an absent key preserves the stored note while an empty string clears it. |
(Array of integers or objects) or string Replaces the monitor alert recipients. Same entry shapes as on create, and "all" / "no_changes" are accepted sentinels. An absent key, or an explicit null, leaves the stored recipients alone — both the ownership guard and the write are skipped. Unlike create, an empty array here is rejected rather than auto-seeded. | |
Array of integers or integer Replaces the monitor group memberships; every id must belong to the account. Absent leaves them unchanged. For an operator whose role is confined to monitor groups the list is rewritten so their edits stay inside their scope groups while owner-assigned memberships outside that scope are preserved rather than orphaned. |
Responses
Request samples
- Payload
{- "is_paused": true,
- "name": "Corporate mail",
- "target": "example.com",
- "extra_ips": [
- "93.184.216.40"
], - "sweep_interval": 1800,
- "verify_checks": 3,
- "watch_web_ip": false,
- "tier_settings": {
- "major": {
- "enabled": true,
- "severity": "critical"
}, - "standard": {
- "enabled": true,
- "severity": "trouble"
}, - "info": {
- "enabled": true
}
}, - "report_up": 1,
- "escalation_policy_id": 0,
- "branding_id": 0,
- "notes": "Shared IP with the marketing mailer",
- "contacts": [
- 12
], - "groups": [
- 3
]
}Response samples
- 200
- 400
- 401
- 403
- 404
- 422
{- "data": {
- "id": 4102,
- "user": 1,
- "name": "Corporate mail",
- "target": "example.com",
- "is_ip": 0,
- "extra_ips": [
- "93.184.216.40"
], - "sweep_interval": 900,
- "verify_checks": 3,
- "watch_web_ip": 1,
- "tier_settings": {
- "major": {
- "enabled": true,
- "severity": "critical"
}, - "standard": {
- "enabled": true,
- "severity": "trouble"
}, - "info": {
- "enabled": true
}
}, - "lists_total": 0,
- "is_up": 1,
- "is_critical": 0,
- "is_trouble": 0,
- "is_paused": 0,
- "paused": 0,
- "is_maintenance": 0,
- "maintenance": 0,
- "suspended": 0,
- "status": 1,
- "active_listings": 0,
- "info_listings": 1,
- "check_errors": 0,
- "error_streak": 0,
- "freeze_reason": "",
- "freeze_at": 0,
- "last_probe_host": "london.uptimia.net",
- "web_ip": "93.184.216.34",
- "web_ipv6": "",
- "web_ips": "[\"93.184.216.34\"]",
- "web_gone_since": 0,
- "mail_host": "mail.example.com",
- "mail_ip": "93.184.216.44",
- "mail_ipv6": "",
- "mail_ips": "[\"93.184.216.44\"]",
- "mail_gone_since": 0,
- "last_check": 1756100000,
- "last_attempt": 1756100000,
- "last_error": 0,
- "next_check": 1756100900,
- "incident_id": 0,
- "is_updated": 0,
- "report_down": 0,
- "report_up": 1,
- "escalation_policy_id": 0,
- "branding_id": 0,
- "notes": "",
- "added_date": 1756000000,
- "contacts": [
- {
- "id": 12,
- "is_team": 0,
- "is_user": 0,
- "is_operator": 0,
- "is_individual_contact": 1
}
], - "groups": [
- 3
]
}
}Detail-page summary
Returns the whole detail page in one payload: the decorated monitor, its listing episodes, the last sweep decoded answers, the newest incidents, the registry of zones the enabled tiers cover and the watched-resource slots. The control panel re-fetches this every 30 seconds, so the resolved listing history is capped at the 200 newest while active episodes are never truncated — truncating an episode that has been open for months would paint a listed cell clean while the same payload counters said listed. The monitor object here carries dqs_configured but no contacts or groups; the incidents are raw rows rather than the formatted shape the incidents endpoint returns.
Authorizations:
path Parameters
| id required | integer\d+ Example: 4102 Blacklist monitor id |
Responses
Response samples
- 200
- 401
- 403
- 404
{- "data": {
- "now": 1756100005,
- "monitor": {
- "id": 4102,
- "user": 1,
- "name": "Corporate mail",
- "target": "example.com",
- "is_ip": 0,
- "extra_ips": [
- "93.184.216.40"
], - "sweep_interval": 900,
- "verify_checks": 3,
- "watch_web_ip": 1,
- "tier_settings": {
- "major": {
- "enabled": true,
- "severity": "critical"
}, - "standard": {
- "enabled": true,
- "severity": "trouble"
}, - "info": {
- "enabled": true
}
}, - "lists_total": 0,
- "is_up": 1,
- "is_critical": 0,
- "is_trouble": 0,
- "is_paused": 0,
- "paused": 0,
- "is_maintenance": 0,
- "maintenance": 0,
- "suspended": 0,
- "status": 1,
- "active_listings": 0,
- "info_listings": 1,
- "check_errors": 0,
- "error_streak": 0,
- "freeze_reason": "",
- "freeze_at": 0,
- "last_probe_host": "london.uptimia.net",
- "web_ip": "93.184.216.34",
- "web_ipv6": "",
- "web_ips": "[\"93.184.216.34\"]",
- "web_gone_since": 0,
- "mail_host": "mail.example.com",
- "mail_ip": "93.184.216.44",
- "mail_ipv6": "",
- "mail_ips": "[\"93.184.216.44\"]",
- "mail_gone_since": 0,
- "last_check": 1756100000,
- "last_attempt": 1756100000,
- "last_error": 0,
- "next_check": 1756100900,
- "incident_id": 0,
- "is_updated": 0,
- "report_down": 0,
- "report_up": 1,
- "escalation_policy_id": 0,
- "branding_id": 0,
- "notes": "",
- "added_date": 1756000000,
- "dqs_configured": false
}, - "listings": [
- {
- "id": 0,
- "monitor": 0,
- "list_id": "zen",
- "list_name": "Spamhaus ZEN",
- "zone": "zen.spamhaus.org",
- "tier": "major",
- "resource": "mail",
- "resource_value": "93.184.216.44",
- "severity": "critical",
- "code": "127.0.0.3",
- "decoded": "CSS — automated: usually a compromised host or account",
- "txt": "string",
- "prev_clean_at": 0,
- "first_seen": 0,
- "last_seen": 0,
- "clean_streak": 0,
- "cleared_at": 0,
- "verified_at": 0,
- "resolved_at": 0,
- "status": "active",
- "close_reason": ""
}
], - "last_sweep": {
- "at": 0,
- "checks": [
- {
- "list_id": "zen",
- "list_name": "Spamhaus ZEN",
- "zone": "zen.spamhaus.org",
- "tier": "major",
- "resource": "mail",
- "resource_value": "93.184.216.44",
- "query": "44.216.184.93.zen.spamhaus.org",
- "state": "clean",
- "code": "",
- "txt": "",
- "decoded": "",
- "whitelisted": false
}
], - "errors": 0
}, - "incidents": [
- {
- "id": 0,
- "site_id": 0,
- "time_started": 0,
- "time_ended": 0,
- "down_report_sent": 0,
- "down_report_code": 0,
- "up_report_sent": 0,
- "up_report_code": 0,
- "close_reason": "string",
- "error_code": "string",
- "error_type": "string",
- "reason": "string",
- "reason_full": "string",
- "is_critical": 0,
- "is_trouble": 0,
- "servers": "string"
}
], - "registry": [
- {
- "id": "zen",
- "name": "Spamhaus ZEN",
- "zone": "zen.spamhaus.org",
- "kind": "ip",
- "tier": "major"
}
], - "resources": [
- {
- "key": "mail",
- "label": "mail IP",
- "value": "93.184.216.44",
- "kind": "ip",
- "source": "mail.example.com — from the MX record",
- "all_values": [
- "string"
], - "unwatched_count": 0
}
]
}
}Re-check all lists now
Queues an on-demand sweep by pulling the monitor next_check forward to now; the per-minute sweep cron picks it up on its next tick, so results land within a minute rather than immediately in this response. The request body is ignored entirely. A monitor the sweep would skip — suspended by the plan pool, paused, or in maintenance — is refused with a 409 rather than being told a recheck was queued, and the endpoint is rate limited to 10 rechecks per minute per account because each one fans a full registry sweep out across the probe fleet.
Authorizations:
path Parameters
| id required | integer\d+ Example: 4102 Blacklist monitor id |
Responses
Response samples
- 200
- 401
- 403
- 404
- 409
- 429
{- "data": {
- "queued": true,
- "next_check": 1756100005
}
}List incidents for a blacklist monitor
Newest-first incident history — listing alerts and their recoveries — formatted into the display shape the shared incidents card consumes, so every value is a string except error_id. Paging is clamped by the family rather than the caller: limit is forced into 1..200 and defaults to 50, so a huge limit cannot return everything and a negative one cannot produce an invalid query. The date window is half-open and overlap-based: an incident counts when it started before date_end AND was still open after date_start, and a zero bound means unbounded on that side.
Authorizations:
path Parameters
| id required | integer\d+ Example: 4102 Blacklist monitor id |
query Parameters
| limit | integer [ 1 .. 200 ] Default: 50 Example: limit=50 Page size. Read only when the key is present; clamped to 1..200 with a default of 50. |
| offset | integer Default: 0 Example: offset=0 Rows to skip, floored at 0. |
| date_start | integer Default: 0 Example: date_start=1756000000 Unix timestamp lower bound, floored at 0. An incident is in the window when it was still open after this instant — time_ended is 0 (ongoing) or greater than date_start. 0, the default, means unbounded. |
| date_end | integer Default: 0 Example: date_end=1756200000 Unix timestamp upper bound, floored at 0. An incident is in the window when time_started is less than date_end. 0, the default, means unbounded. |
Responses
Response samples
- 200
- 401
- 403
- 404
{- "data": [
- {
- "error_id": 90211,
- "impact": { },
- "error_display": "Listed on Spamhaus ZEN",
- "time_started": "August 25, 2026 14:03",
- "time_ended": "August 25, 2026 16:17",
- "date_ended": "2 hours 14 minutes",
- "time_elapsed": "2 hours ago"
}
], - "metadata": {
- "total": 12,
- "monitor_id": 4102
}
}Per-sweep check log
Newest-first page of the append-only per-sweep check log: one row per sweep with its timestamp, fan-out duration, queried-zone count and what it found. Paging is clamped by the family, not the caller — limit is forced into 1..100 and defaults to 20 — and metadata.total is the real number of sweeps stored, independent of the page. Rows are kept for the account plan retention, like every other family check history, so old timestamps eventually disappear.
Authorizations:
path Parameters
| id required | integer\d+ Example: 4102 Blacklist monitor id |
query Parameters
| limit | integer [ 1 .. 100 ] Default: 20 Example: limit=20 Page size. Read only when the key is present; clamped to 1..100 with a default of 20. |
| offset | integer Default: 0 Example: offset=0 Rows to skip, floored at 0. |
Responses
Response samples
- 200
- 401
- 403
- 404
{- "data": [
- {
- "time": 1756100000,
- "duration": 842,
- "status": 1,
- "lists_checked": 30,
- "listings_active": 0,
- "listings_info": 1,
- "errors": 0,
- "freeze_reason": ""
}
], - "metadata": {
- "total": 2880,
- "monitor_id": 4102
}
}One sweep in full
Returns a single sweep counters plus the per-list answers behind them: which zone, which watched address, what it replied and what that decodes to. Only answers that carry information are stored, so clean answers are never listed individually — they are the difference between lists_checked and the cells returned, exposed as clean. cells_source states the provenance of the per-list view, because a sweep older than per-list storage can only have its listings rebuilt from the listing-episode windows and can never name the zones that errored. The {time} segment must be the exact time value from the sweep log.
Authorizations:
path Parameters
| id required | integer\d+ Example: 4102 Blacklist monitor id |
| time required | integer\d+ Example: 1756100000 Exact unix timestamp of the sweep, as returned in the |
Responses
Response samples
- 200
- 401
- 403
- 404
{- "data": {
- "time": 1756100000,
- "duration": 842,
- "status": 1,
- "server_location": 73,
- "location": "London, United Kingdom",
- "flag": "uk",
- "lists_checked": 30,
- "listings_active": 1,
- "listings_info": 0,
- "errors": 0,
- "freeze_reason": "",
- "clean": 29,
- "cells_source": "log",
- "cells": [
- {
- "list_id": "zen",
- "list_name": "Spamhaus ZEN",
- "zone": "zen.spamhaus.org",
- "tier": "major",
- "resource": "mail",
- "resource_value": "93.184.216.44",
- "state": "listed",
- "code": "127.0.0.3",
- "decoded": "CSS — automated: usually a compromised host or account",
- "txt": "string",
- "whitelisted": false
}
]
}, - "metadata": {
- "monitor_id": 4102
}
}Delete a blacklist incident
Deletes one CLOSED incident record from the monitor history. The currently open incident cannot be deleted and answers 409; the check runs twice, once against the monitor incident pointer and once against the stored end timestamp, so a race cannot slip an open incident through. Deleting an incident removes the record only — it does not touch the listing episodes or the sweep log behind it.
Authorizations:
path Parameters
| id required | integer\d+ Example: 4102 Blacklist monitor id |
| incidentId required | integer\d+ Example: 90211 Incident id, as returned in the |
Responses
Response samples
- 200
- 401
- 403
- 404
- 409
{- "data": {
- "deleted": true
}
}Pause a single monitor
Pauses a single monitor by type and ID. Creates a pause history entry.
Authorizations:
path Parameters
| type required | string Enum: "uptime" "ssl" "domain" "virus" "fpl" "transaction" "rum" "heartbeat" "blacklist" "dns" "api" Monitor type |
| id required | integer\d+ Monitor ID |
Responses
Response samples
- 200
{- "status": "success"
}Resume a single monitor
Resumes a single paused monitor by type and ID. Closes the pause history entry.
Authorizations:
path Parameters
| type required | string Enum: "uptime" "ssl" "domain" "virus" "fpl" "transaction" "rum" "heartbeat" "blacklist" "dns" "api" Monitor type |
| id required | integer\d+ Monitor ID |
Responses
Response samples
- 200
{- "status": "success"
}Copy a monitor
Creates a copy of an existing monitor. Currently only supported for uptime monitors.
Authorizations:
path Parameters
| type required | string Value: "uptime" Monitor type (only "uptime" supported) |
| id required | integer\d+ Monitor ID to copy |
Responses
Response samples
- 201
{- "data": {
- "id": 0
}
}List monitor groups
Returns every monitor group on your account — including groups that currently hold no monitors — with the IDs the monitor endpoints' groups field expects. Ordered by id ascending (creation order). Results are not paginated and take no query parameters. Group-scoped operators only see the groups their access is limited to, and metadata.total counts what was returned after that filter, not the account total.
Authorizations:
Responses
Response samples
- 200
- 401
{- "data": [
- {
- "id": 18,
- "name": "Production",
- "color": "#e66465",
- "monitors_count": 7
}
], - "metadata": {
- "total": 4
}
}Create a monitor group
Creates a monitor group. Both name and color are required. The name is stored with HTML tags stripped and surrounding whitespace removed, so what you send back on a later read may differ from what you sent. Group-scoped operators cannot create groups: a new group is nobody's scope until the owner grants it, so it would vanish from the creator's own list. There is no per-account group limit. A form-encoded body (application/x-www-form-urlencoded) is accepted as well as JSON; when both are present the form fields win. The response carries the new ID only — read it back with GET /api/v2/groups if you need the stored name and color.
Authorizations:
Request Body schema: application/json
| name required | string <= 100 characters Group name. The 100 limit is measured in bytes, so a name of multi-byte characters fits fewer of them, and it is checked before tags are stripped. |
| color required | string^#[0-9A-Fa-f]{6}$ Six-digit HEX color with a leading |
Responses
Request samples
- Payload
{- "name": "Production",
- "color": "#e66465"
}Response samples
- 201
- 400
- 401
- 403
{- "data": {
- "id": 18
}
}Delete a monitor group
Deletes a monitor group and removes its monitor associations (the monitors themselves are kept and keep running). Group-mode maintenance windows stop covering it, and any monitor that was in maintenance only because of this group leaves maintenance immediately — it resumes checks and alerts within the current occurrence. The deletion is not reversible and takes no request body.
Authorizations:
path Parameters
| id required | integer\d+ Example: 18 Group ID |
Responses
Response samples
- 200
- 401
- 404
{- "status": "success"
}Update a monitor group
Partially updates a monitor group: only the supplied fields change, and an omitted field keeps its stored value. Omit a field to keep it — sending it as null is rejected with 400 rather than treated as "unchanged", and neither field can be cleared. Both fields are validated by the same rules as create, and the name is stripped of HTML tags and trimmed before it is stored. Groups outside a group-scoped operator's access respond 404, indistinguishable from ones that do not exist. A form-encoded body is accepted as well as JSON; when both are present the form fields win. Sending an empty body is a no-op that still answers 200.
Authorizations:
path Parameters
| id required | integer\d+ Example: 18 Group ID |
Request Body schema: application/json
| name | string <= 100 characters New group name. The 100 limit is measured in bytes. |
| color | string^#[0-9A-Fa-f]{6}$ New six-digit HEX color with a leading |
Responses
Request samples
- Payload
{- "name": "Production EU",
- "color": "#5454e6"
}Response samples
- 200
- 400
- 401
- 404
{- "status": "success"
}Add or remove monitors in a group
Adds and removes many monitors to and from one group in a single transaction. Adding is additive: a monitor keeps every other group it belongs to. A monitor that is already a member (in add) or not a member (in remove) is a no-op and is not reported. Every referenced monitor must exist on your account; one unknown ID answers 404 and nothing is written. Group-scoped operators can only change their own groups and their own monitors: a monitor outside their access answers the same 404. The response lists in skipped what they could not change: last_scoped_group (removing it would leave the monitor in none of their groups, so it would disappear from their view). type takes the public monitor type names (uptime, speed, transaction, rum, ssl, domain, virus, server, heartbeat, blacklist, dns, api). Each list holds at most 1000 entries, and a monitor cannot appear in both.
Authorizations:
path Parameters
| id required | integer\d+ Example: 18 Group ID |
Request Body schema: application/json
Array of objects <= 1000 items Monitors to add to the group | |
Array of objects <= 1000 items Monitors to remove from the group |
Responses
Request samples
- Payload
{- "add": [
- {
- "type": "uptime",
- "id": 4182
}
], - "remove": [
- {
- "type": "uptime",
- "id": 3920
}
]
}Response samples
- 200
- 401
- 404
- 422
{- "added": 52,
- "removed": 3,
- "skipped": [
- {
- "type": "uptime",
- "id": 3920,
- "reason": "last_scoped_group"
}
]
}List monitor templates
Returns the authenticated account's saved monitor templates, newest first (id descending), each with its decoded create-payload settings. Unlike every monitor list in this API the response has NO metadata envelope: the body is {"data": [...]} and nothing else, so there is no metadata.total to read — use the length of data. The list is never paginated and accepts no query parameters. Templates saved from a monitor family that has since been retired (transaction) are filtered out of this list and can never reappear in it, yet they still count towards the 100-template account cap — an account can therefore be refused a new template while showing fewer than 100 here. They remain reachable by id on the update, delete and used endpoints, so a hidden row can still be deleted to free its slot. A group-scoped operator only receives templates whose source monitor is inside their scope; the check fails closed, so a template with no resolvable source monitor (source_monitor_id 0, or a monitor that has since been deleted) is invisible to them.
Authorizations:
Responses
Response samples
- 200
- 401
{- "data": [
- {
- "id": 41,
- "name": "Production HTTPS check",
- "description": "Five-minute uptime check with the standard on-call contacts",
- "monitor_type": "uptime",
- "settings": {
- "project_name": "example.com — landing",
- "monit_type": 2,
- "monitoring_interval_slider": 5,
- "contacts": [
- 12
]
}, - "source_monitor_id": 8241,
- "source_monitor_name": "example.com — landing",
- "times_used": 3,
- "last_used_at": 1756108800,
- "created_at": 1755936000,
- "updated_at": 1755936000
}
]
}Save a monitor as a template
Stores a named snapshot of a monitor's create payload. monitor_type is the monitor family key; settings is the JSON body that family's create endpoint accepts. The response carries the new ID only — the created resource is not echoed back and no Location header is set; read it back with GET /api/v2/monitor-templates. Creating a monitor FROM a template happens client-side: merge the new name and URL/IP over settings and post the result to POST /api/v2/{monitor_type}, then call POST /api/v2/monitor-templates/{id}/used. source_monitor_id is optional here but load-bearing for team accounts: it is the only key group-scope enforcement reads, and it fails closed, so a template saved without it (or pointing at a monitor that is later deleted) is invisible to every group-scoped operator — silently absent from their list (200, one entry shorter) and answered 404 by the update, delete and used endpoints. An account may hold at most 100 templates, counting templates of retired families that the list endpoint no longer returns; those rows are still deletable by id.
Authorizations:
Request Body schema: application/json
| name required | string <= 190 characters Required. 1..190 characters, unique on the account. The value is trimmed before both the blank check and the uniqueness check, so |
| description | string or null <= 255 characters Optional free-text note, measured after trimming and stored trimmed. Over 255 characters is rejected rather than truncated. Omitted, |
| monitor_type required | string Enum: "uptime" "ssl" "fpl" "domain" "virus" "rum" "heartbeat" "blacklist" "dns" "api" Required. The monitor family this snapshot belongs to; case-insensitive and trimmed. |
required | object Required. The create payload to snapshot — the exact JSON body |
| source_monitor_id | integer Default: 0 Optional. ID of the monitor this payload was read from. Never rejected: the value is cast to an integer and floored at 0 (a negative id and a non-numeric string both store 0), and an ID that does not name one of your monitors is stored as sent. It is not checked against your account here, only when group scope is evaluated on a later read — where 0 and unresolvable IDs both hide the template from group-scoped operators (see the endpoint description). |
| source_monitor_name | string <= 190 characters Optional. Display name of the source monitor, stored as a snapshot and never kept in sync. Trimmed and silently truncated to 190 characters rather than rejected. |
Responses
Request samples
- Payload
{- "name": "Production HTTPS check",
- "description": "Five-minute uptime check with the standard on-call contacts",
- "monitor_type": "uptime",
- "settings": {
- "project_name": "example.com — landing",
- "monit_type": 2,
- "monitoring_interval_slider": 5,
- "contacts": [
- 12
]
}, - "source_monitor_id": 8241,
- "source_monitor_name": "example.com — landing"
}Response samples
- 201
- 400
- 401
{- "data": {
- "id": 41
}
}Delete a monitor template
Deletes an owned template and frees one slot against the 100-template account cap. Monitors already created from it are untouched — templates are snapshots, not live configuration. Responds 404 when the template does not exist, is not owned by the account, or is outside a group-scoped operator's scope; the three are deliberately indistinguishable. The success body is {"status":"ok"} — note the literal ok, NOT the success that the update and used endpoints in this same tag return. A shared status === "success" check will read every successful delete as a failure.
Authorizations:
path Parameters
| id required | integer\d+ Example: 41 Template ID |
Responses
Response samples
- 200
- 401
- 404
{- "status": "ok"
}Update a monitor template
Partially updates an owned template's name and/or description. Responds 404 when the template does not exist, is not owned by the account, or is outside a group-scoped operator's scope — the three are deliberately indistinguishable. settings and monitor_type are immutable (templates are snapshots) and are IGNORED rather than rejected: sending them returns 200 and changes nothing, so a client that expects an error for an unsupported edit will never see one. To change a snapshot, create a new template and delete the old one. Presence, not value, decides what is written. A key that is absent is preserved; a key that is present is applied. {"description": null} therefore CLEARS the description (any non-string value does the same, storing an empty string), while omitting description leaves it untouched. {"name": null} is not the equivalent shortcut — it is rejected with invalid_template_name. A request that carries no recognised key — an empty body, or only settings/monitor_type/unknown keys — returns 200 {"status":"success"} having written nothing, not even updated_at, and records no entry in the account activity log. Success here is indistinguishable from a silent no-op, so verify the change with GET /api/v2/monitor-templates rather than trusting the status.
Authorizations:
path Parameters
| id required | integer\d+ Example: 41 Template ID |
Request Body schema: application/json
| name | string <= 190 characters New name: 1..190 characters, unique on the account (the template's own row is excluded from the uniqueness check, so re-sending the current name is allowed). Trimmed before both the blank and the uniqueness check. Markup-only names are rejected. Omit to keep the current name; |
| description | string or null <= 255 characters New note, up to 255 characters measured after trimming. Omit to keep the current description; send |
Responses
Request samples
- Payload
{- "name": "Production HTTPS check",
- "description": "Five-minute uptime check with the standard on-call contacts"
}Response samples
- 200
- 400
- 401
- 404
{- "status": "success"
}Record a template use
Increments the template's times_used counter by one and stamps last_used_at with the current time. Call it after a monitor has been successfully created from the template through POST /api/v2/{monitor_type}; the control panel does exactly this. Bookkeeping only — it creates nothing, changes no monitor, and does not move updated_at. It takes no request body, and any body sent is ignored. It is not idempotent: each call adds one, so a retried call double-counts. The response does not return the counter it just incremented; read times_used back from GET /api/v2/monitor-templates. Responds 404 when the template does not exist, is not owned by the account, or is outside a group-scoped operator's scope.
Authorizations:
path Parameters
| id required | integer\d+ Example: 41 Template ID |
Responses
Response samples
- 200
- 401
- 404
{- "status": "success"
}List monitoring locations (probes) grouped by region
Returns the full catalogue of monitoring checkpoint probes available to the account, organised into geographic regions; each probe lists its ID, city, country, ISO country code and IP. Use these probe IDs when creating or scoping monitors to choose which locations run the checks.
Authorizations:
Responses
Response samples
- 200
- 401
{- "regions": [
- {
- "id": "europe",
- "name": "Europe",
- "probes": [
- {
- "id": 1,
- "city": "Frankfurt",
- "country": "Germany",
- "countryCode": "DE",
- "ip": "203.0.113.10",
- "enabled": true
}
]
}
]
}Reusable, named sets of monitoring locations that can be applied to monitors in bulk.
List location profiles
Returns the authenticated account's saved location profiles together with the seven code-defined built-in profiles (Europe, North America, Asia, Middle East & Africa, South America, Oceania, EU/GDPR). Every profile carries its RESOLVED probe-id list, not its stored rule alone.
The payload is not wrapped in data: saved profiles arrive under profiles (ordered by id ascending) and built-ins under builtins. The two lists carry the same fields but different id types — a saved profile's id is an integer, a built-in's id is the string builtin:<key>. Both forms are accepted by POST /location-profiles/{id}/apply and by the location_profile field on the monitor write endpoints.
in_use_count is a state match, not a provenance record: it counts the account's monitors whose current probes column resolves to exactly this profile's probe set, across all six location-capable families (uptime, speed, ssl, transaction, api, dns). A monitor hand-picked to the same probes is therefore counted, and a monitor the profile really was applied to stops being counted once its probes are edited. Built-ins always report 0. A profile of kind all — and any profile whose selection resolves to nothing — shares the "all probes" signature, so it reports the count of every default-location monitor on the account.
rule is an object only on kind rule profiles; on static and all profiles the empty rule serialises as an empty array [].
Authorizations:
Responses
Response samples
- 200
- 401
{- "profiles": [
- {
- "id": 42,
- "name": "EU core",
- "kind": "static",
- "color": "#1d4ed8",
- "probes": [
- 4,
- 5,
- 11,
- 12,
- 14,
- 15
], - "rule": {
- "regions": [
- "europe"
], - "countries": [
- "DE",
- "FR"
]
}, - "is_builtin": false,
- "in_use_count": 3
}
], - "builtins": [
- {
- "id": "builtin:eu-gdpr",
- "name": "EU / GDPR",
- "kind": "rule",
- "color": "#4338ca",
- "probes": [
- 4,
- 5,
- 11,
- 12,
- 13,
- 14,
- 15
], - "rule": {
- "regions": [
- "northamerica"
], - "countries": [
- "string"
]
}, - "is_builtin": true,
- "in_use_count": 0
}
]
}Create a location profile
Creates a profile owned by the authenticated account. name is the only required field; kind defaults to static.
kind static takes probes — a list of probe ids. kind rule takes rule — regions and/or countries, matched with OR (a probe is selected if its region is listed or its country is listed), so an empty rule selects nothing. kind all needs neither and means every probe. An unrecognised kind is silently coerced to static rather than rejected.
A non-all profile must resolve to at least 6 probes or the request is rejected with too_few_locations. That gate runs against the ids as submitted; storage then intersects a static list with the live probe catalogue and drops ids that are not real probes, so a request made entirely of non-existent ids passes the gate, is created, and resolves to an empty list afterwards. Send ids taken from GET /api/v2/locations.
Values are normalised on write: name is trimmed and has markup stripped, region ids are lower-cased, country codes upper-cased, probe ids de-duplicated and sorted ascending, and a color that is not a six-digit HEX string is stored as empty. probes is only persisted for kind static and rule only for kind rule — the other columns are cleared.
Returns the new id only; call GET /location-profiles to read the resolved profile back.
Authorizations:
Request Body schema: application/json
| name required | string <= 100 characters Profile name, 1..100 characters. A name that is blank, or that consists only of markup, is rejected. |
| kind | string Default: "static" Enum: "static" "rule" "all" How the probe list is derived. Anything outside the enum is coerced to |
| probes | Array of integers Probe ids for kind |
object Selection rule for kind | |
| color | string Six-digit HEX colour used by the control panel. Anything else is stored as an empty string. |
Responses
Request samples
- Payload
{- "name": "EU core",
- "kind": "static",
- "probes": [
- 4,
- 5,
- 11,
- 12,
- 14,
- 15
], - "rule": {
- "regions": [
- "europe"
], - "countries": [
- "DE",
- "FR",
- "NL"
]
}, - "color": "#1d4ed8"
}Response samples
- 201
- 400
- 401
{- "data": {
- "id": 42
}
}Delete a location profile
Deletes a profile owned by the authenticated account and reaps the provenance links that recorded which monitors it was applied to. Built-ins cannot be deleted.
Monitors are not changed: each keeps the probe set it is currently checking from, it simply stops being attributed to a profile. A profile that does not exist, or belongs to another account, responds 404.
Note the success literal: this operation returns status: "ok", while create and update return status: "success".
Authorizations:
path Parameters
| id required | integer\d+ Example: 42 Saved profile id. Built-in ids are not accepted here. |
Responses
Response samples
- 200
- 401
- 404
{- "status": "ok"
}Update a location profile
Partially updates a profile owned by the authenticated account: only the keys present in the body are written, everything else keeps its stored value. Built-ins cannot be updated. A profile that does not exist, or belongs to another account, responds 404.
The resulting (kind, probes, rule) triplet is re-validated as a whole, so the minimum-6 rule is enforced against what would actually be stored — changing kind alone can therefore fail. Supplying kind also recomputes the stored probes and rule columns from the existing values under the new kind (switching to all clears both; switching to rule clears probes).
Editing the probe set re-points live monitors. A saved profile is a live template: when the resolved probe set changes, the new set is written into the probes column of every monitor on the account — across all six location-capable families — whose current probe set matches the profile's previous resolved set. That is a state match, not a provenance match, so a monitor hand-picked to those same probes is rewritten too even though this profile was never applied to it. The propagation is skipped when the previous set was the "all probes" sentinel. The response reports no count of what was rewritten. DNS monitors are updated by raw column write on this path, so they do not get the rotation-cursor and sweep-lock reset that the apply endpoint performs.
probes and rule are only honoured as arrays. A value that is present but is neither an array nor null (a string, a number, false) clears the stored column even though validation ran against the previous value; an explicit null is treated as "not supplied" and keeps the stored value.
Authorizations:
path Parameters
| id required | integer\d+ Example: 42 Saved profile id. Built-in ids are not accepted here. |
Request Body schema: application/json
| name | string <= 100 characters New name, 1..100 characters. Trimmed and stripped of markup on write; blank or markup-only is rejected. |
| kind | string Enum: "static" "rule" "all" New kind. Anything outside the enum is coerced to |
| probes | Array of integers Full replacement probe-id list for kind |
object Full replacement rule for kind | |
| color | string Six-digit HEX colour. Anything else is stored as an empty string. Not validated by this operation — only normalised. |
Responses
Request samples
- Payload
{- "name": "EU core (GDPR)",
- "kind": "rule",
- "probes": [
- 4,
- 5,
- 11,
- 12,
- 14,
- 15
], - "rule": {
- "regions": [
- "europe"
], - "countries": [
- "DE",
- "FR",
- "NL"
]
}, - "color": "#4338ca"
}Response samples
- 200
- 400
- 401
- 404
{- "status": "success"
}Apply a location profile to monitors
Resolves the profile — a saved one by numeric id, or a built-in via builtin:<key> — to its probe list, enforces the minimum-6 rule, and writes that selection into each listed monitor's probes column. Every monitor is ownership-checked, and each one also gets a provenance link so the monitor editor can show which profile was applied.
For uptime, speed, ssl, transaction and api monitors the write is byte-identical to a manual hand-pick (kind all stores the "all probes" sentinel; otherwise a sorted JSON integer list). DNS is not identical: it is routed through the DNS family manager, which additionally resets the vantage rotation cursor, pulls the next sweep forward, drops any sweep lease held against the old pool, and applies a second, stricter floor counting only DNS-capable probes. A profile that cleared the profile-level minimum can therefore still fail for its DNS targets alone.
Partial failure is reported inside a 200. The response always carries one result entry per submitted monitor, in request order, and the HTTP status is 200 even when every entry failed — including the whole-profile failure where the profile itself resolves to fewer than 6 probes, in which case every entry reports too_few_locations and nothing is written. Inspect data.results[].ok; do not infer success from the status code.
Re-applying a profile a monitor already uses is a success, not a no-op failure.
Authorizations:
path Parameters
| id required | string^(\d+|builtin:[a-z-]+)$ Example: builtin:eu-gdpr A saved profile id ( |
Request Body schema: application/json
required | Array of objects [ 1 .. 500 ] items Targets to rebind. At least one, at most 500 per request. | ||||
Array ([ 1 .. 500 ] items)
| |||||
Responses
Request samples
- Payload
{- "monitors": [
- {
- "id": 1204,
- "type": "uptime"
}
]
}Response samples
- 200
- 400
- 401
- 404
{- "data": {
- "results": [
- {
- "id": 1204,
- "type": "uptime",
- "ok": true,
- "error": "monitor_not_found"
}
]
}
}List active incidents
Returns a list of currently active (ongoing) incidents across all monitor types. Supports filtering by groups or individual monitors. Monitors in a running maintenance window are never active incidents; the ungrouped ones whose down or degraded state a window is holding are listed separately in maintenance[] (same account, scope and filters), and they count neither in data nor in metadata.total.
Authorizations:
query Parameters
| all | boolean Default: true Show all monitors (default true) |
| groups[] | Array of integers Filter by group IDs |
| monitors[uptime][] | Array of integers Filter by specific uptime monitor IDs |
| monitors[ssl][] | Array of integers Filter by specific SSL monitor IDs |
| monitors[domain][] | Array of integers Filter by specific domain monitor IDs |
| monitors[fpl][] | Array of integers Filter by specific speed (full page load) monitor IDs. Note: the type key for speed monitors is "fpl". |
| monitors[transaction][] | Array of integers Filter by specific transaction monitor IDs |
| monitors[virus][] | Array of integers Filter by specific virus monitor IDs |
| monitors[rum][] | Array of integers Filter by specific RUM monitor IDs |
| monitors[server][] | Array of integers Filter by specific server monitor IDs |
| monitors[heartbeat][] | Array of integers Filter by specific heartbeat monitor IDs |
Responses
Response samples
- 200
- 401
{- "data": [
- {
- "id": 0,
- "monitor_id": 0,
- "monitor_name": "string",
- "monitor_type": "string",
- "error": "string",
- "error_tooltip": "string",
- "time_started": "string",
- "time_ended": "string",
- "down_since": "string",
- "is_critical": true,
- "is_trouble": true,
- "path": "string",
- "alerts_sent": 0,
- "group_id": 0,
- "manual": true,
- "manual_incident": { },
- "closed_by_manual": true,
- "closed_by_manual_incident": { },
- "impact": { }
}
], - "maintenance": [
- {
- "monitor_id": 0,
- "monitor_name": "string",
- "monitor_type": "uptime",
- "path": "/cp/monitors/uptime/512827",
- "incident_id": 0,
- "incident_started_unix": 0,
- "incident_closed_unix": 0,
- "state": "down",
- "is_critical": true,
- "error": "string",
- "error_tooltip": "string",
- "maintenance_id": 0,
- "maintenance_name": "string",
- "since_unix": 0,
- "ends_unix": 0
}
], - "metadata": {
- "total": 0
}
}List incident history
Returns a paginated history of resolved and ongoing incidents across all monitor types within the specified date range, newest first. With pin_ongoing=1, every ongoing incident is listed first on page 1 and page and limit page the resolved incidents after them.
Authorizations:
query Parameters
| date_start | string Start date: Unix timestamp, or YYYYMMDD read as that day's midnight in the account timezone. Defaults to account-zone midnight six days ago (the last 7 days including today) |
| date_end | string End date: Unix timestamp, or YYYYMMDD read as the last second of that day in the account timezone (now when it is today). Defaults to now |
| groups[] | Array of integers Filter by monitor group IDs: the incidents of every monitor in the groups. Combined with monitors[] as a union. |
| monitors[uptime][] | Array of integers Filter by specific uptime monitor IDs |
| monitors[ssl][] | Array of integers Filter by specific SSL monitor IDs |
| monitors[domain][] | Array of integers Filter by specific domain monitor IDs |
| monitors[fpl][] | Array of integers Filter by specific speed (full page load) monitor IDs. Note: the type key for speed monitors is "fpl". |
| monitors[transaction][] | Array of integers Filter by specific transaction monitor IDs |
| monitors[virus][] | Array of integers Filter by specific virus monitor IDs |
| monitors[rum][] | Array of integers Filter by specific RUM monitor IDs |
| monitors[server][] | Array of integers Filter by specific server monitor IDs |
| monitors[heartbeat][] | Array of integers Filter by specific heartbeat monitor IDs |
| monitors[api][] | Array of integers Filter by specific API monitor IDs (every other family key, such as dns and blacklist, works the same way) |
| q | string Search: keeps incidents whose monitor name or error text contains this text (case-insensitive) |
| type | string Default: "all" Monitor type filter: a type code (uptime, ssl, domain, fpl or speed, ...), several comma-separated, or "all" |
| status | string Default: "all" Enum: "all" "ongoing" "resolved" Status filter: all, ongoing (not ended) or resolved |
| page | integer Default: 1 Page number (default 1) |
| limit | integer Default: 20 Results per page (default 20) |
| pin_ongoing | integer Default: 0 Enum: 0 1 Send 1 to list every ongoing incident first on page 1, ahead of the resolved incidents, which page and limit then page. Without it, ongoing and resolved incidents are paged together, newest first. |
Responses
Response samples
- 200
- 401
{- "data": [
- {
- "path": "string",
- "id": 0,
- "name": "string",
- "error_name": "string",
- "error_tooltip": "string",
- "time": "string",
- "time_started": "string",
- "time_ago": "string",
- "errors_sent_tooltip": "string",
- "errors_sent": 0,
- "duration": "string",
- "incident_id": 0,
- "type_name": "string",
- "type_name_display": "string",
- "incident_start": 0,
- "incident_end": 0,
- "is_critical": true,
- "is_trouble": true,
- "group": { },
- "delivery_counts": {
- "accepted": 0,
- "failed": 0,
- "skipped": 0,
- "cancelled": 0,
- "pending": 0,
- "total": 0
}, - "monitor_created": 0,
- "manual": true,
- "manual_incident": { },
- "closed_by_manual": true,
- "closed_by_manual_incident": { },
- "impact": { }
}
], - "metadata": {
- "total": 0,
- "page": 0,
- "pages": 0,
- "pinned_ongoing": 0,
- "counts": {
- "all": 0,
- "ongoing": 0,
- "resolved": 0
}, - "type_counts": {
- "property1": 0,
- "property2": 0
}
}
}Get incident details
Returns full details for a single incident including monitor info, error data, checkpoint servers, screenshots, response headers/body, and traceroute. Next to those it carries the incident page facts: the monitor target, every probe location that checked the monitor during the incident (failing and passing), the status-page updates posted for it, and per-family facts (speed limit and load times, certificate dates, blacklist listings, DNS expected and received answers, server last report). A fact the product does not store for a family is left out rather than sent empty.
Authorizations:
path Parameters
| type required | string Enum: "uptime" "ssl" "domain" "fpl" "speed" "virus" "transaction" "rum" "server" "heartbeat" "blacklist" "dns" "api" Monitor type (the speed-monitor type key is fpl; speed is accepted as an alias) |
| id required | integer\d+ Incident ID |
Responses
Response samples
- 200
- 400
- 401
- 404
{- "data": {
- "id": 0,
- "name": "string",
- "monitor_id": 0,
- "type": "string",
- "type_id": 0,
- "type_code": "string",
- "url": "string",
- "time_started": "string",
- "time_started_unix": 0,
- "time_ended": "string",
- "time_ended_unix": 0,
- "error": "string",
- "duration": "string",
- "errors": [
- {
- "error": "string",
- "count": 0
}
], - "servers": [
- {
- "name": "string",
- "flag": "string",
- "ip": "string",
- "date": "string",
- "error": "string"
}
], - "response_headers": "string",
- "response_body": "string",
- "traceroute": "string",
- "screenshot": "string",
- "is_critical": true,
- "is_trouble": true,
- "group_id": 0,
- "alerts": [
- {
- "name": "string",
- "channel": "string",
- "value": "string",
- "time": "string",
- "time_full": "string",
- "time_unix": 0,
- "is_up": true
}
], - "alerts_sent": 0,
- "delivery_failures": [
- {
- "id": 0,
- "channel": "email",
- "alert": "outage",
- "reason": "max_attempts",
- "error": "TransportException [SMTP 554]: Message rejected",
- "attempts": 6,
- "failed_at": 0,
- "failed_at_full": "string"
}
], - "deliveries": [
- {
- "id": 0,
- "incident_id": 0,
- "monitor_type": "uptime",
- "monitor_id": 0,
- "contact_id": 0,
- "contact_name": "Jane Doe",
- "channel": "email",
- "alert": "outage",
- "kind": "alert",
- "group_notice": "s1",
- "status": "queued",
- "reason": "max_attempts",
- "final": true,
- "queued_at": 0,
- "scheduled_for": 0,
- "held_until": 0,
- "attempts": 0,
- "last_attempt_at": 0,
- "next_attempt_at": 0,
- "accepted_at": 0,
- "finished_at": 0,
- "last_error": "TransportException [SMTP 554]: Message rejected",
- "attempt_history": [
- {
- "attempt_no": 1,
- "attempted_at": 0,
- "result": "accepted",
- "error": "string",
- "duration_ms": 0
}
]
}
], - "delivery_counts": {
- "accepted": 0,
- "failed": 0,
- "skipped": 0,
- "cancelled": 0,
- "pending": 0,
- "total": 0
}, - "monitor_paused": true,
- "status_updates": [
- {
- "thread_id": 0,
- "title": "string",
- "status": "investigating",
- "message": "string",
- "time": 0
}
], - "locations": [
- {
- "server_id": 0,
- "name": "Frankfurt, Germany",
- "flag": "de",
- "status": "failed",
- "confirmed": true,
- "checks": 0,
- "failed_checks": 0,
- "last_checked_at": 0,
- "last_failed_at": 0,
- "response_ms": 0,
- "error": "string"
}
], - "locations_total": 0,
- "locations_failed": 0,
- "errors_seen": [
- {
- "error": "Error on step #3",
- "count": 12
}
], - "probe_error_type": "string",
- "speed": {
- "limit_seconds": 0,
- "window_minutes": 0,
- "error_code": "website_too_slow",
- "reason": "string",
- "measured_from": 0,
- "measured_to": 0,
- "average_seconds": 0,
- "per_location": [
- {
- "server_id": 0,
- "name": "string",
- "flag": "string",
- "average_seconds": 6.42,
- "max_seconds": 9.1,
- "checks": 0,
- "over_limit": true
}
], - "slowest_check": {
- "time": 0,
- "server_id": 0,
- "load_seconds": 0
}
}, - "ssl": {
- "valid_to": 0,
- "valid_from": 0,
- "issuer": "string",
- "expire_alert_days": 0,
- "days_left_at_start": 0,
- "days_left": 0,
- "source": "check"
}, - "last_report_unix": 0,
- "ip_address": "string",
- "mount": "string",
- "listings": [
- {
- "id": 0,
- "list_id": "zen",
- "list_name": "Spamhaus ZEN",
- "zone": "zen.spamhaus.org",
- "tier": "major",
- "resource": "string",
- "resource_value": "string",
- "severity": "critical",
- "code": "127.0.0.2",
- "decoded": "string",
- "txt": "string",
- "first_seen": 0,
- "last_seen": 0,
- "cleared_at": 0,
- "resolved_at": 0,
- "status": "active",
- "close_reason": "string"
}
], - "lists_total": 0,
- "security": {
- "alert_level": "critical",
- "findings": [
- { }
]
}, - "dns_changes": [
- {
- "id": 0,
- "kind": "change",
- "record": "MX @",
- "watch_key": "@|MX",
- "severity": "string",
- "expected": [
- "string"
], - "got": [
- "string"
], - "ttl_before": 0,
- "ttl_after": 0,
- "ttl_only": true,
- "first_seen": 0,
- "last_seen": 0,
- "restored_at": 0,
- "acked_at": 0,
- "acked_by": "string",
- "ack_note": "string",
- "status": "string"
}
], - "manual": true,
- "manual_incident": { },
- "closed_by_manual": true,
- "closed_by_manual_incident": { },
- "impact": { }
}
}Delete an incident
Permanently deletes an ended incident and its associated status page threads. An ongoing incident is refused with 409 incident_open for every monitor type. An incident of another account, or of a monitor outside a scoped operator's monitor groups, is treated as not existing (400 could_not_delete_incident).
Authorizations:
path Parameters
| type required | string Enum: "uptime" "ssl" "domain" "fpl" "speed" "virus" "transaction" "rum" "server" "heartbeat" "blacklist" "dns" "api" Monitor type (the speed-monitor type key is fpl; speed is accepted as an alias) |
| id required | integer\d+ Incident ID |
Responses
Response samples
- 200
- 400
- 401
- 409
{- "status": "success"
}Set how much of an incident counts as downtime
The incident, its alerts and its logs stay exactly as recorded; only the downtime figure changes, everywhere availability is shown: the dashboard, monitor pages, reports, status pages, exports and the API. Reports already sent are not re-sent. none adds no downtime. partial adds only the stretch counted_from to counted_until, which must lie inside the incident (an ongoing incident is bounded by now, and later time does not count even if it continues). An incident the severity rule does not count as downtime cannot be overridden. Setting none or partial needs the Professional plan or above; restoring to full works on every plan. Each change writes one row to the account activity log. Needs the monitoring.manage capability (owner, Admin, Editor, and API keys). full behaves as the DELETE route.
Authorizations:
path Parameters
| type required | stringuptime|ssl|domain|speed|fpl|transaction|virus... Enum: "uptime" "ssl" "domain" "speed" "fpl" "transaction" "virus" "rum" "server" "heartbeat" "blacklist" "dns" "api" "api-monitor" Monitor type. |
| id required | integer\d+ Example: 3391 Monitor ID |
| incidentId required | integer\d+ Example: 99120 Incident ID. It must belong to the monitor in the path. |
Request Body schema: application/json
| mode required | string Enum: "full" "none" "partial"
|
| counted_from | integer Required for |
| counted_until | integer Required for |
| reason | string <= 500 characters Required for |
Responses
Request samples
- Payload
{- "mode": "partial",
- "counted_from": 1761820000,
- "counted_until": 1761820720,
- "reason": "Scheduled failover"
}Response samples
- 200
- 401
- 403
- 404
- 422
- 500
{- "data": {
- "monitor_type": "uptime",
- "monitor_id": 3391,
- "incident_id": 99120,
- "impact": {
- "mode": "partial",
- "eligible": true,
- "counted_from": 1761820000,
- "counted_until": 1761820720,
- "counted_seconds": 720,
- "reason": "Scheduled failover",
- "set_by": {
- "kind": "operator",
- "id": 5,
- "label": "Anna"
}, - "updated_at": 1761900000
}
}
}Restore an incident to count in full
Removes the incident's availability impact override, so it counts in full again. Idempotent: an incident without an override answers 200 and writes no activity-log row. Works on every plan. Needs the monitoring.manage capability.
Authorizations:
path Parameters
| type required | stringuptime|ssl|domain|speed|fpl|transaction|virus... Enum: "uptime" "ssl" "domain" "speed" "fpl" "transaction" "virus" "rum" "server" "heartbeat" "blacklist" "dns" "api" "api-monitor" Monitor type. |
| id required | integer\d+ Example: 3391 Monitor ID |
| incidentId required | integer\d+ Example: 99120 Incident ID. It must belong to the monitor in the path. |
Responses
Response samples
- 200
- 403
- 404
- 500
{- "data": {
- "monitor_type": "uptime",
- "monitor_id": 3391,
- "incident_id": 99120,
- "impact": { }
}
}Set or restore availability impact on many incidents
The incident, its alerts and its logs stay exactly as recorded; only the downtime figure changes, everywhere availability is shown: the dashboard, monitor pages, reports, status pages, exports and the API. Reports already sent are not re-sent. none adds no downtime. partial adds only the stretch counted_from to counted_until, which must lie inside the incident (an ongoing incident is bounded by now, and later time does not count even if it continues). An incident the severity rule does not count as downtime cannot be overridden. Setting none or partial needs the Professional plan or above; restoring to full works on every plan. Each change writes one row to the account activity log. Needs the monitoring.manage capability (owner, Admin, Editor, and API keys). All or nothing: every item is checked before anything is written, one unknown monitor or incident answers 404 with nothing changed, and the writes share one transaction. An incident the severity rule does not count is not an error here: with none it is reported in skipped. partial is not available in bulk. One activity-log row is written per incident that actually changed.
Authorizations:
Request Body schema: application/json
required | Array of objects [ 1 .. 500 ] items The incidents, at most 500. Duplicates count once. |
| mode required | string Enum: "none" "full" |
| reason | string <= 500 characters Required for |
Responses
Request samples
- Payload
{- "items": [
- {
- "type": "speed",
- "id": 3391,
- "incident_id": 99120
}
], - "mode": "none",
- "reason": "Scheduled failover"
}Response samples
- 200
- 403
- 404
- 422
- 500
{- "data": {
- "updated": 3,
- "skipped": [
- {
- "type": "speed",
- "id": 3391,
- "incident_id": 99120,
- "reason": "not_downtime"
}
], - "items": [
- {
- "type": "speed",
- "id": 3391,
- "incident_id": 99121,
- "impact": { }
}
]
}
}Get a monitor's open manual incident
Returns the monitor's open manual incident, or data: null when it has none (automatic alerting is running normally). Needs the monitoring.view capability.
Authorizations:
path Parameters
| type required | string Enum: "uptime" "speed" "fpl" "transaction" "ssl" "domain" "virus" "server" "heartbeat" "blacklist" "dns" "api" Monitor type. |
| id required | integer\d+ Example: 3391 Monitor ID |
Responses
Response samples
- 200
- 400
- 404
{- "data": {
- "manual_id": 812,
- "incident_id": 99120,
- "monitor_type": "uptime",
- "monitor_id": 3391,
- "monitor_name": "shop.example.com",
- "severity": "critical",
- "message": "Checkout payments fail for card customers.",
- "ongoing": true,
- "opened_at_unix": 1761823200,
- "opened_by_kind": "operator",
- "opened_by_label": "Jane Doe",
- "resolved_at_unix": null,
- "resolved_by_kind": null,
- "resolved_by_label": null,
- "resolve_reason": null,
- "status_thread_id": null
}
}Open a manual incident on a monitor
Opens an incident on the monitor by hand and pauses its automatic alerting until the incident is resolved with POST .../manual-incident/resolve. While it is open, checks keep running and are logged, but no automatic incident is opened, recovered, re-graded or closed for the monitor. An automatic incident that is open when the call arrives is closed silently and replaced: it gets no recovery alert and no monitor.up webhook, its alert-group membership and escalation ladder are cancelled, and its not-yet-sent alerts are removed. Its ids come back in closed_incident_ids. The manual incident then alerts like any other incident: a DOWN goes to the monitor's contacts at once (the monitor's report_down delay does not apply; a recipient outside their alerting hours gets it when those hours start), alert groups and escalation policies pick it up, and resolving it sends the UP with the duration (subject to report_up). A critical incident counts as downtime; a degraded one does not. Available for every monitor type except rum. Needs the monitoring.manage capability (owner, Admin, Editor, and API keys).
Authorizations:
path Parameters
| type required | string Enum: "uptime" "speed" "fpl" "transaction" "ssl" "domain" "virus" "server" "heartbeat" "blacklist" "dns" "api" Monitor type. |
| id required | integer\d+ Example: 3391 Monitor ID |
Request Body schema: application/json
| message required | string [ 1 .. 500 ] characters What is wrong, in plain text (1-500 characters after cleaning). It is shown on the incident, in the control panel and in alerts: SMS cuts it to 160 characters, push to 120, and WhatsApp carries only the monitor name. Control characters, bidirectional override characters and characters above U+FFFF (emoji) are removed; line breaks are kept. A message with nothing readable left is refused as |
| severity required | string Enum: "critical" "degraded"
|
| post_to_status_pages | boolean Also open an incident thread on the status pages that show this monitor (or post an update to the thread already open for it). Only for |
Responses
Request samples
- Payload
{- "message": "Checkout payments fail for card customers. The payment provider is investigating.",
- "severity": "critical",
- "post_to_status_pages": false
}Response samples
- 201
- 400
- 401
- 403
- 404
- 409
- 429
- 500
{- "data": {
- "manual_id": 812,
- "incident_id": 99120,
- "monitor_type": "uptime",
- "monitor_id": 3391,
- "severity": "critical",
- "opened_at_unix": 1761823200,
- "closed_incident_ids": [
- 99107
], - "down_skipped_reason": null,
- "status_thread_id": null,
- "status_thread_error": null
}
}Resolve a monitor's manual incident
Closes the monitor's open manual incident and resumes automatic alerting: the UP alert goes out with the duration (subject to the monitor's report_up; no UP goes out when the DOWN was skipped because the monitor was silenced), status-page threads move to their recovered close-out state, and the next check judges the monitor from live data again. A monitor that is still failing opens a normal automatic incident through its usual confirmation. Takes no request body. Needs the monitoring.manage capability.
Authorizations:
path Parameters
| type required | string Enum: "uptime" "speed" "fpl" "transaction" "ssl" "domain" "virus" "server" "heartbeat" "blacklist" "dns" "api" Monitor type. |
| id required | integer\d+ Example: 3391 Monitor ID |
Responses
Response samples
- 200
- 400
- 401
- 403
- 404
- 409
- 500
{- "data": {
- "manual_id": 812,
- "incident_id": 99120,
- "resolved_at_unix": 1761826800,
- "duration_seconds": 3600
}
}List open manual incidents
Every open manual incident on the account, newest first, with the monitor's name. Not paginated: a monitor has at most one open manual incident. A member limited to monitor groups sees only the incidents of monitors in their groups. Needs the monitoring.view capability.
Authorizations:
Responses
Response samples
- 200
{- "data": [
- {
- "manual_id": 812,
- "incident_id": 99120,
- "monitor_type": "uptime",
- "monitor_id": 3391,
- "monitor_name": "shop.example.com",
- "severity": "critical",
- "message": "Checkout payments fail for card customers.",
- "ongoing": true,
- "opened_at_unix": 1761823200,
- "opened_by_kind": "operator",
- "opened_by_label": "Jane Doe",
- "resolved_at_unix": null,
- "resolved_by_kind": null,
- "resolved_by_label": null,
- "resolve_reason": null,
- "status_thread_id": null
}
], - "metadata": {
- "total": 1
}
}List monitoring logs across all monitor types
Returns a paginated list of monitoring check logs across all monitor types (uptime, SSL, domain, speed, virus, transaction, server, API, DNS, blacklist). Supports filtering by date range, monitor, status, and location. Heartbeat pings are returned when the monitor filter names heartbeat monitors only.
Authorizations:
query Parameters
| date_start | string Start date (Unix timestamp or YYYYMMDD string read as a day in the account timezone, defaults to account-zone midnight six days ago) |
| date_end | string End date (Unix timestamp or YYYYMMDD string read as a day in the account timezone, defaults to now) |
| monitors[uptime][] | Array of integers Filter by specific uptime monitor IDs |
| monitors[ssl][] | Array of integers Filter by specific SSL monitor IDs |
| monitors[domain][] | Array of integers Filter by specific domain monitor IDs |
| monitors[fpl][] | Array of integers Filter by specific speed (full page load) monitor IDs. Note: the type key for speed monitors is "fpl". |
| monitors[transaction][] | Array of integers Filter by specific transaction monitor IDs |
| monitors[virus][] | Array of integers Filter by specific virus monitor IDs |
| monitors[api][] | Array of integers Filter by specific API monitor IDs (one row per run) |
| monitors[dns][] | Array of integers Filter by specific DNS monitor IDs (one row per sweep; these rows carry no probe location) |
| monitors[blacklist][] | Array of integers Filter by specific blacklist monitor IDs (one row per sweep; these rows carry no probe location) |
| status[up] | boolean Default: true Include checks with up status (default true) |
| status[down] | boolean Default: true Include checks with down status (default true) |
| locations | string Default: "all" Location filter: "all" or comma-separated location IDs |
| page | integer Default: 1 Page number (default 1) |
| limit | integer Default: 20 Results per page (default 20) |
Responses
Response samples
- 200
- 401
{- "data": [
- {
- "id": 0,
- "key": "string",
- "location_id": 0,
- "backup": true,
- "signal": "string",
- "monitor_name": "string",
- "monitor_type": "string",
- "monitor_id": 0,
- "location": "string",
- "location_code": "string",
- "time": "string",
- "status": "string",
- "duration": "string",
- "response_time": 0,
- "security_summary": { }
}
], - "metadata": {
- "total": 0,
- "page": 0,
- "pages": 0
}
}Get single monitoring log detail
Returns detailed data for a single monitoring check result.
Authorizations:
path Parameters
| type required | string Enum: "uptime" "ssl" "domain" "virus" "fpl" "transaction" "heartbeat" "server" "api" "dns" "blacklist" Monitor type |
| id required | integer\d+ Log entry ID |
query Parameters
| location | integer The list row's location_id: the probe that ran the check. With backup and signal it picks one check when several share the id (the probes of one round, a heartbeat start and success in one second); without them the id resolves the same check every time. |
| backup | boolean The list row's backup flag (1 = backup-probe re-check) |
| signal | string Heartbeat only: the list row's signal (success, start, manual or fail) |
Responses
List available contact types (Email, SMS, Phone call + user integrations)
Returns the set of contact channel types you can assign to a new contact: the built-in Email (id 1) and SMS (id 2) types plus one entry per integration profile already configured on your account (Slack, Discord, Telegram, webhooks, etc.). Use the returned ids/type codes when creating or updating contacts.
Authorizations:
Responses
Response samples
- 200
{- "data": [
- {
- "id": 0,
- "type": "string",
- "name": "string"
}
]
}List all contacts for the authenticated account
Returns every alert recipient configured on your account, including email and SMS contacts as well as integration-backed contacts, with their confirmation status and the operator they belong to. The response includes a metadata.total count; results are not paginated.
Authorizations:
query Parameters
| scope | string Value: "account" Optional scope filter. Defaults to the operator-scoped list (an operator sees only the contacts it owns). Set to "account" to return every contact on the account regardless of the owning operator. |
Responses
Response samples
- 200
{- "data": [
- {
- "id": 0,
- "user": 0,
- "operator": 0,
- "name": "string",
- "type": 0,
- "contact": "string",
- "is_integration": 0,
- "integration_type": "string",
- "integration_id": 0,
- "is_confirmed": 0,
- "verified": true,
- "confirmed_at": 0,
- "confirmation_sent_at": 0,
- "confirmation_expires_at": 0,
- "verification_status": "verified",
- "alerts_suppressed": 0
}
], - "metadata": {
- "total": 0
}
}Create a new contact
Adds a new alert recipient to your account. For email/sms types the value is the address or phone number, and SMS numbers must be in international format (starting with a +); for integration types the value is optional and integration_id links to an existing integration profile. E-mail, SMS and phone-call contacts start unverified (is_confirmed=false) and receive no alert until the address or number is confirmed: a verification e-mail (link) or SMS (code + link) is sent automatically, free of SMS credits. Your own login address, once proven by signing in, is verified at once. Integrations need no verification.
Authorizations:
Request Body schema: application/json
| name required | string Contact display name |
| type required | string Contact type (email, sms, slack, etc.) |
| value required | string Contact value (email address, phone number, etc.) |
| integration_id | integer Integration ID if this is an integration contact |
| user_id | integer User/operator ID this contact belongs to (0 for main user) |
Responses
Request samples
- Payload
{- "name": "string",
- "type": "email",
- "integration_id": 0,
- "user_id": 0
}Response samples
- 201
- 400
{- "data": {
- "id": 0,
- "is_confirmed": true,
- "verification": "sent"
}
}Get a single contact by ID
Returns the full record for one alert recipient, including its type, value, confirmation flag and integration linkage. The contact must belong to the authenticated account; a 404 is returned if no matching contact exists.
Authorizations:
path Parameters
| id required | integer\d+ |
Responses
Response samples
- 200
- 404
{- "data": {
- "id": 0,
- "user": 0,
- "operator": 0,
- "name": "string",
- "type": 0,
- "contact": "string",
- "is_integration": 0,
- "integration_type": "string",
- "integration_id": 0,
- "is_confirmed": 0,
- "verified": true,
- "confirmed_at": 0,
- "confirmation_sent_at": 0,
- "confirmation_expires_at": 0,
- "verification_status": "verified",
- "alerts_suppressed": 0
}
}Delete a contact
Permanently removes an alert recipient from your account so it will no longer receive notifications. This action cannot be undone; returns a 400 if the contact could not be deleted.
Authorizations:
path Parameters
| id required | integer\d+ |
Responses
Response samples
- 200
- 400
{- "status": "success"
}Update an existing contact
Modifies an existing alert recipient; only the fields you supply are changed and any omitted fields keep their current values. SMS numbers must remain in international format (starting with a +). Changing the address or number of an e-mail, SMS or phone-call contact makes it unverified again and sends a new verification message; an unchanged address keeps its verification state. The contact must belong to the authenticated account, otherwise a 404 is returned.
Authorizations:
path Parameters
| id required | integer\d+ |
Request Body schema: application/json
| name | string Contact display name |
| type | string Contact type |
| value | string Contact value |
| integration_id | integer |
| user_id | integer Reassign the contact to this user/operator (0 = main user). Account owner only. |
Responses
Request samples
- Payload
{- "name": "string",
- "type": "string",
- "value": "string",
- "integration_id": 0,
- "user_id": 0
}Response samples
- 200
- 400
{- "data": {
- "id": 0,
- "is_confirmed": true,
- "verification": "string"
}
}Get contacts with selection state for a specific monitor
Returns all of your contacts annotated with an is_selected flag indicating whether each one will be alerted for the given monitor. Unlike the monitor contacts array — which holds polymorphic references (team / owner / seat / contact) that are only expanded when an alert fires — this endpoint reports the resolved answer: a contact is selected when the monitor names it directly OR when it names a team, the owner, or the seat that owns it. Use it to render an alert-recipient picker, or to answer "who actually gets paged for this monitor". Monitor types: uptime, ssl, domain, fpl, transaction, rum, virus, server, heartbeat, blacklist, dns, api, reports.
Authorizations:
path Parameters
| monitorId required | integer\d+ Monitor ID |
| type required | string Monitor type (uptime, ssl, domain, fpl, transaction, rum, virus, reports) |
Responses
Response samples
- 200
{- "data": [
- {
- "id": 0,
- "name": "string",
- "type": "string",
- "type_text": "string",
- "contact_value": "string",
- "operator_id": 0,
- "is_selected": true
}
]
}Resend the verification message to a contact
Sends a new verification message to an unverified e-mail, SMS or phone-call contact: an e-mail with a confirmation link, or an SMS with a 6-digit code and a link (phone-call numbers are verified by SMS too). A new message replaces the previous link and code. Verification SMS are never charged to your SMS credits. Limits: one resend per contact per 10 minutes, 20 verification e-mails per account per hour, 3 verification SMS per contact and 10 per account per day, 5 messages per address or number per day. A contact that is already verified, or an integration, needs nothing: the call succeeds with result not_needed. The contact must belong to your account; a team member can resend only for contacts they own.
Authorizations:
path Parameters
| id required | integer\d+ |
Responses
Response samples
- 200
- 400
- 429
{- "status": "success",
- "data": {
- "result": "sent",
- "channel": "string"
}
}Allow alerts again for an opted-out contact
Undoes the recipient's own opt-out. When someone uses the "Do not send alerts to this e-mail address" link in an alert e-mail, their contact gets alerts_suppressed = 1 and every alert and report e-mail to it is skipped (alerts are recorded as skipped / contact_opted_out in the delivery record). This sets it back to 0. Because the opt-out was the recipient's choice, an e-mail, SMS or phone-call contact then needs the recipient's confirmation again: it becomes unverified (is_confirmed = 0) and a fresh verification message is sent (the usual verification limits apply), and alerts and reports resume only once the recipient confirms. Your own verified login e-mail stays verified, and integrations need no confirmation. The recipient's decline is remembered for your account and that address or number (also after the contact is deleted and added again); it can be lifted once every 30 days per address: inside that window the call answers 429 too_many_requests with retry_after and nothing changes. The verification field says what happened: sent, confirmed (no confirmation needed), not_required (integration), waiting_for_login, account_unverified, rate_limited, failed or not_sent (use request-confirmation to send it). The link opts out every contact of the account carrying that address, so this clears the flag on each of them that you may edit. The contact must belong to your account; a team member can change only contacts they own.
Authorizations:
path Parameters
| id required | integer\d+ |
Responses
Response samples
- 200
- 400
- 404
- 429
{- "data": {
- "id": 0,
- "alerts_suppressed": 0,
- "cleared_ids": [
- 0
], - "verification": "sent"
}
}Verify a contact with the code from its verification SMS
Confirms an SMS or phone-call contact with the 6-digit code from its verification SMS (whoever holds the phone can also tap the link in that SMS instead). On success the contact is verified and receives alerts. A code works for 30 minutes; after 5 wrong entries it stops working and a new one must be sent with request-confirmation. The contact must belong to your account; a team member can confirm only contacts they own.
Authorizations:
path Parameters
| id required | integer\d+ |
Request Body schema: application/json
| code required | string The code from the verification SMS |
Responses
Request samples
- Payload
{- "code": "123456"
}Response samples
- 200
- 400
{- "status": "success"
}Send the verification message to a contact
Same as POST /api/v2/contacts/{id}/request-confirmation (both paths stay available): sends a new verification message to an unverified e-mail, SMS or phone-call contact — an e-mail with a confirmation link, or an SMS with a 6-digit code and a link (phone-call numbers are verified by SMS too). A new message replaces the previous link and code. Verification SMS are never charged to your SMS credits. Limits: one message per contact per 10 minutes, 20 verification e-mails per account per hour, 3 verification SMS per contact and 10 per account per day, 5 messages per address or number per day. A contact that is already verified, or an integration, needs nothing: the call succeeds with result not_needed. The contact must belong to your account; a team member can send only for contacts they own.
Authorizations:
path Parameters
| id required | integer\d+ |
Responses
Response samples
- 200
- 400
- 404
- 429
{- "status": "success",
- "data": {
- "result": "sent",
- "channel": "string"
}
}Verify a contact with the code from its verification SMS
Same as POST /api/v2/contacts/{id}/confirm (both paths stay available): confirms an SMS or phone-call contact with the 6-digit code from its verification SMS (whoever holds the phone can also tap the link in that SMS instead). On success the contact is verified and receives alerts. A code works for 30 minutes; after 5 wrong entries it stops working and a new one must be sent with send-verification. The contact must belong to your account; a team member can verify only contacts they own.
Authorizations:
path Parameters
| id required | integer\d+ |
Request Body schema: application/json
| code required | string The code from the verification SMS |
Responses
Request samples
- Payload
{- "code": "123456"
}Response samples
- 200
- 400
- 404
{- "status": "success"
}Send a test alert to a contact
Sends a sample Down alert marked TEST to one verified e-mail, SMS or phone-call contact, through the same sender, SMS account and caller number a real alert uses. Nothing is down and no incident is opened. An SMS or phone-call test uses one of your SMS or phone-call credits, like a real alert. Every test is recorded in the alert delivery record (GET /api/v2/deliveries, kind test) with what happened to it. Limits: 5 tests per contact and 20 per account per hour. Integrations have their own test: POST /api/v2/integrations/{id}/test. The contact must belong to your account; a team member can test only contacts they own.
Authorizations:
path Parameters
| id required | integer\d+ |
Responses
Response samples
- 200
- 400
- 404
- 429
{- "status": "success",
- "data": {
- "result": "sent",
- "channel": "string",
- "delivery_id": 0
}
}Unverified contacts that monitors rely on
Lists the unverified e-mail, SMS and phone-call contacts that at least one active (not paused) monitor would alert, directly, through a team or seat, or through its escalation policy. These contacts receive no alert until they are verified. Opted-out contacts are not listed. A team member sees only the contacts they own.
Authorizations:
Responses
Response samples
- 200
{- "data": [
- {
- "id": 0,
- "name": "string",
- "type": "string",
- "contact": "string",
- "verification_status": "string",
- "monitors": 0
}
], - "metadata": {
- "total": 0
}
}List active (open) alert groups
Returns every OPEN alert group on the account — single-monitor groups included — newest first, and never paginated. For a monitor-scoped seat (Editor or Read-only member limited to monitor groups) each card carries only the members in its scope, the counts are computed from those members, and a group with no member in scope is left out. Each element is the GroupCard: the members folded into the storm, distinct-monitor counts, acknowledgement state with MTTA, the notifications-sent tally and the storm-math counterfactual (without_grouping). GET /api/v2/alert-groups/{id} returns this same card plus the detail-only extras, so an incident dashboard can list here and drill in there without reshaping anything.
Authorizations:
Responses
Response samples
- 200
- 401
{- "data": [
- {
- "id": 4821,
- "status": "open",
- "acked": {
- "at": 1756108800,
- "by": "Jane Doe",
- "via": "control panel",
- "mtta_minutes": 4,
- "resumed": false
}, - "resumed_at": 0,
- "acked_active": false,
- "maintenance_held": false,
- "policy": {
- "id": 12,
- "name": "Production on-call"
}, - "opened_at": 1756108500,
- "confirmed_at": 1756108560,
- "duration_seconds": 900,
- "cancelled_at": 0,
- "member_count": 7,
- "distinct_monitor_count": 5,
- "down_count": 3,
- "members": [
- {
- "monitor_type": "uptime",
- "monitor_id": 3391,
- "incident_id": 88214,
- "label": "api.example.com",
- "error": "Connection timed out",
- "confirmed_at": 1756108740,
- "joined_offset_minutes": 3,
- "recovered_at": 0,
- "maintenance_held": false,
- "path": "/cp/monitors/uptime/3391"
}
], - "notifications_sent": 9,
- "notification_rounds": 3,
- "without_grouping": 10,
- "window_minutes": 3
}
], - "metadata": {
- "total": 2
}
}Get one alert group
Returns one owned alert group as the same GroupCard the active list returns, plus nine detail-only extras: the raw events trail, the ladder (per-row schedule and status), the delivery ledger, a live policy_snapshot, ack_url_enabled, the alerts_sent tally, the undelivered delivery_failures, and every per-recipient delivery in deliveries with its delivery_counts. Any owned group can be fetched, not only open ones — resolved and cancelled groups stay readable. A group whose escalation state row has been purged still returns, with an empty ladder. A monitor-scoped seat gets 404 for a group with no member in its scope; for any other group the card is account-wide and only the recipient-level delivery sections are narrowed to its members.
Authorizations:
path Parameters
| id required | integer\d+ Example: 4821 Alert group ID |
Responses
Response samples
- 200
- 401
- 404
{- "data": {
- "id": 4821,
- "status": "open",
- "acked": {
- "at": 1756108800,
- "by": "Jane Doe",
- "via": "control panel",
- "mtta_minutes": 4,
- "resumed": false
}, - "resumed_at": 0,
- "acked_active": false,
- "maintenance_held": false,
- "policy": {
- "id": 12,
- "name": "Production on-call"
}, - "opened_at": 1756108500,
- "confirmed_at": 1756108560,
- "duration_seconds": 900,
- "cancelled_at": 0,
- "member_count": 7,
- "distinct_monitor_count": 5,
- "down_count": 3,
- "members": [
- {
- "monitor_type": "uptime",
- "monitor_id": 3391,
- "incident_id": 88214,
- "label": "api.example.com",
- "error": "Connection timed out",
- "confirmed_at": 1756108740,
- "joined_offset_minutes": 3,
- "recovered_at": 0,
- "maintenance_held": false,
- "path": "/cp/monitors/uptime/3391",
- "details_available": true,
- "manual_incident": { },
- "closed_by_manual_incident": { },
- "closed_by_manual": true
}
], - "notifications_sent": 9,
- "notification_rounds": 3,
- "without_grouping": 10,
- "window_minutes": 3,
- "events": [
- {
- "id": 90142,
- "kind": "digest_sent",
- "at": 1756108560,
- "data": { }
}
], - "ladder": [
- {
- "index": 0,
- "label": "step",
- "state": "fired",
- "at": 1756108560,
- "recipients": [
- {
- "id": 41,
- "is_team": 0,
- "is_user": 0,
- "is_operator": 0,
- "is_individual_contact": 1
}
]
}
], - "ledger": [
- {
- "at": 1756108560,
- "kind": "digest",
- "sub": "step 1 · 5 monitors",
- "contacts": 5,
- "channels": [
- "email"
], - "members": [
- {
- "monitor_type": "uptime",
- "monitor_id": 3391,
- "label": "api.example.com"
}
], - "new_members": [
- {
- "monitor_type": "uptime",
- "monitor_id": 3391,
- "label": "api.example.com"
}
]
}
], - "policy_snapshot": {
- "steps": [
- {
- "wait_minutes": 15,
- "recipients": [
- {
- "id": 41,
- "is_team": 0,
- "is_user": 0,
- "is_operator": 0,
- "is_individual_contact": 1
}
]
}
], - "repeat": {
- "enabled": true,
- "scope": "last",
- "every_minutes": 30,
- "max_rounds": 2
}
}, - "ack_url_enabled": true,
- "alerts_sent": 14,
- "delivery_failures": [
- {
- "id": 0,
- "channel": "email",
- "alert": "outage",
- "reason": "max_attempts",
- "error": "string",
- "attempts": 6,
- "failed_at": 0
}
], - "deliveries": [
- {
- "id": 0,
- "incident_id": 0,
- "monitor_type": "uptime",
- "monitor_id": 0,
- "contact_id": 0,
- "contact_name": "Jane Doe",
- "channel": "email",
- "alert": "outage",
- "kind": "alert",
- "group_notice": "s1",
- "status": "queued",
- "reason": "max_attempts",
- "final": true,
- "queued_at": 0,
- "scheduled_for": 0,
- "held_until": 0,
- "attempts": 0,
- "last_attempt_at": 0,
- "next_attempt_at": 0,
- "accepted_at": 0,
- "finished_at": 0,
- "last_error": "TransportException [SMTP 554]: Message rejected",
- "attempt_history": [
- {
- "attempt_no": 1,
- "attempted_at": 0,
- "result": "accepted",
- "error": "string",
- "duration_ms": 0
}
]
}
], - "delivery_counts": {
- "accepted": 0,
- "failed": 0,
- "skipped": 0,
- "cancelled": 0,
- "pending": 0,
- "total": 0
}
}
}Acknowledge an alert group
Pauses the group's escalation ladder — pending steps and repeat rounds stop — without closing the incident: monitoring continues and the recovery notice still goes out. Records who acknowledged, through which channel, and the MTTA. Acknowledging is an operational action, so every operator seat that can view alerting may do it, read-only viewers included. A group whose earlier acknowledgement already auto-resumed can be acknowledged again; a resolved or cancelled one cannot. A monitor-scoped seat can only acknowledge a group with at least one member in its scope; any other group answers 404. Takes no request body.
Authorizations:
path Parameters
| id required | integer\d+ Example: 4821 Alert group ID |
Responses
Response samples
- 200
- 401
- 403
- 404
- 409
- 500
{- "status": "success",
- "acked": {
- "at": 1756108800,
- "by": "Jane Doe",
- "via": "control panel",
- "mtta_minutes": 4,
- "resumed": false
}
}Un-acknowledge an alert group
Clears the acknowledgement and re-arms the ladder from the current step; steps whose scheduled time passed while the group was paused fire immediately. Every acknowledgement link already distributed for this group stops working (the ack epoch is bumped). Allowed for every operator seat that can view alerting, viewers included. Unlike acknowledging, this is not restricted to open groups — ownership, monitor-group scope (a scoped seat gets 404 for a group with no member in its scope) and an existing acknowledgement are the only checks, so a resolved group can still be un-acknowledged. Takes no request body, and returns no acked block.
Authorizations:
path Parameters
| id required | integer\d+ Example: 4821 Alert group ID |
Responses
Response samples
- 200
- 401
- 403
- 404
- 409
{- "status": "success"
}What happened to every alert sent: accepted, dropped after every attempt failed, skipped, cancelled or pending, with each attempt and its error.
List the alert deliveries of an incident
Returns one entry per recipient per alert sent for this incident (outage and recovery alerts, alert-group digests and escalation steps), whatever happened to it: accepted by our mail relay or the channel's API, dropped after every attempt failed, skipped before sending (an opted-out or unconfirmed contact, for example), cancelled (acknowledged, paused, recovered before it was due), or still pending (queued, held by quiet hours, retrying). Each entry carries its attempts with the sanitized error of each failed one. The recipient is a snapshot taken when the alert was queued, so deliveries to a contact deleted since still show. accepted means the relay or API took the message; delivery to the inbox is not tracked.
Authorizations:
path Parameters
| type required | string Enum: "uptime" "ssl" "domain" "fpl" "speed" "virus" "transaction" "rum" "server" "heartbeat" "blacklist" "dns" "api" Monitor type (the speed-monitor type key is fpl; speed is accepted as an alias) |
| id required | integer\d+ Incident ID |
Responses
Response samples
- 200
- 400
- 401
- 404
{- "data": [
- {
- "id": 0,
- "incident_id": 0,
- "monitor_type": "uptime",
- "monitor_id": 0,
- "contact_id": 0,
- "contact_name": "Jane Doe",
- "channel": "email",
- "alert": "outage",
- "kind": "alert",
- "group_notice": "s1",
- "status": "queued",
- "reason": "max_attempts",
- "final": true,
- "queued_at": 0,
- "scheduled_for": 0,
- "held_until": 0,
- "attempts": 0,
- "last_attempt_at": 0,
- "next_attempt_at": 0,
- "accepted_at": 0,
- "finished_at": 0,
- "last_error": "TransportException [SMTP 554]: Message rejected",
- "attempt_history": [
- {
- "attempt_no": 1,
- "attempted_at": 0,
- "result": "accepted",
- "error": "string",
- "duration_ms": 0
}
]
}
], - "metadata": {
- "total": 0,
- "truncated": true,
- "counts": {
- "accepted": 0,
- "failed": 0,
- "skipped": 0,
- "cancelled": 0,
- "pending": 0,
- "total": 0
}
}
}List alert deliveries
Returns your account's alert deliveries, newest first, so you can find the alerts that did not reach anyone — for example ?status=dropped for every alert whose attempts all failed. Filters combine. The time window applies to when the alert was queued and defaults to the last 30 days. Deliveries are kept for as long as your plan keeps resolved incidents. A member limited to monitor groups sees only the deliveries of monitors in those groups.
Authorizations:
query Parameters
| status | string Example: status=dropped Only these statuses, comma-separated: queued, held, retrying, accepted, dropped, skipped, cancelled |
| reason | string Only this reason code (for example max_attempts, contact_opted_out, acknowledged) |
| channel | string Only this channel code (email, sms, voice, slack, webhook, push, fcm, …) |
| monitor_type | string Enum: "uptime" "ssl" "domain" "fpl" "speed" "virus" "transaction" "rum" "server" "heartbeat" "blacklist" "dns" "api" Only this monitor type (speed is accepted as an alias of fpl). Required with monitor_id and incident_id. |
| monitor_id | integer Only this monitor |
| incident_id | integer Only this incident |
| from | string Example: from=20260901 Window start: Unix timestamp, or a date as YYYYMMDD / YYYY-MM-DD in your account timezone. Defaults to 30 days ago. |
| to | string Example: to=20260930 Window end: Unix timestamp, or a date as YYYYMMDD / YYYY-MM-DD (the whole day is included). Defaults to now. |
| page | integer Default: 1 Page number (default 1) |
| limit | integer <= 200 Default: 50 Results per page (default 50, max 200) |
Responses
Response samples
- 200
- 400
- 401
{- "data": [
- {
- "id": 0,
- "incident_id": 0,
- "monitor_type": "uptime",
- "monitor_id": 0,
- "contact_id": 0,
- "contact_name": "Jane Doe",
- "channel": "email",
- "alert": "outage",
- "kind": "alert",
- "group_notice": "s1",
- "status": "queued",
- "reason": "max_attempts",
- "final": true,
- "queued_at": 0,
- "scheduled_for": 0,
- "held_until": 0,
- "attempts": 0,
- "last_attempt_at": 0,
- "next_attempt_at": 0,
- "accepted_at": 0,
- "finished_at": 0,
- "last_error": "TransportException [SMTP 554]: Message rejected",
- "attempt_history": [
- {
- "attempt_no": 1,
- "attempted_at": 0,
- "result": "accepted",
- "error": "string",
- "duration_ms": 0
}
], - "monitor_name": "string"
}
], - "metadata": {
- "total": 0,
- "page": 0,
- "pages": 0,
- "from": 0,
- "to": 0
}
}List escalation policies
Returns every escalation policy on the account, oldest id first, each with its ordered ladder of steps, its repeat block and how many monitors currently point at it. Not paginated and not filterable. The id of a policy here is the value the monitor endpoints of all twelve escalation-capable monitor types (uptime, SSL, speed, domain, transaction, virus, RUM, server, heartbeat, blacklist, DNS, API) accept as escalation_policy_id.
Authorizations:
Responses
Response samples
- 200
- 401
- 403
{- "data": [
- {
- "id": 41,
- "name": "Production — pager ladder",
- "steps": [
- {
- "wait_minutes": 10,
- "recipients": [
- {
- "id": 12,
- "is_team": 0,
- "is_user": 0,
- "is_operator": 0,
- "is_individual_contact": 1
}
]
}
], - "advance_on_failure": true,
- "repeat": {
- "enabled": true,
- "scope": "last",
- "every_minutes": 30,
- "max_rounds": 2
}, - "monitors": {
- "count": 7,
- "sample": [
- "Checkout",
- "Marketing site",
- "Login API"
]
}, - "created_at": 1753900000,
- "updated_at": 1755100000
}
], - "metadata": {
- "total": 3
}
}Create an escalation policy
Creates an escalation policy — a reusable ladder that monitors then bind to by id.
The ladder is the whole resource. Step 0 fires the moment the incident opens (whatever wait_minutes it was sent, the stored value is 0); every later step fires wait_minutes after the PREVIOUS step, so a ladder of 0 / 10 / 15 pages at t+0, t+10 and t+25. Every step must page at least one recipient: a step with an empty or missing recipients list is refused with invalid_steps.
repeat is optional. Omit it (or send null) and the policy is stored with repeats off: {enabled: false, scope: "last", every_minutes: 30, max_rounds: 2}. advance_on_failure is optional too and defaults to true. The response is the new id only — read it back with GET /escalation-policies/{id} if you need the stored shape.
Requires the Owner, Admin or Editor role, and the Professional plan or above; AppSumo lifetime accounts are exempt from the plan gate. Existing policies on a lower plan keep firing — only creation is gated.
Authorizations:
Request Body schema: application/json
| name required | string <= 100 characters Policy name, 1..100 characters. Stored trimmed and with HTML tags stripped, so |
required | Array of objects [ 1 .. 10 ] items The ladder, 1..10 steps, in fire order. Must be a JSON array (a keyed object is rejected). |
| advance_on_failure | boolean Default: true When every alert of a step ends undelivered and nobody got it, fire the next step at once instead of waiting. Optional; defaults to true. |
object or null What happens once the last step has fired and the incident is still open. Optional; each key falls back to the default shown. |
Responses
Request samples
- Payload
{- "name": "Production — pager ladder",
- "steps": [
- {
- "wait_minutes": 10,
- "recipients": [
- {
- "id": 12,
- "is_team": 0,
- "is_user": 0,
- "is_operator": 0,
- "is_individual_contact": 1
}
]
}
], - "advance_on_failure": true,
- "repeat": {
- "enabled": true,
- "scope": "last",
- "every_minutes": 30,
- "max_rounds": 2
}
}Response samples
- 201
- 400
- 401
- 403
{- "data": {
- "id": 41
}
}Get a single escalation policy
Returns one escalation policy owned by the account, in the same shape as the list entry. Unlike the list, the response carries no metadata envelope.
Authorizations:
path Parameters
| id required | integer\d+ Example: 41 Policy ID |
Responses
Response samples
- 200
- 401
- 403
- 404
{- "data": {
- "id": 41,
- "name": "Production — pager ladder",
- "steps": [
- {
- "wait_minutes": 10,
- "recipients": [
- {
- "id": 12,
- "is_team": 0,
- "is_user": 0,
- "is_operator": 0,
- "is_individual_contact": 1
}
]
}
], - "advance_on_failure": true,
- "repeat": {
- "enabled": true,
- "scope": "last",
- "every_minutes": 30,
- "max_rounds": 2
}, - "monitors": {
- "count": 7,
- "sample": [
- "Checkout",
- "Marketing site",
- "Login API"
]
}, - "created_at": 1753900000,
- "updated_at": 1755100000
}
}Delete an escalation policy
Deletes a policy owned by the account, along with its steps. The delete cascades, and it is not reversible:
- every monitor of every type bound to this policy is detached (
escalation_policy_idback to 0) and falls back to its own all-at-once recipient list — fallback, never silence. The count is returned asdetached. - escalation ladders currently running on this policy are cancelled where they stand.
- alert groups still open on this policy are cancelled, and their queued-but-unsent notification rows are deleted. Alerts that were already queued for those incidents will not be delivered.
The monitors themselves, and their own contact lists, are untouched. Requires the Owner, Admin or Editor role.
Note that status here is "ok", not the "success" the create and update paths return.
Authorizations:
path Parameters
| id required | integer\d+ Example: 41 Policy ID |
Responses
Response samples
- 200
- 401
- 403
- 404
{- "status": "ok",
- "detached": 7
}Update an escalation policy
Partially updates a policy owned by the account. Four keys are read — name, steps, repeat and advance_on_failure — and anything else in the body is ignored rather than rejected. A key you omit is left alone; sending all four is equivalent to a full save.
The two supplied keys behave differently, and the difference matters:
stepsis a full replace. The submitted array becomes the ladder; every step not in it is deleted. There is no way to edit one rung — read the policy, change the array, send it back.repeatis a merge. Keys you send are validated and written; keys you omit keep the value already stored, so{"repeat":{"every_minutes":15}}on anall / 120 / 5policy leaves scopealland 5 rounds in place. Sending"repeat": nullis the one exception: it is an explicit reset to{enabled: false, scope: "last", every_minutes: 30, max_rounds: 2}.
Nothing is written until the whole body validates, so a rejected request leaves the policy exactly as it was. The response carries no policy — read it back if you need the stored shape.
Requires the Owner, Admin or Editor role. There is no plan gate here: a policy that already exists stays editable on any plan.
Authorizations:
path Parameters
| id required | integer\d+ Example: 41 Policy ID |
Request Body schema: application/json
| name | string <= 100 characters New name, 1..100 characters. Stored trimmed and with HTML tags stripped. Omit to keep the current name; |
Array of objects [ 1 .. 10 ] items FULL REPLACEMENT of the ladder, 1..10 steps in fire order. Omit to keep the existing steps. Step 0 fires at t+0 (its | |
| advance_on_failure | boolean When every alert of a step ends undelivered and nobody got it, fire the next step at once instead of waiting. Omit to keep the stored value. |
object or null Merged over the stored repeat block: send only the keys you want to change. Omit the whole object to leave repeats untouched; send |
Responses
Request samples
- Payload
{- "name": "Production — pager ladder",
- "steps": [
- {
- "wait_minutes": 10,
- "recipients": [
- {
- "id": 12,
- "is_team": 0,
- "is_user": 0,
- "is_operator": 0,
- "is_individual_contact": 1
}
]
}
], - "advance_on_failure": true,
- "repeat": {
- "enabled": true,
- "scope": "all",
- "every_minutes": 120,
- "max_rounds": 5
}
}Response samples
- 200
- 400
- 401
- 403
- 404
{- "status": "success"
}Account-wide incident grouping and acknowledgement behaviour, including what the acknowledgement link in an alert does: one_click, sign_in (a password sign-in to the control panel first) or off.
Get incident grouping & acknowledgement settings
Returns the account's grouping and acknowledgement settings. An account that has never saved them gets the product defaults: grouping on, scope policy, 3-minute rolling window, 3-minute update throttle, one-click acknowledgement links, no auto-resume. Values are always catalogue members, so a client can render them as fixed choices.
Authorizations:
Responses
Response samples
- 200
- 401
{- "data": {
- "grouping_enabled": true,
- "group_scope": "policy",
- "window_minutes": 3,
- "window_mode": "rolling",
- "throttle_minutes": 3,
- "ack_links_enabled": true,
- "ack_link_mode": "one_click",
- "ack_resume_minutes": 0
}
}Update incident grouping & acknowledgement settings
Partial update — only the fields you send change, and every value must be a catalogue member: window_minutes 1|2|3|5|10, window_mode rolling|fixed, throttle_minutes 1|3|5|10, group_scope policy|policy+group, ack_resume_minutes 0|30|60|120 (0 = never), ack_link_mode off|one_click|sign_in. The two boolean fields also accept 0 and 1, as integer or string. ack_link_mode wins when a body carries both it and ack_links_enabled; a lone ack_links_enabled false sets the mode to off, and true sets one_click unless links are already on (a sign_in account stays sign_in). Groups snapshot the window and throttle when they open, so an edit only affects groups opened after it. Requires a seat that can manage alerting (Owner, Admin or Editor); Read-only and Accounting seats get 403. A body carrying no recognised field is accepted and writes nothing, so a mistyped field name still answers 200 — check the field names against the list above rather than trusting the status code.
Authorizations:
Request Body schema: application/json
| grouping_enabled | boolean Whether incidents are folded into groups at all |
| group_scope | string Enum: "policy" "policy+group" What has to match for two incidents to share a group |
| window_minutes | integer Enum: 1 2 3 5 10 How long a group keeps accepting new members, in minutes |
| window_mode | string Enum: "rolling" "fixed"
|
| throttle_minutes | integer Enum: 1 3 5 10 Minimum minutes between join-update notices for one group |
| ack_links_enabled | boolean Legacy switch for acknowledgement links. false = mode |
| ack_link_mode | string Enum: "off" "one_click" "sign_in" How the acknowledgement link in an alert works: |
| ack_resume_minutes | integer Enum: 0 30 60 120 Minutes an acknowledgement holds the ladder before resuming on its own; 0 = never |
Responses
Request samples
- Payload
{- "grouping_enabled": true,
- "group_scope": "policy",
- "window_minutes": 3,
- "window_mode": "rolling",
- "throttle_minutes": 3,
- "ack_links_enabled": true,
- "ack_link_mode": "one_click",
- "ack_resume_minutes": 0
}Response samples
- 200
- 400
- 401
- 403
{- "status": "success"
}List all integrations for the authenticated account
Returns every third-party notification channel configured on your account (Slack, Discord, Telegram, Google Chat, Gotify, ntfy, Pushover, Jira Cloud issues, GitHub Issues, GitLab Issues, Datadog, PagerDuty, Jira Service Management / Opsgenie, ilert, Splunk On-Call, SIGNL4, MS Teams, Statuspage, Mattermost, Slack-compatible webhooks, Twilio, WhatsApp, webhooks, etc.) with its id, name and type. The response includes a metadata.total count; results are not paginated.
Authorizations:
Responses
Response samples
- 200
{- "data": [
- {
- "id": 0,
- "name": "string",
- "type": "string",
- "needs_reconnect": true
}
], - "metadata": {
- "total": 0
}
}Create a new integration
Creates a third-party notification channel of the given type (slack, discord, telegram, mattermost, pagerduty, whatsapp, twilio, statuspage, etc.). Required and meaningful fields vary by type and are validated server-side; the type-specific values map onto component_id, page_id, custom_headers, additional_value_1 and settings (for example the Slack channel or the Twilio phone number). An unknown type is rejected with a validation_error. A matching alert contact is created automatically, so the response returns both the integration id and the new contact_id.
Authorizations:
Request Body schema: application/json
| name required | string Integration display name |
| type required | string Integration type (slack, discord, telegram, mattermost, pagerduty, whatsapp, twilio, statuspage, etc.) |
| api_key required | string API key or webhook URL |
| component_id | string Component ID (statuspage/twilio) |
| page_id | string Page ID (statuspage/twilio) |
| custom_headers | string Custom headers (webhook) |
| additional_value_1 | string Additional value (slack channel, twilio phone) |
| settings | string JSON object of channel settings; jira only: {"issue_type":"Task","resolve_transition":"Done","labels":["uptimia"],"priority":true}. Validated and re-serialised server-side. |
Responses
Request samples
- Payload
{- "name": "string",
- "type": "slack",
- "api_key": "string",
- "component_id": "string",
- "page_id": "string",
- "custom_headers": "string",
- "additional_value_1": "string",
- "settings": "string"
}Response samples
- 201
{- "data": {
- "id": 0,
- "contact_id": 0
}
}List all webhook integrations
Returns only the webhook-type integrations on your account, each with its target URL (api_key) and any custom headers. Use this to manage outbound HTTP notification endpoints separately from other integration types.
Authorizations:
Responses
Response samples
- 200
{- "data": [
- {
- "id": 0,
- "name": "string",
- "api_key": "string",
- "custom_headers": "string"
}
]
}Create a new webhook
Registers a new outbound webhook that Uptimia will POST to when alerts fire. The api_key field must be a valid public URL; it is validated and screened against an SSRF guard, so internal/private addresses are rejected with a 400. Optionally supply custom_headers as a JSON string to attach to each request.
Authorizations:
Request Body schema: application/json
| name required | string Webhook display name |
| api_key required | string Webhook URL |
| custom_headers | string Custom headers as JSON string |
Responses
Request samples
- Payload
{- "name": "string",
- "api_key": "string",
- "custom_headers": "string"
}Response samples
- 201
{- "data": {
- "id": 0
}
}Get a single webhook by ID
Returns the configuration of one webhook integration, including its target URL and custom headers. The integration must belong to your account and be of type webhook; a 404 is returned otherwise.
Authorizations:
path Parameters
| id required | integer\d+ |
Responses
Response samples
- 200
{- "data": {
- "id": 0,
- "name": "string",
- "api_key": "string",
- "custom_headers": "string"
}
}Delete a webhook
Permanently removes a webhook integration so it will no longer receive alert notifications. The integration must belong to your account and be of type webhook (404 otherwise); returns a 400 if deletion fails.
Authorizations:
path Parameters
| id required | integer\d+ |
Responses
Response samples
- 200
{- "status": "success"
}Update a webhook
Updates the name, target URL or custom headers of an existing webhook; omitted fields keep their current values. The integration must belong to your account and be of type webhook, otherwise a 404 is returned.
Authorizations:
path Parameters
| id required | integer\d+ |
Request Body schema: application/json
| name | string |
| api_key | string Webhook URL |
| custom_headers | string |
Responses
Request samples
- Payload
{- "name": "string",
- "api_key": "string",
- "custom_headers": "string"
}Response samples
- 200
{- "data": {
- "id": 0,
- "name": "string",
- "api_key": "string",
- "custom_headers": "string"
}
}Get a single integration by ID
Returns the full configuration of one integration of any type, including its api_key plus type-specific fields such as component_id, page_id, custom_headers and additional_value_1. The integration must belong to the authenticated account; a 404 is returned if not found.
Authorizations:
path Parameters
| id required | integer\d+ |
Responses
Response samples
- 200
{- "data": {
- "id": 0,
- "name": "string",
- "type": "string",
- "api_key": "string",
- "component_id": "string",
- "custom_headers": "string",
- "page_id": "string",
- "additional_value_1": "string"
}
}Delete an integration
Permanently removes an integration of any type along with its associated alert contacts so it no longer receives notifications. The integration must belong to your account (404 otherwise); returns a 400 if deletion fails.
Authorizations:
path Parameters
| id required | integer\d+ |
Responses
Response samples
- 200
{- "status": "success"
}Update an existing integration
Updates any field of an existing integration of any type; omitted fields retain their current values. If the integration name changes, the linked alert contact is renamed to match. The integration must belong to your account, otherwise a 404 is returned.
Authorizations:
path Parameters
| id required | integer\d+ |
Request Body schema: application/json
| name | string |
| type | string |
| api_key | string |
| component_id | string |
| page_id | string |
| custom_headers | string |
| additional_value_1 | string |
Responses
Request samples
- Payload
{- "name": "string",
- "type": "string",
- "api_key": "string",
- "component_id": "string",
- "page_id": "string",
- "custom_headers": "string",
- "additional_value_1": "string"
}Response samples
- 200
{- "data": {
- "id": 0,
- "name": "string",
- "type": "string",
- "api_key": "string",
- "component_id": "string",
- "custom_headers": "string",
- "page_id": "string",
- "additional_value_1": "string"
}
}Send a test notification for an integration
Dispatches a sample alert through the integration using its stored credentials so you can confirm the channel is wired up correctly before relying on it. The integration must belong to your account (404 otherwise); unsupported integration types or delivery failures (for example an invalid Twilio phone number) return a 400. Requires a verified account e-mail (403 email_not_verified). At most 5 tests per account every 10 minutes, shared with POST /api/v1/integrations-test/{id}; WhatsApp tests are also limited to 3 a day on free and trial plans and 5 a day per destination number across all accounts (429 rate_limited with a Retry-After header). A URL-based integration whose address fails the outbound address check answers 400 test_failed and nothing is sent.
Authorizations:
path Parameters
| id required | integer\d+ |
Responses
Response samples
- 200
{- "status": "success"
}Outbound events: subscribe a URL and Uptimia POSTs each matching event to it (Zapier, Make, n8n or your own service). See Event subscriptions in the introduction for events, payloads and signatures.
List event subscriptions
Returns every outbound event subscription on the account (Zapier hooks and API-created ones): the event, the target URL, the optional monitor filter, when it last delivered and whether it has been disabled after repeated failures. Secrets are never returned after creation.
Authorizations:
Responses
Response samples
- 200
{- "data": [
- {
- "id": 0,
- "event": "string",
- "target_url": "string",
- "monitor_type": "string",
- "monitor_id": 0,
- "source": "zapier",
- "created_at": "string",
- "last_delivered_at": "string",
- "consecutive_failures": 0,
- "disabled_at": "string"
}
]
}Subscribe a URL to an event
Creates an outbound event subscription. Every matching event is POSTed to target_url as JSON with the headers X-Uptimia-Event, X-Uptimia-Delivery and X-Uptimia-Signature (sha256= HMAC-SHA256 of the raw body with the secret returned here — it is shown once). Events: monitor.down, monitor.up, expiry.warning (ssl/domain countdowns, with kind), incident.acknowledged. Optional monitor_type and monitor_id narrow the subscription to one family or one monitor. Targets must be https and public; hooks.zapier.com targets are recorded as source zapier. Any 2xx counts as delivered; a 410 Gone (or a 404 from hooks.zapier.com) removes the subscription; other failures are retried five times with backoff (six attempts), and after 25 consecutive failed deliveries (events whose every attempt failed) the subscription is disabled; POSTing the same subscription again replaces a disabled one.
Authorizations:
Request Body schema: application/json
| event required | string Enum: "monitor.down" "monitor.up" "expiry.warning" "incident.acknowledged" |
| target_url required | string |
| monitor_type | string or null Family filter: uptime, transaction, speed, rum, ssl, domain, virus, server, heartbeat, blacklist, dns, api |
| monitor_id | integer or null One monitor of that family; omit or 0 for all |
Responses
Request samples
- Payload
{- "event": "monitor.down",
- "monitor_type": "string",
- "monitor_id": 0
}Response samples
- 201
{- "data": {
- "id": 0,
- "event": "string",
- "target_url": "string",
- "monitor_type": "string",
- "monitor_id": 0,
- "source": "zapier",
- "secret": "string",
- "created_at": "string"
}
}Recent events in the delivery payload shape (sample data)
Returns the most recent real events of one kind for the account, newest first, in exactly the JSON shape a subscription delivers — automation tools use this for sample data while a workflow is being built. Empty when the account has no such event yet.
Authorizations:
query Parameters
| event required | string Enum: "monitor.down" "monitor.up" "expiry.warning" "incident.acknowledged" |
| limit | integer <= 25 Default: 3 |
Responses
Response samples
- 200
{- "data": [
- {
- "id": 0,
- "monitor_type": "string",
- "monitor_name": "string",
- "monitor_unique_id": "string",
- "monitor_status": "down",
- "severity": "string",
- "incident_start_time": "string",
- "incident_end_time": "string",
- "incident_duration_seconds": 0,
- "message": "string",
- "monitor_notes": "string",
- "event": "string",
- "event_id": "string",
- "monitor_url": "string",
- "cp_url": "string",
- "occurred_at": "string",
- "error_reason": "string",
- "kind": "string",
- "acknowledged_by": "string"
}
]
}List all maintenance windows for the authenticated user
Returns every scheduled maintenance window belonging to the authenticated account, each with its schedule, current status, and the monitors it covers — plus a computed schedule decoration: a truthful computed_status (running/recurring/scheduled/expired/invalid), a structured schedule block, the current/next occurrence, and every concrete occurrence intersecting the requested horizon. All occurrence epochs are computed in the account timezone. The metadata object reports the total count, the account timezone, the server now, and one entry per horizon day.
Authorizations:
query Parameters
| horizon_days | integer [ 1 .. 31 ] Default: 14 How many account-local days (starting today) to expand occurrences over. Clamped to 1..31. |
Responses
Response samples
- 200
{- "data": [
- {
- "id": 0,
- "name": "string",
- "frequency_code": 0,
- "frequency": "string",
- "start": "string",
- "end": "string",
- "status": "string",
- "monitors": "string",
- "thread_id": 0,
- "computed_status": "running",
- "schedule": {
- "type": "once",
- "start_hhmm": "string",
- "end_hhmm": "string",
- "cross_midnight": true,
- "days": [
- 0
], - "weeks": [
- 0
], - "once_start": 0,
- "once_end": 0
}, - "current_occurrence": {
- "start": 0,
- "end": 0
}, - "next_occurrence": {
- "start": 0,
- "end": 0
}, - "occurrences": [
- {
- "start": 0,
- "end": 0
}
]
}
], - "metadata": {
- "total": 0,
- "timezone": {
- "code": 0,
- "name": "Europe/Vilnius",
- "offset_label": "UTC+03:00"
}, - "now": 0,
- "days": [
- {
- "date": "2026-07-08",
- "start": 0,
- "end": 0,
- "dow": 0,
- "label": "Tue, Jul 8"
}
]
}
}Create a new maintenance window
Schedules a new maintenance window during which alerts are suppressed for the selected monitors. Set type to once, daily, weekly, or monthly, and pick monitors via selection_mode (all, by_type, by_group, or individual). For a one-off window you may also publish a status page thread via status_page and status_summary. Returns the new window ID.
Authorizations:
Request Body schema: application/json
| name required | string Maintenance window name |
| type required | string Enum: "once" "daily" "weekly" "monthly" Schedule type: once, daily, weekly, monthly |
| time_start | string Start time (datetime string for once, HHMM string for recurring) |
| time_end | string End time (datetime string for once, HHMM string for recurring) |
| days | Array of strings Days (weekday numbers for weekly, day-of-month for monthly) |
| weeks | Array of strings Week numbers for weekly type |
| selection_mode | string Enum: "all" "by_type" "by_group" "individual" Monitor selection mode: all, by_type, by_group, individual |
| uptime | integer Include uptime monitors (for by_type mode) |
| ssl | integer |
| domain | integer |
| virus | integer |
| fpl | integer |
| transaction | integer |
| rum | integer |
| server | integer |
| heartbeat | integer |
| blacklist | integer |
| dns | integer |
| api | integer |
| individual_monitors | Array of strings Monitor IDs as type|id strings |
| individual_groups | Array of integers Group IDs |
| status_page | integer Create status page thread (published for both one-time and recurring windows) |
| status_summary | string Status page thread summary |
Responses
Request samples
- Payload
{- "name": "string",
- "type": "once",
- "time_start": "string",
- "time_end": "string",
- "days": [
- "string"
], - "weeks": [
- "string"
], - "selection_mode": "all",
- "uptime": 0,
- "ssl": 0,
- "domain": 0,
- "virus": 0,
- "fpl": 0,
- "transaction": 0,
- "rum": 0,
- "server": 0,
- "heartbeat": 0,
- "blacklist": 0,
- "dns": 0,
- "api": 0,
- "individual_monitors": [
- "string"
], - "individual_groups": [
- 0
], - "status_page": 0,
- "status_summary": "string"
}Response samples
- 201
{- "data": {
- "id": 0
}
}Get all monitors available for maintenance selection
Returns all of the account monitors that can be attached to a maintenance window, grouped by monitor type (uptime, ssl, domain, virus, fpl, transaction, rum, server, heartbeat, api). Use the returned IDs when building the individual_monitors selection.
Authorizations:
Responses
Response samples
- 200
{- "data": {
- "uptime": [
- {
- "id": 0,
- "name": "string"
}
], - "ssl": [
- {
- "id": 0,
- "name": "string"
}
], - "domain": [
- {
- "id": 0,
- "name": "string"
}
], - "virus": [
- {
- "id": 0,
- "name": "string"
}
], - "fpl": [
- {
- "id": 0,
- "name": "string"
}
], - "transaction": [
- {
- "id": 0,
- "name": "string"
}
], - "rum": [
- {
- "id": 0,
- "name": "string"
}
], - "server": [
- {
- "id": 0,
- "name": "string"
}
], - "heartbeat": [
- {
- "id": 0,
- "name": "string"
}
], - "blacklist": [
- {
- "id": 0,
- "name": "string"
}
], - "dns": [
- {
- "id": 0,
- "name": "string"
}
], - "api": [
- {
- "id": 0,
- "name": "string"
}
]
}
}Preview the concrete occurrences of a maintenance draft without saving it
Validates a create/update payload with the exact same schedule rules as POST/PATCH and, when valid, expands the draft into its next occurrences (up to 5, computed in the account timezone over the next 400 days) plus the first overlap with any other maintenance window. Name/title/summary problems never block a preview — they are returned as non-blocking warnings, because the preview concerns the schedule, not the announcement. Always responds 200; inspect data.valid.
Authorizations:
Request Body schema: application/json
| name | string Maintenance window name (missing/short name only produces a warning here) |
| type required | string Enum: "once" "daily" "weekly" "monthly" |
| time_start | string Start time (datetime string for once, HHMM string for recurring) |
| time_end | string End time (datetime string for once, HHMM string for recurring; next-day ends encoded as hour+24, e.g. 2630) |
| days | Array of strings Days (weekday numbers 1-7 for weekly, day-of-month 1-31 for monthly). Required for weekly/monthly — an empty selection, or a value outside those ranges, makes the preview invalid with invalid_days |
| weeks | Array of strings Week of the month (1-5) for weekly type; anything outside that range makes the preview invalid with invalid_weeks |
| selection_mode | string Enum: "all" "by_type" "by_group" "individual" Monitor selection mode. Defaults to all when omitted. The same selection rule create/PATCH apply is applied here (see the server flag), but the selection does not change the computed occurrences, and preview does not run the group-scope confinement create/PATCH run — a scoped member can preview a selection those endpoints would reject |
| uptime | integer Include uptime monitors (for by_type mode) |
| ssl | integer |
| domain | integer |
| virus | integer |
| fpl | integer |
| transaction | integer |
| rum | integer |
| server | integer Accepted for request-body symmetry only: a truthy server flag with selection_mode=by_type makes the preview invalid with invalid_selection, because the maintenance table has no server by-type column. Attach servers through individual selection instead |
| heartbeat | integer |
| blacklist | integer |
| dns | integer |
| api | integer |
| individual_monitors | Array of strings Monitor IDs as type|id strings (for individual mode) |
| individual_groups | Array of integers Group IDs (for by_group mode) |
| status_page | integer Publish a status page thread. Set to 1 to have the announcement rules checked — they come back as non-blocking warnings (invalid_title, invalid_summary), never as an invalid preview |
| status_summary | string Status page thread summary; under 10 characters produces the invalid_summary warning when status_page is 1 |
| status_page_ids | Array of integers Status page IDs the thread should be published to. Accepted for symmetry with create/PATCH — it travels through the same payload builder, but the preview only expands the schedule, so it changes nothing in the response |
| exclude_id | integer Window id to exclude from the overlap scan (edit mode: the window being edited) |
Responses
Request samples
- Payload
{- "name": "Nightly database failover",
- "type": "weekly",
- "time_start": "0130",
- "time_end": "0300",
- "days": [
- "string"
], - "weeks": [
- "string"
], - "selection_mode": "by_type",
- "uptime": 1,
- "ssl": 0,
- "domain": 0,
- "virus": 0,
- "fpl": 0,
- "transaction": 0,
- "rum": 0,
- "server": 0,
- "heartbeat": 0,
- "blacklist": 0,
- "dns": 0,
- "api": 0,
- "individual_monitors": [
- "uptime|91234"
], - "individual_groups": [
- 77
], - "status_page": 1,
- "status_summary": "We are failing the primary database over to its replica.",
- "status_page_ids": [
- 314
], - "exclude_id": 5120
}Response samples
- 200
{- "data": {
- "valid": true,
- "error_code": "string",
- "message": "string",
- "occurrences": [
- {
- "start": 0,
- "end": 0
}
], - "overlap": {
- "id": 0,
- "name": "string",
- "at": 0
}, - "timezone": {
- "name": "Europe/Vilnius",
- "offset_label": "UTC+03:00"
}, - "warnings": [
- {
- "code": "string",
- "message": "string"
}
]
}
}Get a single maintenance window with full details
Returns the full configuration of one maintenance window, including its schedule type, start/end times, recurrence days and weeks, monitor selection, and whether the window is currently running. Returns 404 if the window does not exist or does not belong to your account.
Authorizations:
path Parameters
| id required | integer\d+ |
Responses
Response samples
- 200
{- "data": {
- "id": 0,
- "name": "string",
- "type": "string",
- "time_start": "string",
- "time_end": "string",
- "days": "string",
- "weeks": "string",
- "all_monitors": 0,
- "groups": 0,
- "uptime": 0,
- "fpl": 0,
- "ssl": 0,
- "domain": 0,
- "virus": 0,
- "transaction": 0,
- "rum": 0,
- "server": 0,
- "heartbeat": 0,
- "blacklist": 0,
- "dns": 0,
- "api": 0,
- "individual_monitors": "string",
- "individual_groups": "string",
- "selection_mode": "all",
- "individual_monitors_list": [
- "string"
], - "individual_groups_list": [
- "string"
], - "days_list": [
- 0
], - "weeks_list": [
- 0
], - "is_maintenance_running": true,
- "is_maintenance_temporary": true,
- "status_page": 0,
- "status_summary": "string"
}
}Delete a maintenance window and revert affected monitors
Permanently deletes a maintenance window and reverts any monitors currently held in maintenance by it back to active checking. Returns 404 if the window does not exist or does not belong to your account.
Authorizations:
path Parameters
| id required | integer\d+ |
Responses
Response samples
- 200
{- "status": "success"
}Update an existing maintenance window
Replaces the configuration of an existing maintenance window with the supplied schedule and monitor selection, then returns the updated window (same shape as GET /{id}, including the additive selection_mode and *_list array fields). Returns 404 if the window does not exist or does not belong to your account.
Authorizations:
path Parameters
| id required | integer\d+ |
Request Body schema: application/json
| name | string |
| type | string Enum: "once" "daily" "weekly" "monthly" |
| time_start | string |
| time_end | string |
| days | Array of strings |
| weeks | Array of strings |
| selection_mode | string Enum: "all" "by_type" "by_group" "individual" |
| uptime | integer |
| ssl | integer |
| domain | integer |
| virus | integer |
| fpl | integer |
| transaction | integer |
| rum | integer |
| server | integer |
| heartbeat | integer |
| blacklist | integer |
| dns | integer |
| api | integer |
| individual_monitors | Array of strings |
| individual_groups | Array of integers |
| status_page | integer |
| status_summary | string |
Responses
Request samples
- Payload
{- "name": "string",
- "type": "once",
- "time_start": "string",
- "time_end": "string",
- "days": [
- "string"
], - "weeks": [
- "string"
], - "selection_mode": "all",
- "uptime": 0,
- "ssl": 0,
- "domain": 0,
- "virus": 0,
- "fpl": 0,
- "transaction": 0,
- "rum": 0,
- "server": 0,
- "heartbeat": 0,
- "blacklist": 0,
- "dns": 0,
- "api": 0,
- "individual_monitors": [
- "string"
], - "individual_groups": [
- 0
], - "status_page": 0,
- "status_summary": "string"
}Delete all expired one-time maintenance windows
Permanently deletes every one-time (type once) maintenance window of the authenticated account whose end time is in the past. Unlike deleting a single window, resolved status-page threads and their history (updates and page targeting) are PRESERVED; only unresolved announcement threads are removed together with the windows. Returns how many windows were deleted.
Authorizations:
Responses
Response samples
- 200
{- "data": {
- "deleted": 0
}
}List all status pages for the authenticated account
Returns every public status page owned by the authenticated account, each with its display name, link slug, custom domain, visibility toggles and custom CSS/JS/HTML. The response includes a metadata.total count.
Authorizations:
Responses
Response samples
- 200
{- "data": [
- {
- "id": 0,
- "name": "string",
- "status_page_link_name": "string",
- "custom_domain_name": "string",
- "is_incident_history_on": 0,
- "is_current_incidents_on": 0,
- "is_timezone_utc": 0,
- "is_scheduled_maintenances_on": 0,
- "is_footer_link_on": 0,
- "is_past_maintenances_on": 0,
- "custom_css": "string",
- "custom_js": "string",
- "custom_html": "string"
}
], - "metadata": {
- "total": 0
}
}Create a new status page
Creates a new public status page for the account from the supplied name, URL slug, optional custom domain, visibility toggles, custom styling/logo and section structure (sections each listing the monitors they display). Returns the new page ID on success, or a 400 validation error.
Authorizations:
Request Body schema: application/json
| name required | string Status page display name |
| status_page_link_name required | string URL slug for the status page |
| custom_domain_name | string Custom CNAME domain |
| is_incident_history_on | integer Enum: 0 1 Show incident history |
| is_current_incidents_on | integer Enum: 0 1 Show current incidents |
| is_scheduled_maintenances_on | integer Enum: 0 1 Show scheduled maintenances |
| is_past_maintenances_on | integer Enum: 0 1 Show past maintenances |
| is_footer_link_on | integer Enum: 0 1 Show footer link |
| is_timezone_utc | integer Enum: 0 1 Use UTC timezone |
| custom_css | string Custom CSS |
| custom_js | string Custom JavaScript |
| custom_html | string Custom HTML |
| custom_logo | string Custom logo URL or base64 |
required | Array of objects Page sections with monitors |
Responses
Request samples
- Payload
{- "name": "string",
- "status_page_link_name": "string",
- "custom_domain_name": "string",
- "is_incident_history_on": 0,
- "is_current_incidents_on": 0,
- "is_scheduled_maintenances_on": 0,
- "is_past_maintenances_on": 0,
- "is_footer_link_on": 0,
- "is_timezone_utc": 0,
- "custom_css": "string",
- "custom_js": "string",
- "custom_html": "string",
- "custom_logo": "string",
- "structure": [
- {
- "name": "string",
- "monitors": [
- {
- "id": 0,
- "type": "string"
}
]
}
]
}Response samples
- 201
- 400
{- "data": {
- "id": 0
}
}Count the confirmed subscribers who would actually be emailed
Computes how many confirmed email subscribers would be notified for the affected monitor/incident across every owned status page that displays it, mirroring the incident notification fan-out. Only pages with subscriptions enabled (subscribe_enabled=1) contribute; pages with subscriptions off count 0 so the figure reflects who is ACTUALLY emailed. Scoped to the authenticated account.
Authorizations:
query Parameters
| status_page_id | integer Restrict to a single owned status page |
| monitor_type | string Enum: "uptime" "fpl" "transaction" Affected monitor type |
| monitor_id | integer Affected monitor (site) ID |
| incident_id | integer Affected incident ID |
| thread_id | integer Reach of the next update on this existing thread, resolved the way it is delivered: a general thread's own page; otherwise its explicit target pages, or every page listing one of its monitors when it has none. Takes precedence over every other parameter. |
| status_page_ids[] | Array of integers Explicit pages a new thread will be published to (its 'Publish to' set); delivery honours these over monitor membership. Pages you do not own are ignored. Takes precedence over status_page_id and the monitor/incident parameters. A comma-separated |
Responses
Response samples
- 200
{- "total": 0,
- "pages": [
- {
- "id": 0,
- "name": "string",
- "confirmed": 0
}
], - "hidden_by_impact": true
}Get a single status page with settings and structure
Retrieves one status page by ID, including all of its settings, visibility toggles, custom styling and its section/monitor structure. The page must belong to the authenticated account; returns 404 if it does not exist or is not yours.
Authorizations:
path Parameters
| id required | integer\d+ |
Responses
Response samples
- 200
- 404
{- "data": {
- "id": 0,
- "name": "string",
- "status_page_link_name": "string",
- "custom_domain_name": "string",
- "is_incident_history_on": 0,
- "is_current_incidents_on": 0,
- "is_timezone_utc": 0,
- "is_scheduled_maintenances_on": 0,
- "is_footer_link_on": 0,
- "is_past_maintenances_on": 0,
- "custom_css": "string",
- "custom_js": "string",
- "custom_html": "string",
- "structure": [
- {
- "name": "string",
- "description": "string",
- "monitors": [
- {
- "id": 0,
- "type": "string"
}
]
}
], - "has_custom_logo": true,
- "custom_logo_url": "string"
}
}Delete a status page
Permanently deletes the status page identified by ID along with its configuration. The page must belong to the authenticated account. This action cannot be undone and returns a 400 error if the deletion fails.
Authorizations:
path Parameters
| id required | integer\d+ |
Responses
Response samples
- 200
- 400
{- "status": "success"
}Update an existing status page
Partially updates a status page; any omitted field keeps its current value, which is merged from the stored record before saving. A logo may be supplied either as a multipart file upload or via the custom_logo JSON field. The page must belong to the authenticated account; returns 404 if not found.
Authorizations:
path Parameters
| id required | integer\d+ |
Request Body schema: application/json
| name | string |
| status_page_link_name | string |
| custom_domain_name | string |
| is_incident_history_on | integer Enum: 0 1 |
| is_current_incidents_on | integer Enum: 0 1 |
| is_scheduled_maintenances_on | integer Enum: 0 1 |
| is_past_maintenances_on | integer Enum: 0 1 |
| is_footer_link_on | integer Enum: 0 1 |
| is_timezone_utc | integer Enum: 0 1 |
| custom_css | string |
| custom_js | string |
| custom_html | string |
| custom_logo | string |
Array of objects |
Responses
Request samples
- Payload
{- "name": "string",
- "status_page_link_name": "string",
- "custom_domain_name": "string",
- "is_incident_history_on": 0,
- "is_current_incidents_on": 0,
- "is_scheduled_maintenances_on": 0,
- "is_past_maintenances_on": 0,
- "is_footer_link_on": 0,
- "is_timezone_utc": 0,
- "custom_css": "string",
- "custom_js": "string",
- "custom_html": "string",
- "custom_logo": "string",
- "structure": [
- {
- "name": "string",
- "monitors": [
- {
- "id": 0,
- "type": "string"
}
]
}
]
}Response samples
- 200
- 400
{- "data": {
- "id": 0,
- "name": "string"
}
}List threads filtered by resolved status
Returns the account's status threads (incidents, scheduled maintenances and general announcements shown on status pages), filtered by the is_resolved query flag (0 = active, 1 = resolved). Each entry summarises the thread and how many status updates it has, with a metadata.total count.
Authorizations:
query Parameters
| is_resolved | integer Default: 0 Enum: 0 1 0 = active, 1 = resolved |
Responses
Response samples
- 200
{- "data": [
- {
- "id": 0,
- "title": "string",
- "thread_type": "string",
- "summary": "string",
- "current_status": "string",
- "is_resolved": 0,
- "time_created": 0,
- "time_updated": 0,
- "status_updates_posted": 0,
- "status_page_id": 0,
- "status_page_ids": [
- 0
], - "effective_page_ids": [
- 0
]
}
], - "metadata": {
- "total": 0
}
}Create a new thread
Opens a new status thread of the chosen type (general, incident or maintenance) with a title, summary and a first status update. Depending on the type you must reference the relevant status_page_id, incident_id or maintenance_id and may list affected monitors. Returns the new thread ID, or a 400 validation error.
Authorizations:
Request Body schema: application/json
| thread_type required | string Enum: "general" "incident" "maintenance" Thread type |
| title required | string Thread title (min 5 chars) |
| summary required | string Thread summary (min 10 chars) |
| initial_status_update required | string Initial status update message (min 5 chars) |
| status_page_id | integer Status page ID (for general type) |
| incident_id | integer Incident ID (for incident type) |
| monitor_type | string Enum: "uptime" "speed" "transaction" Monitor type (for incident type) |
| maintenance_id | integer Maintenance ID (for maintenance type) |
Array of objects Affected monitors | |
| next_update_at | integer "Next update by" ETA for the initial update (epoch seconds; 0 or omit = none) |
Responses
Request samples
- Payload
{- "thread_type": "general",
- "title": "string",
- "summary": "string",
- "initial_status_update": "string",
- "status_page_id": 0,
- "incident_id": 0,
- "monitor_type": "uptime",
- "maintenance_id": 0,
- "monitors": [
- {
- "id": 0,
- "type": "string"
}
], - "next_update_at": 0
}Response samples
- 201
- 400
{- "data": {
- "id": 0
}
}Find the existing thread for a monitor or incident
Looks up whether a status thread already exists for the given monitor (monitor_type + monitor_id) and/or incident (incident_id), so the control panel can offer to continue posting to it instead of opening a duplicate. An UNRESOLVED thread is preferred; if none is open the most recent RESOLVED thread for the same incident/monitor is returned with resolved=true so the client can offer to reopen it. Returns thread=null, resolved=false when nothing matches. Scoped to the authenticated account.
Authorizations:
query Parameters
| monitor_type | string Enum: "uptime" "fpl" "transaction" Monitor type of an incident thread |
| monitor_id | integer Monitor (site) ID |
| incident_id | integer Incident ID |
Responses
Response samples
- 200
{- "data": {
- "thread": {
- "id": 0,
- "title": "string",
- "thread_type": "string",
- "severity": "minor",
- "summary": "string",
- "current_status": "string",
- "is_resolved": 0,
- "time_created": 0,
- "time_updated": 0,
- "incident_id": 0,
- "monitor_type": "string",
- "status_page_id": 0,
- "status_updates_posted": 0,
- "effective_page_ids": [
- 0
]
}, - "resolved": true
}
}Get a single thread with all updates
Retrieves one status thread by ID together with its full chronological list of status updates (each with status, message and timestamp). The thread must belong to the authenticated account; returns 404 if not found.
Authorizations:
path Parameters
| id required | integer\d+ |
Responses
Response samples
- 200
- 404
{- "data": {
- "id": 0,
- "title": "string",
- "thread_type": "string",
- "summary": "string",
- "current_status": "string",
- "is_resolved": 0,
- "time_created": 0,
- "time_updated": 0,
- "updates": [
- {
- "id": 0,
- "status": "string",
- "message": "string",
- "time": 0,
- "next_update_at": 0
}
]
}
}Delete a thread and all its updates
Permanently deletes a status thread together with every status update posted to it. The thread must belong to the authenticated account. This action cannot be undone and returns a 400 error if it fails.
Authorizations:
path Parameters
| id required | integer\d+ |
Responses
Response samples
- 200
- 400
{- "status": "success"
}Update an existing thread
Updates a status thread's title, summary and the list of affected monitors. To change the thread status post a new status update instead. The thread must belong to the authenticated account; returns 404 if not found.
Authorizations:
path Parameters
| id required | integer\d+ |
Request Body schema: application/json
| title | string Thread title (min 5 chars) |
| summary | string Thread summary (min 10 chars) |
| status_page_id | integer Status page ID (required for general threads) |
Array of objects Affected monitors |
Responses
Request samples
- Payload
{- "title": "string",
- "summary": "string",
- "status_page_id": 0,
- "monitors": [
- {
- "id": 0,
- "type": "string"
}
]
}Response samples
- 200
- 400
{- "status": "success"
}Post a status update to a thread
Appends a new status update to the thread identified by threadId, advancing its current status (for example investigating, monitoring or resolved) with an accompanying message. The parent thread must belong to the authenticated account. Returns the new update ID, or a 400 validation error.
Authorizations:
path Parameters
| threadId required | integer\d+ |
Request Body schema: application/json
| status required | string Enum: "open" "acknowledged" "investigating" "in_progress" "monitoring" "resolved" "scheduled" "completed" "update" "closed" Status progression value |
| message required | string Update message (min 10 chars) |
| next_update_at | integer "Next update by" ETA for this update (epoch seconds; 0 or omit = none) |
Responses
Request samples
- Payload
{- "status": "open",
- "message": "string",
- "next_update_at": 0
}Response samples
- 201
- 400
{- "data": {
- "id": 0
}
}Delete a single status update
Removes one status update from its thread without deleting the thread itself. The update must belong to a thread owned by the authenticated account. This action cannot be undone and returns a 400 error if it fails.
Authorizations:
path Parameters
| id required | integer\d+ |
Responses
Response samples
- 200
- 400
{- "status": "success"
}Edit an existing status update
Edits a previously posted status update, changing its status and message. You must pass the parent thread_id alongside the update ID. The update must belong to a thread owned by the authenticated account.
Authorizations:
path Parameters
| id required | integer\d+ |
Request Body schema: application/json
| thread_id required | integer Parent thread ID |
| status required | string Status value |
| message required | string Update message (min 10 chars) |
| next_update_at | integer "Next update by" ETA for this update (epoch seconds; 0 or omit = none) |
Responses
Request samples
- Payload
{- "thread_id": 0,
- "status": "string",
- "message": "string",
- "next_update_at": 0
}Response samples
- 200
- 400
{- "status": "success"
}Get presentation settings for a status page
Returns the default theme and subscribe toggle for one owned status page.
Authorizations:
path Parameters
| id required | integer\d+ |
Responses
Response samples
- 200
{- "data": {
- "default_theme": "system",
- "subscribe_enabled": 0,
- "show_name_with_logo": 0,
- "visibility": "public",
- "has_password": true,
- "access_ip_allowlist": "string",
- "password_protection_allowed": true
}
}Update presentation settings for a status page
Sets the presentation settings of one owned status page — default theme, whether the public Subscribe affordance is shown, whether the page name is shown next to the logo — and its access control (public, shared password, or private IP allowlist). Note that the two toggles are absolute, not partial: a body that omits subscribe_enabled or show_name_with_logo stores 0 for it, so send the full pair. Access control is the exception — omitting visibility keeps the current mode. Establishing or rotating protection needs an eligible plan (422 upgrade_required); removing it never does. Turning subscribe_enabled on (0 to 1) needs a verified account e-mail (422 email_not_verified); a stored 1 is kept on save; on free and trial plans it stays inert, with no Subscribe button and no subscriber mail, while the account e-mail is unverified, and on the expired free plan it always does. The response echoes the stored settings in the same shape as GET /status-pages/{id}/settings.
Authorizations:
path Parameters
| id required | integer\d+ Example: 314 Status page ID |
Request Body schema: application/json
| default_theme | string Enum: "system" "light" "dark" Anything other than system, light or dark is stored as system |
| subscribe_enabled | integer Enum: 0 1 Any falsy value stores 0; anything else stores 1. Omitting the key stores 0 |
| show_name_with_logo | integer Enum: 0 1 Any falsy value stores 0; anything else stores 1. Omitting the key stores 0 |
| visibility | string Enum: "public" "password" "private" public removes protection exactly like clear_password. Omit the key to edit the current protected mode in place (rotate the password, change the allowlist); an unrecognised value behaves like omitting it |
| password | string New shared password (>=10 chars) when visibility=password; omit to keep the current one. Setting or rotating it signs every current visitor out |
| clear_password | boolean Remove protection (revert to public + revoke grants). Always permitted, even on a plan that could not re-establish it |
| access_ip_allowlist | string Newline/comma-separated IPs/CIDRs required when visibility=private. Stored de-duplicated and newline-joined |
Responses
Request samples
- Payload
{- "default_theme": "dark",
- "subscribe_enabled": 1,
- "show_name_with_logo": 0,
- "visibility": "password",
- "password": "winter-harbour-42",
- "clear_password": false,
- "access_ip_allowlist": "203.0.113.4\n198.51.100.0/24"
}Response samples
- 200
- 401
- 404
- 422
{- "data": {
- "default_theme": "dark",
- "subscribe_enabled": 1,
- "show_name_with_logo": 0,
- "visibility": "password",
- "has_password": true,
- "access_ip_allowlist": "203.0.113.4\n198.51.100.0/24",
- "password_protection_allowed": true
}
}List email subscribers for a status page
Returns the email subscribers of one owned status page with their confirmation state, plus a metadata.total / metadata.confirmed count. Unconfirmed (pending double opt-in) subscribers are included; the whole list is returned, unpaginated.
Authorizations:
path Parameters
| id required | integer\d+ Example: 314 Status page ID |
Responses
Response samples
- 200
- 401
- 404
{- "data": [
- {
- "id": 4821,
- "is_confirmed": 1,
- "created_at": 1751932800,
- "confirmed_at": 1751933105
}
], - "metadata": {
- "total": 12,
- "confirmed": 9
}
}Remove an email subscriber from a status page
Unsubscribes one email address from an owned status page: the subscriber row is deleted outright, so the address stops receiving incident and maintenance notifications and would have to opt in again. The deletion is scoped to the page, so a subscriberId belonging to another status page is never touched. The call is idempotent — an unknown or already-removed subscriberId still returns 200 with status "success"; only an unknown or non-owned status page id produces a 404. This action cannot be undone.
Authorizations:
path Parameters
| id required | integer\d+ Example: 314 Status page ID |
| subscriberId required | integer\d+ Example: 4821 Subscriber ID as returned by GET /status-pages/{id}/subscribers |
Responses
Response samples
- 200
- 401
- 404
{- "status": "success"
}List all scheduled reports for the authenticated account
Returns the recurring email reports configured on your account, each with its frequency (and a human-readable frequency_label), last/next send timestamps, attachment options and monitor-scope flags. One-time temporary reports are excluded; the response includes a metadata.total count and is not paginated.
Authorizations:
Responses
Response samples
- 200
{- "data": [
- {
- "id": 0,
- "name": "string",
- "frequency": 0,
- "frequency_label": "string",
- "temporary": 0,
- "last_sent": 0,
- "next_send": 0,
- "show_all_incidents": 0,
- "show_maintenances": 0,
- "attach_pdf": 0,
- "attach_html": 0,
- "attach_csv": 0,
- "theme_id": 0,
- "all_monitors": 0,
- "all_uptime": 0,
- "all_ssl": 0,
- "all_fpl": 0,
- "all_rum": 0,
- "all_domain": 0,
- "all_virus": 0,
- "all_transaction": 0
}
], - "metadata": {
- "total": 0
}
}Create a new scheduled report
Creates a recurring email report delivered on the chosen frequency (1=Daily, 7=Weekly, 30=Monthly, 90=Quarterly, 365=Yearly). Choose monitors via the per-type all_* flags or by selecting specific monitors, set which contacts receive it, and toggle PDF/HTML/CSV attachments and incident/maintenance inclusion. Contacts are validated for ownership; invalid frequencies or contacts return a 400.
Authorizations:
Request Body schema: application/json
| name required | string Report name |
| frequency required | integer Frequency: 1=Daily, 7=Weekly, 30=Monthly, 90=Quarterly, 365=Yearly |
| theme | integer Branding theme ID |
| all_monitors | integer Include all monitors |
| all_uptime | integer |
| all_fpl | integer |
| all_rum | integer |
| all_ssl | integer |
| all_domain | integer |
| all_virus | integer |
| all_transaction | integer |
| monitors_uptime | Array of integers Specific uptime monitor IDs to include (when not using all_uptime). |
| monitors_ssl | Array of integers Specific SSL monitor IDs to include. |
| monitors_domain | Array of integers Specific domain monitor IDs to include. |
| monitors_fpl | Array of integers Specific speed (full page load) monitor IDs to include. The type key for speed monitors is "fpl". |
| monitors_transaction | Array of integers Specific transaction monitor IDs to include. |
| monitors_virus | Array of integers Specific virus monitor IDs to include. |
| monitors_rum | Array of integers Specific RUM monitor IDs to include. |
| monitors_blacklist | Array of integers Specific blacklist monitor IDs to include. There is no all_blacklist toggle — blacklist monitors are picked individually or covered by all_monitors. |
| show_all_incidents | integer |
| show_maintenances | integer |
| attach_pdf | integer |
| attach_html | integer |
| attach_csv | integer |
Array of objects |
Responses
Request samples
- Payload
{- "name": "string",
- "frequency": 7,
- "theme": 0,
- "all_monitors": 1,
- "all_uptime": 0,
- "all_fpl": 0,
- "all_rum": 0,
- "all_ssl": 0,
- "all_domain": 0,
- "all_virus": 0,
- "all_transaction": 0,
- "monitors_uptime": [
- 0
], - "monitors_ssl": [
- 0
], - "monitors_domain": [
- 0
], - "monitors_fpl": [
- 0
], - "monitors_transaction": [
- 0
], - "monitors_virus": [
- 0
], - "monitors_rum": [
- 0
], - "monitors_blacklist": [
- 0
], - "show_all_incidents": 0,
- "show_maintenances": 0,
- "attach_pdf": 0,
- "attach_html": 0,
- "attach_csv": 0,
- "contacts": [
- {
- "id": 0,
- "is_team": 0,
- "is_user": 0,
- "is_operator": 0,
- "is_individual_contact": 0
}
]
}Response samples
- 201
- 400
{- "data": {
- "id": 0
}
}Get a single report with monitors and contacts
Returns one scheduled report including its full settings plus the expanded list of monitors it covers and the contacts (individual recipients, teams, users and operators) it is delivered to. Monitors and contacts are resolved only when they belong to the report owner. The report must belong to the authenticated account; a 404 is returned if not found.
Authorizations:
path Parameters
| id required | integer\d+ |
Responses
Response samples
- 200
- 404
{- "data": {
- "id": 0,
- "name": "string",
- "frequency": 0,
- "frequency_label": "string",
- "temporary": 0,
- "last_sent": 0,
- "next_send": 0,
- "show_all_incidents": 0,
- "show_maintenances": 0,
- "attach_pdf": 0,
- "attach_html": 0,
- "attach_csv": 0,
- "theme_id": 0,
- "all_monitors": 0,
- "all_uptime": 0,
- "all_ssl": 0,
- "all_fpl": 0,
- "all_rum": 0,
- "all_domain": 0,
- "all_virus": 0,
- "all_transaction": 0,
- "monitors": [
- {
- "id": 0,
- "type": "string",
- "name": "string"
}
], - "contacts": [
- {
- "id": 0,
- "name": "string",
- "type": 0,
- "is_team": 0,
- "is_user": 0,
- "is_operator": 0,
- "is_individual_contact": 0
}
]
}
}Delete a scheduled report
Permanently deletes a scheduled report along with its monitor and contact associations so it stops being sent. The report must belong to the authenticated account; a 404 is returned if not found.
Authorizations:
path Parameters
| id required | integer\d+ |
Responses
Response samples
- 200
{- "status": "success"
}Update an existing scheduled report
Updates the settings, monitor selection, contacts and attachment options of an existing scheduled report. The report must belong to your account (404 otherwise); supplied contacts are validated for ownership and an invalid set returns a 400.
Authorizations:
path Parameters
| id required | integer\d+ |
Request Body schema: application/json
| name | string |
| frequency | integer |
| theme | integer |
| all_monitors | integer |
| all_uptime | integer |
| all_fpl | integer |
| all_rum | integer |
| all_ssl | integer |
| all_domain | integer |
| all_virus | integer |
| all_transaction | integer |
| monitors_uptime | Array of integers Specific uptime monitor IDs to include (when not using all_uptime). |
| monitors_ssl | Array of integers Specific SSL monitor IDs to include. |
| monitors_domain | Array of integers Specific domain monitor IDs to include. |
| monitors_fpl | Array of integers Specific speed (full page load) monitor IDs to include. The type key for speed monitors is "fpl". |
| monitors_transaction | Array of integers Specific transaction monitor IDs to include. |
| monitors_virus | Array of integers Specific virus monitor IDs to include. |
| monitors_rum | Array of integers Specific RUM monitor IDs to include. |
| monitors_blacklist | Array of integers Specific blacklist monitor IDs to include. There is no all_blacklist toggle — blacklist monitors are picked individually or covered by all_monitors. |
| show_all_incidents | integer |
| show_maintenances | integer |
| attach_pdf | integer |
| attach_html | integer |
| attach_csv | integer |
Array of objects |
Responses
Request samples
- Payload
{- "name": "string",
- "frequency": 0,
- "theme": 0,
- "all_monitors": 0,
- "all_uptime": 0,
- "all_fpl": 0,
- "all_rum": 0,
- "all_ssl": 0,
- "all_domain": 0,
- "all_virus": 0,
- "all_transaction": 0,
- "monitors_uptime": [
- 0
], - "monitors_ssl": [
- 0
], - "monitors_domain": [
- 0
], - "monitors_fpl": [
- 0
], - "monitors_transaction": [
- 0
], - "monitors_virus": [
- 0
], - "monitors_rum": [
- 0
], - "monitors_blacklist": [
- 0
], - "show_all_incidents": 0,
- "show_maintenances": 0,
- "attach_pdf": 0,
- "attach_html": 0,
- "attach_csv": 0,
- "contacts": [
- {
- "id": 0,
- "is_team": 0,
- "is_user": 0,
- "is_operator": 0,
- "is_individual_contact": 0
}
]
}Response samples
- 200
{- "data": {
- "id": 0
}
}List branding themes available for reports
Returns the branding themes (logo/colour presets) defined on your account that can be applied to report emails and exports via the theme field when creating or updating a report.
Authorizations:
Responses
Response samples
- 200
{- "data": [
- {
- "id": 0,
- "name": "string"
}
]
}Send a report email immediately
Generates the report on demand and emails it right away to each of its email recipients that is verified and has not opted out (free and trial accounts: only the account's own login address and addresses proven through contact verification, at most 10 distinct addresses per send with the account's own address first, and at most 50 report recipients per rolling 24 hours across scheduled and on-demand reports), honouring the report's configured PDF/HTML/CSV attachments, then updates its last_sent timestamp. Your account email must be verified (403 email_not_verified otherwise), the report must belong to you (404 otherwise), and a temporary one-time report is removed after sending. Refused with 403 reports_unsubscribed when the account unsubscribed from Uptimia e-mails, and with 429 rate_limited (Retry-After header) after 5 on-demand sends per rolling 24 hours on free and trial plans or 50 on paid plans — a budget shared with POST /api/v2/reports/custom email delivery. Returns the number of emails actually sent.
Authorizations:
path Parameters
| id required | integer\d+ |
Responses
Response samples
- 200
{- "status": "success",
- "emails_sent": 0
}Download report as PDF
Generates the report on demand and returns it as a downloadable PDF file (application/pdf) rather than emailing it. The report must belong to your account (404 otherwise), a report with no data returns a 400, and a temporary one-time report is removed after the export.
Authorizations:
path Parameters
| id required | integer\d+ |
Responses
Download report as HTML
Generates the report on demand and returns it as a downloadable standalone HTML file (text/html). The report must belong to your account (404 otherwise), a report with no data returns a 400, and a temporary one-time report is removed after the export.
Authorizations:
path Parameters
| id required | integer\d+ |
Responses
Download report as CSV
Generates the report on demand and returns its data as a downloadable CSV file (text/csv) for use in spreadsheets. The report must belong to your account (404 otherwise), a report with no data returns a 400, and a temporary one-time report is removed after the export.
Authorizations:
path Parameters
| id required | integer\d+ |
Responses
Generate a public link for a report
Renders the report to a static HTML page saved under /reports and returns a shareable URL that anyone with the link can view without authenticating. The report must belong to your account (404 otherwise) and a report with no data returns a 400; a temporary one-time report is removed once the page is generated.
Authorizations:
path Parameters
| id required | integer\d+ |
Responses
Response samples
- 200
{- "data": {
- "url": "string"
}
}Create and deliver a custom one-time report
Builds an ad-hoc report for an explicit date range and monitor/contact selection and delivers it in one call according to the delivery field: email it to the chosen contacts, return it as a downloadable file in the requested format (pdf, html or csv), or publish it as a public link. Email delivery requires a verified account email (403 email_not_verified otherwise), mails only verified recipients that have not opted out (free and trial accounts: only the account's own login address and addresses proven through contact verification, at most 10 distinct addresses with the account's own address first, and at most 50 report recipients per rolling 24 hours across scheduled and on-demand reports), is refused with 403 reports_unsubscribed when the account unsubscribed from Uptimia e-mails, and with 429 rate_limited (Retry-After header) after 5 on-demand sends per rolling 24 hours on free and trial plans or 50 on paid plans, a budget shared with POST /api/v2/reports/{id}/send; download and public_link are not limited. The temporary report is removed after delivery and the response returns its id plus a url when a public link was generated.
Authorizations:
Request Body schema: application/json
| name | string Report name (defaults to "Custom Report" when omitted) |
| date_started | integer Start date as YYYYMMDD integer |
| date_ended | integer End date as YYYYMMDD integer |
Array of objects | |
Array of objects | |
| show_all_incidents | integer |
| show_maintenances | integer |
| delivery | string Enum: "email" "download" "public_link" |
| format | string Enum: "pdf" "html" "csv" Export format for download delivery |
| all_monitors | integer |
| theme | integer |
Responses
Request samples
- Payload
{- "name": "Custom Report",
- "date_started": 20240301,
- "date_ended": 20240331,
- "monitors": [
- {
- "id": 0,
- "type": "uptime"
}
], - "contacts": [
- {
- "id": 0,
- "is_team": 0,
- "is_user": 0,
- "is_operator": 0,
- "is_individual_contact": 0
}
], - "show_all_incidents": 1,
- "show_maintenances": 0,
- "delivery": "email",
- "format": "pdf",
- "all_monitors": 0,
- "theme": 0
}Response samples
- 201
{- "data": {
- "id": 0,
- "url": "string"
}
}List all teams for the authenticated account
Returns every alerting team owned by the authenticated account, including each team members, alerting-hours window, do-not-alert days, and whether the team overrides individual members alerting settings. The metadata object reports the total count.
Authorizations:
Responses
Response samples
- 200
{- "data": [
- {
- "id": 0,
- "name": "string",
- "alerted_for": "string",
- "alerted_for_count": 0,
- "total_monitors": 0,
- "alerting_times": "string",
- "team_members_raw": [
- 0
], - "team_members_names": "string",
- "is_alerting_settings_override": true,
- "do_not_alert_days": [
- "string"
], - "alert_hours_from": 0,
- "alert_hours_to": 0
}
], - "metadata": {
- "total": 0
}
}Create a new team
Creates an alerting team that groups members so they can be notified together as a single contact. Requires a name and at least one member, and every member must belong to your account. Optionally override members individual alerting with a custom alerting-hours window (from must be before to) and do-not-alert days. Returns the new team ID.
Authorizations:
Request Body schema: application/json
| name required | string Team name |
| members required | Array of integers Array of user/operator IDs |
| is_alerting_settings_override | boolean Override individual alerting settings |
| do_not_alert_days | Array of strings Items Enum: "mon" "tue" "wed" "thu" "fri" "sat" "sun" Days to suppress alerts |
| alert_hours_from | integer Alert window start hour (0-23) |
| alert_hours_to | integer Alert window end hour (0-23) |
Responses
Request samples
- Payload
{- "name": "string",
- "members": [
- 0
], - "is_alerting_settings_override": true,
- "do_not_alert_days": [
- "mon"
], - "alert_hours_from": 8,
- "alert_hours_to": 17
}Response samples
- 201
- 400
{- "data": {
- "id": 0
}
}Get a single team with full member details
Returns one team with its full roster of members (each flagged as the main account user or an operator) plus its alerting-override flag, alerting-hours window, and do-not-alert days. Returns 404 if the team does not exist or does not belong to your account.
Authorizations:
path Parameters
| id required | integer\d+ |
Responses
Response samples
- 200
- 404
{- "data": {
- "id": 0,
- "name": "string",
- "is_settings_override": 0,
- "do_not_alert_days": [
- "string"
], - "alert_hours_from": 0,
- "alert_hours_to": 0,
- "members_list": [
- {
- "id": 0,
- "name": "string",
- "is_main_user": true
}
]
}
}Delete a team
Permanently deletes a team belonging to the authenticated account; the underlying member users themselves are not deleted. Returns an error if the team could not be removed.
Authorizations:
path Parameters
| id required | integer\d+ |
Responses
Response samples
- 200
- 400
{- "status": "success"
}Update an existing team
Updates a team; only the supplied fields change while omitted fields keep their current values. The member list must remain non-empty and every member must belong to your account, and alerting-hours from must be before to. Returns 404 if the team does not exist or does not belong to your account.
Authorizations:
path Parameters
| id required | integer\d+ |
Request Body schema: application/json
| name | string |
| members | Array of integers |
| is_alerting_settings_override | boolean |
| do_not_alert_days | Array of strings Items Enum: "mon" "tue" "wed" "thu" "fri" "sat" "sun" |
| alert_hours_from | integer |
| alert_hours_to | integer |
Responses
Request samples
- Payload
{- "name": "string",
- "members": [
- 0
], - "is_alerting_settings_override": true,
- "do_not_alert_days": [
- "mon"
], - "alert_hours_from": 0,
- "alert_hours_to": 0
}Response samples
- 200
- 400
{- "data": {
- "id": 0,
- "name": "string"
}
}Roles & permissions matrix
Returns the fixed five-role model and the permission matrix, serialized from the very map the backend enforces on every request (RoleCapabilities) — the matrix shown on /cp/roles can never disagree with enforcement. Cell values: y = yes, v = usage only (plan counters, no invoices or prices), n = no, o = optional. A row may cover several capabilities that grant exactly the same roles. footnotes holds plain sentences shown under the matrix. Columns are in owner, admin, editor, viewer, billing order.
Authorizations:
Responses
Response samples
- 200
{- "data": {
- "roles": [
- {
- "key": "string",
- "label": "string",
- "description": "string"
}
], - "matrix": [
- {
- "group": "string",
- "rows": [
- {
- "label": "string",
- "cells": [
- "string"
]
}
]
}
], - "footnotes": [
- "string"
]
}
}Recent team access changes
Returns the most recent access-change events on the account (invites, role changes, member removals, invite resends) for the audit card on the team-members page. Visible to the account owner and Admin operators; the sentence to render is "{actor} {detail}".
Authorizations:
Responses
Response samples
- 200
{- "data": [
- {
- "id": 0,
- "action": "string",
- "actor": "string",
- "target": "string",
- "detail": "string",
- "created": 0
}
]
}List all users and operators for the authenticated account
Returns the account owner (main user) and every operator (sub-user) on the account, including each one's contact email, alerting schedule (do-not-alert days and alert-hour window) and confirmation state. The response includes a metadata.total count.
Authorizations:
Responses
Response samples
- 200
{- "data": [
- {
- "id": 0,
- "email": "string",
- "full_name": "string",
- "alerting_times": "string",
- "do_not_alert_days": "string",
- "alert_hours_from": 0,
- "alert_hours_to": 0,
- "is_main_user": true,
- "is_confirmed": true,
- "last_sign_in": {
- "at": 1793352000,
- "ip": "203.0.113.7",
- "country_code": "DE",
- "city": "Berlin",
- "method": "password",
- "method_label": "Password + email code",
- "device_label": "Firefox on Windows"
}
}
], - "metadata": {
- "total": 0
}
}Create a new operator
Adds a new operator (sub-user) to the account with a full name, email and optional alerting schedule, and mails them an activation link. If no contacts are supplied an email contact is auto-created (unverified) from the operator's email. The full name is stored as plain text: tags, URLs, domains, e-mail addresses and control characters are removed and it is capped at 60 characters. Alert hours must form a valid range and the operator count is capped by the account plan; returns a 400 error on validation failure or plan limit. Requires the account owner's login e-mail to be verified (403 email_not_verified). Invite mails are rate-limited per account (10 per day on free and trial, 100 on paid plans, re-sends included) and per recipient address (5 per day across all accounts): 429 rate_limited with a Retry-After header.
Authorizations:
Request Body schema: application/json
| full_name required | string Operator full name |
| email required | string <email> Operator email address |
Array of objects Array of contacts for the operator | |
| do_not_alert_days | Array of strings Items Enum: "mon" "tue" "wed" "thu" "fri" "sat" "sun" Days to suppress alerts |
| alert_hours_from | integer Alert window start hour (0-23) |
| alert_hours_to | integer Alert window end hour (0-23) |
| time_zone | integer Timezone id (1-127) the alert window is evaluated in. 0 = inherit the account timezone. |
Responses
Request samples
- Payload
{- "full_name": "string",
- "contacts": [
- {
- "name": "string",
- "type": "email",
- "contact": "string",
- "is_new": true
}
], - "do_not_alert_days": [
- "mon"
], - "alert_hours_from": 8,
- "alert_hours_to": 17,
- "time_zone": 0
}Response samples
- 201
- 400
{- "data": {
- "id": 0
}
}Get a single user or operator
Retrieves one operator (sub-user) by ID with a whitelisted set of fields: email, full name and their alerting schedule (do-not-alert days and alert-hour window). The operator must belong to the authenticated account; returns 404 if not found.
Authorizations:
path Parameters
| id required | integer\d+ |
Responses
Response samples
- 200
- 404
{- "data": {
- "id": 0,
- "email": "string",
- "full_name": "string",
- "do_not_alert_days": "string",
- "alert_hours_from": 0,
- "alert_hours_to": 0
}
}Delete an operator
Permanently removes an operator (sub-user) and their contacts from the account. The operator must belong to the authenticated account. This action cannot be undone and returns a 400 error if it fails.
Authorizations:
path Parameters
| id required | integer\d+ |
Responses
Response samples
- 200
- 400
{- "status": "success"
}Update an operator
Partially updates an operator's full name, email and alerting schedule; omitted fields keep their current values, and a full_name sent back unchanged is kept as stored. A changed full_name is stored as plain text (no links or e-mail addresses). The email must be valid and unique and the alert hours must form a valid range. Changing the email counts as one team invite: it needs a verified account owner e-mail (403 email_not_verified) and draws from the invite limits (429 rate_limited with Retry-After). The operator must belong to the authenticated account; returns 404 if not found.
Authorizations:
path Parameters
| id required | integer\d+ |
Request Body schema: application/json
| full_name | string |
string <email> | |
| do_not_alert_days | Array of strings Items Enum: "mon" "tue" "wed" "thu" "fri" "sat" "sun" |
| alert_hours_from | integer |
| alert_hours_to | integer |
| time_zone | integer Timezone id (1-127) the alert window is evaluated in. 0 = inherit the account timezone. |
Responses
Request samples
- Payload
{- "full_name": "string",
- "do_not_alert_days": [
- "mon"
], - "alert_hours_from": 0,
- "alert_hours_to": 0,
- "time_zone": 0
}Response samples
- 200
- 400
{- "data": {
- "id": 0,
- "email": "string",
- "full_name": "string"
}
}Update main user alerting preferences
Updates the alerting preferences of the authenticated account owner (main user): the do-not-alert days and the daily alert-hour window during which notifications are sent. Alert hours must form a valid range or a 400 error is returned.
Authorizations:
Request Body schema: application/json
| do_not_alert_days | Array of strings Items Enum: "mon" "tue" "wed" "thu" "fri" "sat" "sun" Days to suppress alerts |
| alert_hours_from | integer Alert window start hour (0-23) |
| alert_hours_to | integer Alert window end hour (0-23) |
Responses
Request samples
- Payload
{- "do_not_alert_days": [
- "mon"
], - "alert_hours_from": 8,
- "alert_hours_to": 17
}Response samples
- 200
- 400
{- "status": "success"
}Re-send the activation email to a pending operator
Re-sends the activation (direct-access) email for an operator who has not yet activated their access, with a freshly minted activation link valid 14 days. Owner + Admin; returns 404 if the operator does not exist, does not belong to the account, or has already activated. Requires the account owner's login e-mail to be verified (403 email_not_verified). One re-send per pending member per hour, and each re-send counts toward the account's daily invite budget and the recipient's daily limit (429 rate_limited with a Retry-After header).
Authorizations:
path Parameters
| id required | integer\d+ |
Responses
Response samples
- 200
{- "status": "success"
}Reset a team member's two-factor authentication
Turns off two-factor authentication for one operator (sub-user) and removes any authenticator app and recovery codes set up for it, so a member who can no longer complete their own 2FA (lost mailbox or phone) can sign in again and re-enrol. Owner + Admin only (team management), account-scoped, and written to the team audit log and to the account activity log as a security row (details.event twofa_reset_by_admin); the member is emailed that it happened. Does not affect the account owner's own 2FA, and a member cannot reset their own (403). Returns 400 when the member already has 2FA off.
Authorizations:
path Parameters
| id required | integer\d+ |
Responses
Response samples
- 200
- 400
{- "status": "success"
}Get the pending activation link for an operator
Mints a fresh direct-access activation link, valid 14 days, for an operator who has not yet activated their access, so the owner can share it out-of-band. Owner-only; 404 when there is no pending invite.
Authorizations:
path Parameters
| id required | integer\d+ |
Responses
Response samples
- 200
{- "data": {
- "invite_link": "string"
}
}List available timezones
Returns the complete map of supported timezone IDs to their human-readable labels, intended to populate a timezone picker before setting the time_zone field on the user profile. This endpoint requires no parameters.
Authorizations:
Responses
Response samples
- 200
{- "data": { }
}Get current user profile
Returns the authenticated user's profile (name, email, timezone, custom email sender, 2FA and login-email flags) plus the account plan, trial state and per-monitor-type usage versus plan limits. When signed in as an operator the operator's own name and email are returned along with impersonation context.
Authorizations:
header Parameters
| X-Browser-Time-Zone | string Example: Europe/Vilnius IANA zone of the caller's browser (Intl.DateTimeFormat().resolvedOptions().timeZone). Fills the account zone once when it is not set (owner sessions only) |
Responses
Response samples
- 200
- 401
{- "data": {
- "full_name": "string",
- "email": "string",
- "time_zone": 0,
- "time_zone_name": "Europe/Berlin",
- "custom_sender": "string",
- "twofa": 0,
- "twofa_method": "email",
- "recovery_codes_remaining": 0,
- "totp_enabled_at": 0,
- "twofa_recovery_session": true,
- "totp_available": true,
- "login_in_emails": true,
- "plan": 0,
- "api_user": 0,
- "operator_id": 0,
- "is_operator": true,
- "is_impersonated": true,
- "is_dashboard_tour_seen": true,
- "setup_journey_enabled": true,
- "hidden_channel_types": [
- "string"
], - "importers_enabled": true,
- "is_sumoling": true,
- "sumo_tier": 0,
- "incident_impact_allowed": true,
- "needs_password_setup": true,
- "server_monitoring_enabled": true,
- "heartbeat_monitoring_enabled": true,
- "blacklist_monitoring_enabled": true,
- "dns_monitoring_enabled": true,
- "api_monitoring_enabled": true,
- "scope_group_ids": [
- 0
]
}, - "plan": {
- "name": "string",
- "is_legacy": true,
- "is_trial": true,
- "trial_days_left": 0,
- "is_sumoling": true,
- "usage": {
- "uptime": {
- "used": 0,
- "max": 0
}, - "ssl": {
- "used": 0,
- "max": 0
}, - "domain": {
- "used": 0,
- "max": 0
}, - "virus": {
- "used": 0,
- "max": 0
}, - "rum": {
- "used": 0,
- "max": 0
}, - "sms_credits": {
- "used": 0,
- "max": 0
}, - "users": {
- "used": 0,
- "max": 0
}
}
}
}Update user profile
Updates the authenticated user's full name, email, timezone and custom email sender. The email must be valid and unique and the custom sender must be 3-11 alphanumeric characters. A changed name is cleaned of links, domains and e-mail addresses and cut to 60 characters (400 invalid_name when nothing is left); an unchanged name is kept as stored. Changing the account e-mail needs the current password and is limited to 3 changes per account per 24 hours (429). For operator sessions the operator's own name and email are returned so the form is not overwritten with the owner's values; returns a 400 error on validation failure.
Authorizations:
Request Body schema: application/json
| full_name | string |
string <email> | |
| time_zone | string |
| custom_sender | string |
Responses
Request samples
- Payload
{- "full_name": "string",
- "time_zone": "string",
- "custom_sender": "string"
}Response samples
- 200
- 400
- 429
{- "data": {
- "full_name": "string",
- "email": "string",
- "time_zone": 0,
- "time_zone_name": "string",
- "custom_sender": "string"
}
}List all branding themes for the authenticated account
Returns every branding theme owned by the account, each with its ID, name, a fully-qualified logo URL (relative filenames are expanded to the CDN uploads base URL), the full colour map, and live usage counts (how many scheduled reports and monitors currently reference the theme). Themes control the colours and logo applied to email reports and status pages. The response includes a metadata.total count.
Authorizations:
Responses
Response samples
- 200
{- "data": [
- {
- "id": 0,
- "name": "string",
- "logo": "string",
- "colors": { },
- "used_reports": 0,
- "used_monitors": 0
}
], - "metadata": {
- "total": 0
}
}Create a new branding theme
Creates a new branding theme for the account with a required name and an optional map of colour overrides; unspecified colours fall back to defaults. A logo is added separately via the logo upload endpoint. Returns the new theme ID, or a 400 error if the name is missing or creation fails.
Authorizations:
Request Body schema: application/json
| name required | string Theme name |
| colors | object Color overrides |
Responses
Request samples
- Payload
{- "name": "string",
- "colors": { }
}Response samples
- 201
- 400
{- "data": {
- "id": 0
}
}Get a single branding theme with all color data
Retrieves one branding theme by ID with its name, logo URL and the full set of colour fields (backgrounds, text, headers, links and separators). The theme must belong to the authenticated account; returns 404 if it does not exist or is not yours.
Authorizations:
path Parameters
| id required | integer\d+ |
Responses
Response samples
- 200
- 404
{- "data": {
- "id": 0,
- "name": "string",
- "logo": "string",
- "colors": { }
}
}Delete a branding theme
Permanently deletes a branding theme from the account. The theme must belong to the authenticated account; returns 404 if not found. This action cannot be undone.
Authorizations:
path Parameters
| id required | integer\d+ |
Responses
Response samples
- 200
- 404
{- "status": "success"
}Update an existing branding theme
Updates a branding theme's name and colours; supplied colour fields are merged over the theme's existing colours and the current logo is preserved. Returns the full updated theme. The theme must belong to the authenticated account; returns 404 if not found.
Authorizations:
path Parameters
| id required | integer\d+ |
Request Body schema: application/json
| name | string |
| colors | object |
Responses
Request samples
- Payload
{- "name": "string",
- "colors": { }
}Response samples
- 200
- 400
{- "data": {
- "id": 0,
- "name": "string",
- "logo": "string",
- "colors": { }
}
}Duplicate a branding theme
Creates a copy of an existing theme — same colours, name suffixed with " (copy)", and a physical copy of the logo file (so deleting either theme later cannot orphan the other's logo). The source theme must belong to the authenticated account; returns 404 if not found.
Authorizations:
path Parameters
| id required | integer\d+ |
Responses
Response samples
- 201
{- "data": {
- "id": 0,
- "name": "string"
}
}Upload a logo for a branding theme
Uploads a logo image (PNG, JPG or SVG, max 2MB; SVGs are sanitized server-side) via multipart form-data and attaches it to the theme, replacing any previous logo. Returns the fully-qualified logo URL. The theme must belong to the authenticated account; returns 404 if not found.
Authorizations:
path Parameters
| id required | integer\d+ |
Request Body schema: multipart/form-data
| logo | string <binary> Logo file (PNG, JPG or SVG, max 2MB). SVGs are sanitized server-side. |
Responses
Response samples
- 200
- 400
{- "data": {
- "filename": "string"
}
}Remove the logo from a branding theme
Clears the logo from a branding theme while keeping the theme and its colours intact. The theme must belong to the authenticated account; returns 404 if not found.
Authorizations:
path Parameters
| id required | integer\d+ |
Responses
Response samples
- 200
{- "status": "success"
}