# REST API reference

Generated from the API's OpenAPI document ([`/api/v1/openapi.json`](https://dash.justmonitorit.com/api/v1/openapi.json)). Every path below is relative to `https://dash.justmonitorit.com/api/v1`. Send your API token as `Authorization: Bearer jmi_…`; the token role shown for each operation is the lowest role that may call it. Conventions (errors, pagination, idempotency, `If-Match`, rate limits) are in [REST conventions](../rest.md).

## Contents

- **Token**
  - [Describe the calling token](#describe-the-calling-token) `GET /token`
- **Organisation**
  - [Get the organisation](#get-the-organisation) `GET /orgs/{org}`
  - [Update the organisation](#update-the-organisation) `PATCH /orgs/{org}`
  - [Audit log](#audit-log) `GET /orgs/{org}/audit-log`
  - [Plan limits and usage](#plan-limits-and-usage) `GET /orgs/{org}/entitlements`
  - [List open invitations](#list-open-invitations) `GET /orgs/{org}/invites`
  - [Invite someone](#invite-someone) `POST /orgs/{org}/invites`
  - [Revoke an invitation](#revoke-an-invitation) `DELETE /orgs/{org}/invites/{inv}`
  - [Resend an invitation](#resend-an-invitation) `POST /orgs/{org}/invites/{inv}/resend`
  - [List members](#list-members) `GET /orgs/{org}/members`
  - [Change a member's role](#change-a-member-s-role) `PATCH /orgs/{org}/members/{usr}`
  - [Remove a member](#remove-a-member) `DELETE /orgs/{org}/members/{usr}`
  - [Organisation overview](#organisation-overview) `GET /orgs/{org}/overview`
- **Monitors**
  - [List locations](#list-locations) `GET /locations`
  - [List monitors](#list-monitors) `GET /orgs/{org}/monitors`
  - [Create a monitor](#create-a-monitor) `POST /orgs/{org}/monitors`
  - [Run a check once](#run-a-check-once) `POST /orgs/{org}/monitors/preview-check`
  - [Get a monitor](#get-a-monitor) `GET /orgs/{org}/monitors/{mon}`
  - [Update a monitor](#update-a-monitor) `PATCH /orgs/{org}/monitors/{mon}`
  - [Delete a monitor](#delete-a-monitor) `DELETE /orgs/{org}/monitors/{mon}`
  - [List a monitor's contacts](#list-a-monitor-s-contacts) `GET /orgs/{org}/monitors/{mon}/contacts`
  - [Set a monitor's contacts](#set-a-monitor-s-contacts) `PUT /orgs/{org}/monitors/{mon}/contacts`
  - [List a monitor's incidents](#list-a-monitor-s-incidents) `GET /orgs/{org}/monitors/{mon}/incidents`
  - [Pause a monitor](#pause-a-monitor) `POST /orgs/{org}/monitors/{mon}/pause`
  - [List heartbeat pings](#list-heartbeat-pings) `GET /orgs/{org}/monitors/{mon}/pings`
  - [List check results](#list-check-results) `GET /orgs/{org}/monitors/{mon}/results`
  - [Resume a monitor](#resume-a-monitor) `POST /orgs/{org}/monitors/{mon}/resume`
  - [List rollups](#list-rollups) `GET /orgs/{org}/monitors/{mon}/rollups`
  - [Response-time series](#response-time-series) `GET /orgs/{org}/monitors/{mon}/series`
  - [Status timeline](#status-timeline) `GET /orgs/{org}/monitors/{mon}/timeline`
  - [List path traces](#list-path-traces) `GET /orgs/{org}/monitors/{mon}/traces`
  - [Run a path trace](#run-a-path-trace) `POST /orgs/{org}/monitors/{mon}/traces`
  - [Uptime](#uptime) `GET /orgs/{org}/monitors/{mon}/uptime`
  - [Get a path trace](#get-a-path-trace) `GET /orgs/{org}/traces/{trc}`
- **Incidents**
  - [List incidents](#list-incidents) `GET /orgs/{org}/incidents`
  - [Get an incident](#get-an-incident) `GET /orgs/{org}/incidents/{inc}`
  - [Acknowledge an incident](#acknowledge-an-incident) `POST /orgs/{org}/incidents/{inc}/acknowledge`
  - [Add a note to an incident](#add-a-note-to-an-incident) `POST /orgs/{org}/incidents/{inc}/notes`
- **Contacts**
  - [List contacts](#list-contacts) `GET /orgs/{org}/contacts`
  - [Create a contact](#create-a-contact) `POST /orgs/{org}/contacts`
  - [Get a contact](#get-a-contact) `GET /orgs/{org}/contacts/{con}`
  - [Update a contact](#update-a-contact) `PATCH /orgs/{org}/contacts/{con}`
  - [Delete a contact](#delete-a-contact) `DELETE /orgs/{org}/contacts/{con}`
  - [Disable a contact](#disable-a-contact) `POST /orgs/{org}/contacts/{con}/disable`
  - [Enable a contact](#enable-a-contact) `POST /orgs/{org}/contacts/{con}/enable`
  - [Set a contact's monitors](#set-a-contact-s-monitors) `PUT /orgs/{org}/contacts/{con}/monitors`
  - [Send a test notification](#send-a-test-notification) `POST /orgs/{org}/contacts/{con}/test`
  - [Verify a contact](#verify-a-contact) `POST /orgs/{org}/contacts/{con}/verify`
  - [Send a verification code](#send-a-verification-code) `POST /orgs/{org}/contacts/{con}/verify/send`
- **Notifications**
  - [List notifications](#list-notifications) `GET /orgs/{org}/notifications`
  - [Get a notification](#get-a-notification) `GET /orgs/{org}/notifications/{ntf}`
- **Maintenance windows**
  - [List maintenance windows](#list-maintenance-windows) `GET /orgs/{org}/maintenance`
  - [Schedule a maintenance window](#schedule-a-maintenance-window) `POST /orgs/{org}/maintenance`
  - [Get a maintenance window](#get-a-maintenance-window) `GET /orgs/{org}/maintenance/{mnt}`
  - [Change a maintenance window](#change-a-maintenance-window) `PATCH /orgs/{org}/maintenance/{mnt}`
  - [Delete a maintenance window](#delete-a-maintenance-window) `DELETE /orgs/{org}/maintenance/{mnt}`
  - [End a maintenance window now](#end-a-maintenance-window-now) `POST /orgs/{org}/maintenance/{mnt}/end`
- **Billing**
  - [List plans](#list-plans) `GET /billing/plans`
  - [Billing state](#billing-state) `GET /orgs/{org}/billing`
- [Schemas](#schemas)

## Token

### Describe the calling token

`GET /token` · token role: `viewer`

The token making the request, the organisation it belongs to and its effective role (the lower of the token's role and its creator's current role). Use it to find the org id for the other paths. Only API tokens can call it.

**Responses**

- `200`: [`TokenInfo`](#schema-tokeninfo)
- Errors: `401` `unauthenticated` (no, or an invalid, API token); `403` `account_suspended`; `429` `rate_limited`

## Organisation

### Get the organisation

`GET /orgs/{org}` · token role: `viewer`

Name, timezone, state, your effective role (`my_role`) and plan of the organisation.

**Path parameters**

| Name | Type | Description |
|---|---|---|
| `org` | id `org_…` | Organisation id (`org_…`); `GET /token` returns it |

**Responses**

- `200`: [`Org`](#schema-org)
- Errors: `401` `unauthenticated`; `403` `account_suspended`; `404` `not_found`; `429` `rate_limited`

### Update the organisation

`PATCH /orgs/{org}` · token role: `admin`

Rename the organisation or change its timezone (an IANA name such as `Europe/London`).

**Path parameters**

| Name | Type | Description |
|---|---|---|
| `org` | id `org_…` | Organisation id (`org_…`); `GET /token` returns it |

**Request body** ([`OrgPatch`](#schema-orgpatch))

| Field | Type | Description |
|---|---|---|
| `name` (optional) | string or null | 1-100 characters. |
| `timezone` (optional) | string or null | IANA timezone. |

**Responses**

- `200`: [`Org`](#schema-org)
- Errors: `400` `bad_request`; `401` `unauthenticated`; `403` `forbidden`, `org_suspended`, `org_deleting`, `billing_inactive`; `404` `not_found`; `413` `payload_too_large`; `415` `unsupported_media_type`; `422` `validation`; `429` `rate_limited`

### Audit log

`GET /orgs/{org}/audit-log` · token role: `admin`

Who did what in the organisation, newest first. Entries made with an API token carry `data.api_token` (`{id, name}`).

**Path parameters**

| Name | Type | Description |
|---|---|---|
| `org` | id `org_…` | Organisation id (`org_…`); `GET /token` returns it |

**Query parameters**

| Name | Type | Description |
|---|---|---|
| `action` (optional) | string | Exact action, e.g. `monitor.updated` (at most 64 characters). |
| `actor` (optional) | string | Actor user id (`usr_…`). |
| `cursor` (optional) | string | Opaque cursor from `next_cursor`. |
| `limit` (optional) | integer | 1-200, default 50. |

**Responses**

- `200`: page of `AuditEntry`
- Errors: `400` `bad_request`; `401` `unauthenticated`; `403` `forbidden`, `org_suspended`, `org_deleting`; `404` `not_found`; `422` `validation` (`/query/...`); `429` `rate_limited`

### Plan limits and usage

`GET /orgs/{org}/entitlements` · token role: `viewer`

What the organisation may do right now (monitor count, minimum interval, locations per monitor, checks per minute, contacts, seats, SMS credits, retention) and how much of it is used.

**Path parameters**

| Name | Type | Description |
|---|---|---|
| `org` | string | Organisation id (`org_…`); `GET /token` returns it |

**Responses**

- `200`: [`OrgEntitlements`](#schema-orgentitlements)
- Errors: `401` `unauthenticated`; `403` `account_suspended`, `org_deleting`; `404` `not_found` (unknown org or not a member); `429` `rate_limited`

### List open invitations

`GET /orgs/{org}/invites` · token role: `admin`

Invitations that have not been accepted, revoked or expired, newest first.

**Path parameters**

| Name | Type | Description |
|---|---|---|
| `org` | id `org_…` | Organisation id (`org_…`); `GET /token` returns it |

**Query parameters**

| Name | Type | Description |
|---|---|---|
| `cursor` (optional) | string | Opaque cursor from `next_cursor`. |
| `limit` (optional) | integer | 1-200, default 50. |

**Responses**

- `200`: page of [`Invite`](#schema-invite)
- Errors: `400` `bad_request`; `401` `unauthenticated`; `403` `forbidden`, `org_suspended`, `org_deleting`; `404` `not_found`; `422` `validation` (`/query/cursor`, `/query/limit`); `429` `rate_limited`

### Invite someone

`POST /orgs/{org}/invites` · token role: `admin`

Email an invitation to join the organisation with a role (`admin`, `member` or `viewer`; owner invitations need a signed-in owner). The invitation is valid for 7 days. Counts against the plan's seats.

**Path parameters**

| Name | Type | Description |
|---|---|---|
| `org` | id `org_…` | Organisation id (`org_…`); `GET /token` returns it |

**Headers**

| Name | Type | Description |
|---|---|---|
| `Idempotency-Key` (optional) | string or null | 8-64 characters [A-Za-z0-9_-] |

**Request body** ([`CreateInviteRequest`](#schema-createinviterequest))

| Field | Type | Description |
|---|---|---|
| `email` | string |  |
| `role` | `owner` \| `admin` \| `member` \| `viewer` |  |

**Responses**

- `201`: [`Invite`](#schema-invite)
- Errors: `400` `bad_request`; `401` `unauthenticated`; `403` `forbidden`, `plan_limit` (seats), `org_suspended`, `org_deleting`, `billing_inactive`; `404` `not_found`; `409` `conflict` (already a member or invited), `idempotency_in_progress`; `413` `payload_too_large`; `415` `unsupported_media_type`; `422` `validation`, `idempotency_mismatch`; `429` `rate_limited` (20 per day)

### Revoke an invitation

`DELETE /orgs/{org}/invites/{inv}` · token role: `admin`

The link in the invitation email stops working.

**Path parameters**

| Name | Type | Description |
|---|---|---|
| `org` | id `org_…` | Organisation id (`org_…`); `GET /token` returns it |
| `inv` | id `inv_…` | Invitation id (`inv_…`) |

**Responses**

- `204`: no body (revoked)
- Errors: `401` `unauthenticated`; `403` `forbidden`, `org_suspended`, `org_deleting`, `billing_inactive`; `404` `not_found`; `429` `rate_limited`

### Resend an invitation

`POST /orgs/{org}/invites/{inv}/resend` · token role: `admin`

Send the invitation again with a new link (the old link stops working) and a fresh 7-day expiry.

**Path parameters**

| Name | Type | Description |
|---|---|---|
| `org` | id `org_…` | Organisation id (`org_…`); `GET /token` returns it |
| `inv` | id `inv_…` | Invitation id (`inv_…`) |

**Responses**

- `202`: no body (sent)
- Errors: `401` `unauthenticated`; `403` `forbidden`, `plan_limit`, `org_suspended`, `org_deleting`, `billing_inactive`; `404` `not_found`; `429` `rate_limited`

### List members

`GET /orgs/{org}/members` · token role: `viewer`

Every member of the organisation with their role and when they were last active.

**Path parameters**

| Name | Type | Description |
|---|---|---|
| `org` | id `org_…` | Organisation id (`org_…`); `GET /token` returns it |

**Responses**

- `200`: list of [`Member`](#schema-member)
- Errors: `401` `unauthenticated`; `403` `org_suspended`, `org_deleting`; `404` `not_found`; `429` `rate_limited`

### Change a member's role

`PATCH /orgs/{org}/members/{usr}` · token role: `admin`

Set a member's role to `admin`, `member` or `viewer`. Granting or removing `owner` is not possible with a token. If the member is demoted below the role of one of their API tokens, that token is revoked.

**Path parameters**

| Name | Type | Description |
|---|---|---|
| `org` | id `org_…` | Organisation id (`org_…`); `GET /token` returns it |
| `usr` | id `usr_…` | User id (`usr_…`) |

**Request body** ([`RoleRequest`](#schema-rolerequest))

| Field | Type | Description |
|---|---|---|
| `role` | `owner` \| `admin` \| `member` \| `viewer` |  |

**Responses**

- `200`: [`Member`](#schema-member)
- Errors: `400` `bad_request`; `401` `unauthenticated`; `403` `forbidden`, `org_suspended`, `org_deleting`, `billing_inactive`; `404` `not_found`; `409` `sole_owner`; `413` `payload_too_large`; `415` `unsupported_media_type`; `422` `validation`; `429` `rate_limited`

### Remove a member

`DELETE /orgs/{org}/members/{usr}` · token role: `admin`

Remove a member from the organisation; their API tokens for it are deleted. Removing an owner, or the token's own creator (leaving), is not possible with a token.

**Path parameters**

| Name | Type | Description |
|---|---|---|
| `org` | id `org_…` | Organisation id (`org_…`); `GET /token` returns it |
| `usr` | id `usr_…` | User id (`usr_…`) |

**Responses**

- `204`: no body (removed)
- Errors: `401` `unauthenticated`; `403` `forbidden`, `org_suspended`, `org_deleting`; `404` `not_found`; `409` `sole_owner`; `429` `rate_limited`

### Organisation overview

`GET /orgs/{org}/overview` · token role: `viewer`

Monitor counts by status, open incidents (newest first, at most 20), the latest status changes and usage against the plan.

**Path parameters**

| Name | Type | Description |
|---|---|---|
| `org` | id `org_…` | Organisation id (`org_…`); `GET /token` returns it |

**Responses**

- `200`: [`Overview`](#schema-overview)
- Errors: `401` `unauthenticated`; `403` `forbidden`, `account_suspended`, `org_suspended`, `org_deleting`; `404` `not_found`; `429` `rate_limited`

## Monitors

### List locations

`GET /locations` · token role: none (public)

The active check locations and the regions they belong to. Monitors name locations by `code` (`lon1`) or by region. No authentication needed.

**Responses**

- `200`: [`LocationsResponse`](#schema-locationsresponse)
- Errors: `429` `rate_limited`

### List monitors

`GET /orgs/{org}/monitors` · token role: `viewer`

The organisation's monitors, filtered and sorted, with cursor pagination (up to 500 per page).

**Path parameters**

| Name | Type | Description |
|---|---|---|
| `org` | id `org_…` | Organisation id (`org_…`); `GET /token` returns it |

**Query parameters**

| Name | Type | Description |
|---|---|---|
| `status` (optional) | string | Comma-separated statuses: `unknown,up,down,degraded,no_data,paused`. |
| `kind` (optional) | string | Comma-separated kinds. |
| `label` (optional) | string | One label. |
| `run_state` (optional) | string | Comma-separated run states: `active,paused,system_paused`. |
| `contact_id` (optional) | string | Monitors linked to this contact. |
| `q` (optional) | string | Substring of the name or target (≤ 100 characters). |
| `sort` (optional) | string | `created_at` (default) \| `name` \| `status` \| `status_since`. |
| `dir` (optional) | string | `asc` (default) \| `desc`. |
| `limit` (optional) | integer | 1-500, default 50. |
| `cursor` (optional) | string | `next_cursor` of the previous page. |

**Responses**

- `200`: page of [`Monitor`](#schema-monitor)
- Errors: `400` `bad_request`; `401` `unauthenticated`; `403` `forbidden`, `account_suspended`, `org_suspended`, `org_deleting`; `404` `not_found`; `422` `validation`; `429` `rate_limited`

### Create a monitor

`POST /orgs/{org}/monitors` · token role: `member`

Validate and create a monitor. It starts checking at once unless `paused` is true. Send an `Idempotency-Key` so a retried request does not create a second monitor. `403 plan_limit` names the limit that would be exceeded.

**Path parameters**

| Name | Type | Description |
|---|---|---|
| `org` | id `org_…` | Organisation id (`org_…`); `GET /token` returns it |

**Headers**

| Name | Type | Description |
|---|---|---|
| `Idempotency-Key` (optional) | string or null | 8-64 characters [A-Za-z0-9_-] |

**Request body** ([`MonitorInput`](#schema-monitorinput))

| Field | Type | Description |
|---|---|---|
| `contact_ids` (optional) | array of id `con_…` or null | At most 100; omitted = every `auto_attach` contact. |
| `degraded_after_ms` (optional) | integer or null | `http_adv` only; 100-30000 and below the time budget; `null` = off. |
| `failure_threshold` (optional) | integer or null | 1-10, default 2; for `domain_expiry` 1, the default (a second probe confirms each change instead); for `heartbeat` 1. |
| `interval_s` | integer | 30, 60, 120, 300, 600, 900, 1800 or 3600, at least the plan minimum. `domain_expiry`: 86400 only. `heartbeat`: the period between pings, 60-604800 (no plan minimum). |
| `kind` | `icmp` \| `http` \| `http_adv` \| `tcp` \| `ssl_expiry` \| `domain_expiry` \| `imap` \| `smtp` \| `heartbeat` | Immutable after create. |
| `labels` (optional) | array of string | At most 10, each 1-32 characters of `[a-z0-9-_.]`. |
| `locations` (optional) | [`MonitorLocationsInput`](#schema-monitorlocationsinput) or null | Required, except for `domain_expiry`, which must omit it: each of its lookups runs from one location, a different one each day; and for `heartbeat`, which runs no checks. |
| `name` | string | 1-100 characters. |
| `paused` (optional) | boolean |  |
| `recovery_threshold` (optional) | integer or null | 1-10, default 2; for `domain_expiry` and `heartbeat` 1, the default. |
| `reminder_interval_s` (optional) | integer or null | `null` = no reminders; 900-86400. |
| `spec` | [`MonitorSpecInput`](#schema-monitorspecinput) | The per-kind spec without the type tag: the `MonitorSpecInput` schema of `kind`; secret fields are write-only. Fields per kind: [Monitor kinds](../monitor-kinds.md). |

**Responses**

- `201`: [`Monitor`](#schema-monitor), header `ETag`
- Errors: `400` `bad_request`; `401` `unauthenticated`; `403` `forbidden`, `billing_inactive`, `plan_limit`, `org_suspended`, `org_deleting`, `account_suspended`; `404` `not_found`; `409` `idempotency_in_progress`; `413` `payload_too_large`; `415` `unsupported_media_type`; `422` `validation`, `idempotency_mismatch`; `429` `rate_limited`

### Run a check once

`POST /orgs/{org}/monitors/preview-check` · token role: `member`

Run a check from one location now and return the result, without creating a monitor or storing anything. The same validation as creating a monitor applies. Waits for the probe (up to the check's time budget plus 15 seconds); `503 unavailable` if no probe in that location can run it.

**Path parameters**

| Name | Type | Description |
|---|---|---|
| `org` | id `org_…` | Organisation id (`org_…`); `GET /token` returns it |

**Request body** ([`PreviewCheckInput`](#schema-previewcheckinput))

| Field | Type | Description |
|---|---|---|
| `kind` | `icmp` \| `http` \| `http_adv` \| `tcp` \| `ssl_expiry` \| `domain_expiry` \| `imap` \| `smtp` \| `heartbeat` |  |
| `location` | string | Location code. |
| `spec` | [`MonitorSpecInput`](#schema-monitorspecinput) | The spec as in `MonitorInput` (secrets inline). Fields per kind: [Monitor kinds](../monitor-kinds.md). |

**Responses**

- `200`: [`CheckResult`](#schema-checkresult)
- Errors: `400` `bad_request`; `401` `unauthenticated`; `403` `forbidden`, `billing_inactive`, `org_suspended`, `org_deleting`, `account_suspended`; `404` `not_found`; `413` `payload_too_large`; `415` `unsupported_media_type`; `422` `validation`; `429` `rate_limited`; `503` `unavailable`

### Get a monitor

`GET /orgs/{org}/monitors/{mon}` · token role: `viewer`

One monitor with its current state per location and its uptime over the last 24 hours. The `ETag` header carries `version` for `If-Match`.

**Path parameters**

| Name | Type | Description |
|---|---|---|
| `org` | id `org_…` | Organisation id (`org_…`); `GET /token` returns it |
| `mon` | id `mon_…` | Monitor id (`mon_…`) |

**Responses**

- `200`: [`Monitor`](#schema-monitor), header `ETag`
- Errors: `401` `unauthenticated`; `403` `forbidden`, `account_suspended`, `org_suspended`, `org_deleting`; `404` `not_found`; `429` `rate_limited`

### Update a monitor

`PATCH /orgs/{org}/monitors/{mon}` · token role: `member`

Change any field except `kind` (JSON merge patch: omitted fields are unchanged, `null` clears a nullable field, arrays are replaced whole; `spec` is merged field by field). Send `If-Match: "<version>"` to get `412 precondition_failed` instead of overwriting someone else's change.

**Path parameters**

| Name | Type | Description |
|---|---|---|
| `org` | id `org_…` | Organisation id (`org_…`); `GET /token` returns it |
| `mon` | id `mon_…` | Monitor id (`mon_…`) |

**Headers**

| Name | Type | Description |
|---|---|---|
| `If-Match` (optional) | string or null | "<version>" |

**Request body** ([`MonitorPatch`](#schema-monitorpatch))

| Field | Type | Description |
|---|---|---|
| `contact_ids` (optional) | array of id `con_…` or null |  |
| `degraded_after_ms` (optional) | integer or null |  |
| `failure_threshold` (optional) | integer or null |  |
| `interval_s` (optional) | integer or null |  |
| `kind` (optional) | `icmp` \| `http` \| `http_adv` \| `tcp` \| `ssl_expiry` \| `domain_expiry` \| `imap` \| `smtp` \| `heartbeat` or null | Not allowed (`422 not_allowed`): the kind is immutable. |
| `labels` (optional) | array of string or null |  |
| `locations` (optional) | [`MonitorLocationsInput`](#schema-monitorlocationsinput) or null |  |
| `name` (optional) | string or null |  |
| `paused` (optional) | boolean or null | `false` runs the same checks as `POST .../resume`. |
| `recovery_threshold` (optional) | integer or null |  |
| `reminder_interval_s` (optional) | integer or null |  |
| `spec` (optional) | object or null | Merged into the stored spec (RFC 7396): any subset of the `MonitorSpecInput` schema of the monitor's kind. Fields per kind: [Monitor kinds](../monitor-kinds.md). |

**Responses**

- `200`: [`Monitor`](#schema-monitor), header `ETag`
- Errors: `400` `bad_request`; `401` `unauthenticated`; `403` `forbidden`, `billing_inactive`, `plan_limit`, `org_suspended`, `org_deleting`, `account_suspended`; `404` `not_found`; `412` `precondition_failed`; `413` `payload_too_large`; `415` `unsupported_media_type`; `422` `validation`; `429` `rate_limited`

### Delete a monitor

`DELETE /orgs/{org}/monitors/{mon}` · token role: `member`

Delete a monitor with its results, incidents and pending notifications. No notification is sent.

**Path parameters**

| Name | Type | Description |
|---|---|---|
| `org` | id `org_…` | Organisation id (`org_…`); `GET /token` returns it |
| `mon` | id `mon_…` | Monitor id (`mon_…`) |

**Headers**

| Name | Type | Description |
|---|---|---|
| `If-Match` (optional) | string or null | "<version>" |

**Responses**

- `204`: no body (deleted)
- Errors: `401` `unauthenticated`; `403` `forbidden`, `org_suspended`, `org_deleting`, `account_suspended`; `404` `not_found`; `412` `precondition_failed`; `422` `validation`; `429` `rate_limited`

### List a monitor's contacts

`GET /orgs/{org}/monitors/{mon}/contacts` · token role: `viewer`

The contacts notified for a monitor.

**Path parameters**

| Name | Type | Description |
|---|---|---|
| `org` | id `org_…` | Organisation id (`org_…`); `GET /token` returns it |
| `mon` | id `mon_…` | Monitor id (`mon_…`) |

**Responses**

- `200`: list of [`Contact`](#schema-contact)
- Errors: `401` `unauthenticated`; `403` `forbidden`, `account_suspended`, `org_suspended`, `org_deleting`; `404` `not_found`; `429` `rate_limited`

### Set a monitor's contacts

`PUT /orgs/{org}/monitors/{mon}/contacts` · token role: `member`

Replace the set of contacts notified for a monitor.

**Path parameters**

| Name | Type | Description |
|---|---|---|
| `org` | id `org_…` | Organisation id (`org_…`); `GET /token` returns it |
| `mon` | id `mon_…` | Monitor id (`mon_…`) |

**Headers**

| Name | Type | Description |
|---|---|---|
| `If-Match` (optional) | string or null | "<version>" of the monitor |

**Request body** ([`MonitorContactIds`](#schema-monitorcontactids))

| Field | Type | Description |
|---|---|---|
| `contact_ids` | array of id `con_…` | At most 100 contacts of the org; replaces the set. |

**Responses**

- `200`: [`MonitorContactIds`](#schema-monitorcontactids)
- Errors: `400` `bad_request`; `401` `unauthenticated`; `403` `forbidden`, `billing_inactive`, `org_suspended`, `org_deleting`, `account_suspended`; `404` `not_found`; `412` `precondition_failed`; `415` `unsupported_media_type`; `422` `validation`; `429` `rate_limited`

### List a monitor's incidents

`GET /orgs/{org}/monitors/{mon}/incidents` · token role: `viewer`

Incidents of one monitor, newest first, within the plan's history.

**Path parameters**

| Name | Type | Description |
|---|---|---|
| `org` | id `org_…` | Organisation id (`org_…`); `GET /token` returns it |
| `mon` | id `mon_…` | Monitor id (`mon_…`) |

**Query parameters**

| Name | Type | Description |
|---|---|---|
| `limit` (optional) | integer | 1-200, default 50. |
| `cursor` (optional) | string | `next_cursor` of the previous page. |

**Responses**

- `200`: page of [`Incident`](#schema-incident)
- Errors: `400` `bad_request`; `401` `unauthenticated`; `403` `forbidden`, `account_suspended`, `org_suspended`, `org_deleting`; `404` `not_found`; `422` `validation`; `429` `rate_limited`

### Pause a monitor

`POST /orgs/{org}/monitors/{mon}/pause` · token role: `member`

Stop checking until resumed. Pausing resolves an open incident. Idempotent.

**Path parameters**

| Name | Type | Description |
|---|---|---|
| `org` | id `org_…` | Organisation id (`org_…`); `GET /token` returns it |
| `mon` | id `mon_…` | Monitor id (`mon_…`) |

**Responses**

- `200`: [`Monitor`](#schema-monitor), header `ETag`
- Errors: `401` `unauthenticated`; `403` `forbidden`, `org_suspended`, `org_deleting`, `account_suspended`; `404` `not_found`; `429` `rate_limited`

### List heartbeat pings

`GET /orgs/{org}/monitors/{mon}/pings` · token role: `viewer`

A heartbeat monitor's ping history, newest first: when each ping arrived, `success` or `fail` (the job requested the ping URL followed by `/fail`), the method, the client's address and user agent. The newest 100 are kept; other kinds have none.

**Path parameters**

| Name | Type | Description |
|---|---|---|
| `org` | id `org_…` | Organisation id (`org_…`); `GET /token` returns it |
| `mon` | id `mon_…` | Monitor id (`mon_…`) |

**Query parameters**

| Name | Type | Description |
|---|---|---|
| `limit` (optional) | integer | 1-100, default 50. |

**Responses**

- `200`: list of `HeartbeatPing`
- Errors: `401` `unauthenticated`; `403` `forbidden`, `account_suspended`, `org_suspended`, `org_deleting`; `404` `not_found`; `422` `validation`; `429` `rate_limited`

### List check results

`GET /orgs/{org}/monitors/{mon}/results` · token role: `viewer`

Raw results of a monitor, newest first. The window defaults to the last 24 hours, spans at most 7 days, and starts no earlier than the plan's raw data retention.

**Path parameters**

| Name | Type | Description |
|---|---|---|
| `org` | id `org_…` | Organisation id (`org_…`); `GET /token` returns it |
| `mon` | id `mon_…` | Monitor id (`mon_…`) |

**Query parameters**

| Name | Type | Description |
|---|---|---|
| `from` (optional) | string | Default `to − 24 h`; at most 7 days before `to`; clamped to the plan's raw retention. |
| `to` (optional) | string | Default now. |
| `location` (optional) | string | Location code. |
| `outcome` (optional) | string | Comma-separated outcomes. |
| `limit` (optional) | integer | 1-1000, default 50. |
| `cursor` (optional) | string | `next_cursor` of the previous page. |

**Responses**

- `200`: page of [`CheckResult`](#schema-checkresult)
- Errors: `400` `bad_request`; `401` `unauthenticated`; `403` `forbidden`, `account_suspended`, `org_suspended`, `org_deleting`; `404` `not_found`; `422` `validation`; `429` `rate_limited`

### Resume a monitor

`POST /orgs/{org}/monitors/{mon}/resume` · token role: `member`

Start checking again. `403 plan_limit` if the plan no longer allows the monitor.

**Path parameters**

| Name | Type | Description |
|---|---|---|
| `org` | id `org_…` | Organisation id (`org_…`); `GET /token` returns it |
| `mon` | id `mon_…` | Monitor id (`mon_…`) |

**Responses**

- `200`: [`Monitor`](#schema-monitor), header `ETag`
- Errors: `401` `unauthenticated`; `403` `forbidden`, `plan_limit`, `billing_inactive`, `org_suspended`, `org_deleting`, `account_suspended`; `404` `not_found`; `422` `validation`; `429` `rate_limited`

### List rollups

`GET /orgs/{org}/monitors/{mon}/rollups` · token role: `viewer`

Hourly (`hour`, at most 31 days) or daily (`day`, at most 731 days) aggregates per location: check counts by outcome and latency statistics. Not paginated.

**Path parameters**

| Name | Type | Description |
|---|---|---|
| `org` | id `org_…` | Organisation id (`org_…`); `GET /token` returns it |
| `mon` | id `mon_…` | Monitor id (`mon_…`) |

**Query parameters**

| Name | Type | Description |
|---|---|---|
| `granularity` (optional) | string | `hour` (default) or `day`. |
| `from` (optional) | string | Default `to − 24 h` (hour) or `to − 30 days` (day); at most 31 / 731 days before `to`; clamped to plan history. |
| `to` (optional) | string |  |
| `location` (optional) | string | Location code. |

**Responses**

- `200`: [`RollupsResponse`](#schema-rollupsresponse)
- Errors: `400` `bad_request`; `401` `unauthenticated`; `403` `forbidden`, `account_suspended`, `org_suspended`, `org_deleting`; `404` `not_found`; `422` `validation`; `429` `rate_limited`

### Response-time series

`GET /orgs/{org}/monitors/{mon}/series` · token role: `viewer`

Median response time per location over `24h`, `7d`, `30d` or `90d`, with the periods the monitor was not up.

**Path parameters**

| Name | Type | Description |
|---|---|---|
| `org` | id `org_…` | Organisation id (`org_…`); `GET /token` returns it |
| `mon` | id `mon_…` | Monitor id (`mon_…`) |

**Query parameters**

| Name | Type | Description |
|---|---|---|
| `range` (optional) | string | `24h` (default) \| `7d` \| `30d` \| `90d`. |

**Responses**

- `200`: [`Series`](#schema-series)
- Errors: `400` `bad_request`; `401` `unauthenticated`; `403` `forbidden`, `account_suspended`, `org_suspended`, `org_deleting`; `404` `not_found`; `422` `validation`; `429` `rate_limited`

### Status timeline

`GET /orgs/{org}/monitors/{mon}/timeline` · token role: `viewer`

The status periods (up, down, degraded, no data, paused) overlapping a window, oldest first.

**Path parameters**

| Name | Type | Description |
|---|---|---|
| `org` | id `org_…` | Organisation id (`org_…`); `GET /token` returns it |
| `mon` | id `mon_…` | Monitor id (`mon_…`) |

**Query parameters**

| Name | Type | Description |
|---|---|---|
| `from` (optional) | string | Default `to − 24 h`; clamped to plan history. |
| `to` (optional) | string | Default now. |

**Responses**

- `200`: list of `StatusPeriod`
- Errors: `400` `bad_request`; `401` `unauthenticated`; `403` `forbidden`, `account_suspended`, `org_suspended`, `org_deleting`; `404` `not_found`; `422` `validation`; `429` `rate_limited`

### List path traces

`GET /orgs/{org}/monitors/{mon}/traces` · token role: `viewer`

Traceroute/MTR runs towards a monitor's target, newest first, within the plan's raw data retention. Traces run automatically when an incident opens.

**Path parameters**

| Name | Type | Description |
|---|---|---|
| `org` | id `org_…` | Organisation id (`org_…`); `GET /token` returns it |
| `mon` | id `mon_…` | Monitor id (`mon_…`) |

**Query parameters**

| Name | Type | Description |
|---|---|---|
| `incident_id` (optional) | string | Only the traces run for this incident. |
| `limit` (optional) | integer | 1-50, default 10. |

**Responses**

- `200`: list of [`PathTrace`](#schema-pathtrace)
- Errors: `400` `bad_request`; `401` `unauthenticated`; `403` `forbidden`, `account_suspended`, `org_suspended`, `org_deleting`; `404` `not_found`; `422` `validation`; `429` `rate_limited`

### Run a path trace

`POST /orgs/{org}/monitors/{mon}/traces` · token role: `member`

Trace the network path to the monitor's target from a location (`mtr` by default, about 15 seconds; `traceroute` about 5). Returns at once with `status: pending`; poll `GET /orgs/{org}/traces/{trc}` for the result.

**Path parameters**

| Name | Type | Description |
|---|---|---|
| `org` | id `org_…` | Organisation id (`org_…`); `GET /token` returns it |
| `mon` | id `mon_…` | Monitor id (`mon_…`) |

**Request body** ([`PathTraceInput`](#schema-pathtraceinput))

| Field | Type | Description |
|---|---|---|
| `location` | string | Location code to trace from (any active location). |
| `mode` (optional) | `traceroute` \| `mtr` or null | Default `mtr`. |
| `protocol` (optional) | `icmp` \| `tcp` or null | Default: `tcp` to the target's port when it has one (falling back to `icmp` when no probe there can trace over TCP), else `icmp`. |

**Responses**

- `202`: [`PathTrace`](#schema-pathtrace)
- Errors: `400` `bad_request`; `401` `unauthenticated`; `403` `forbidden`, `billing_inactive`, `org_suspended`, `org_deleting`, `account_suspended`; `404` `not_found`; `409` `conflict` (a trace from this location is still running); `413` `payload_too_large`; `415` `unsupported_media_type`; `422` `validation`; `429` `rate_limited`; `503` `unavailable` (no probe there can run it)

### Uptime

`GET /orgs/{org}/monitors/{mon}/uptime` · token role: `viewer`

Uptime ratio and seconds down, degraded, without data and paused, for named windows (`24h`, `7d`, `30d`, `90d`) or a `from`/`to` range. Degraded counts as up; paused and no-data time is left out of the ratio.

**Path parameters**

| Name | Type | Description |
|---|---|---|
| `org` | id `org_…` | Organisation id (`org_…`); `GET /token` returns it |
| `mon` | id `mon_…` | Monitor id (`mon_…`) |

**Query parameters**

| Name | Type | Description |
|---|---|---|
| `from` (optional) | string |  |
| `to` (optional) | string |  |
| `windows` (optional) | string | Comma-separated `24h,7d,30d,90d` (default all four). |

**Responses**

- `200`: list of `Uptime`
- Errors: `400` `bad_request`; `401` `unauthenticated`; `403` `forbidden`, `account_suspended`, `org_suspended`, `org_deleting`; `404` `not_found`; `422` `validation`; `429` `rate_limited`

### Get a path trace

`GET /orgs/{org}/traces/{trc}` · token role: `viewer`

One path trace with its hops.

**Path parameters**

| Name | Type | Description |
|---|---|---|
| `org` | id `org_…` | Organisation id (`org_…`); `GET /token` returns it |
| `trc` | id `trc_…` | Path trace id (`trc_…`) |

**Responses**

- `200`: [`PathTrace`](#schema-pathtrace)
- Errors: `401` `unauthenticated`; `403` `forbidden`, `account_suspended`, `org_suspended`, `org_deleting`; `404` `not_found`; `429` `rate_limited`

## Incidents

### List incidents

`GET /orgs/{org}/incidents` · token role: `viewer`

The organisation's incidents, newest first, filtered by status, monitor, severity and time.

**Path parameters**

| Name | Type | Description |
|---|---|---|
| `org` | id `org_…` | Organisation id (`org_…`); `GET /token` returns it |

**Query parameters**

| Name | Type | Description |
|---|---|---|
| `status` (optional) | string | `open` \| `resolved`. |
| `monitor_id` (optional) | string |  |
| `severity` (optional) | string | `down` \| `degraded`. |
| `from` (optional) | string | Opened at or after (clamped to plan history; open incidents are always listed). |
| `to` (optional) | string | Opened before. |
| `limit` (optional) | integer | 1-200, default 50. |
| `cursor` (optional) | string | `next_cursor` of the previous page. |

**Responses**

- `200`: page of [`Incident`](#schema-incident)
- Errors: `400` `bad_request`; `401` `unauthenticated`; `403` `forbidden`, `account_suspended`, `org_suspended`, `org_deleting`; `404` `not_found`; `422` `validation`; `429` `rate_limited`

### Get an incident

`GET /orgs/{org}/incidents/{inc}` · token role: `viewer`

One incident with its timeline events and the notifications it sent.

**Path parameters**

| Name | Type | Description |
|---|---|---|
| `org` | id `org_…` | Organisation id (`org_…`); `GET /token` returns it |
| `inc` | id `inc_…` | Incident id (`inc_…`) |

**Responses**

- `200`: [`IncidentDetail`](#schema-incidentdetail)
- Errors: `401` `unauthenticated`; `403` `forbidden`, `account_suspended`, `org_suspended`, `org_deleting`; `404` `not_found`; `429` `rate_limited`

### Acknowledge an incident

`POST /orgs/{org}/incidents/{inc}/acknowledge` · token role: `member`

Mark an open incident as acknowledged: reminders stop. Idempotent; `409 incident_resolved` once it is resolved.

**Path parameters**

| Name | Type | Description |
|---|---|---|
| `org` | id `org_…` | Organisation id (`org_…`); `GET /token` returns it |
| `inc` | id `inc_…` | Incident id (`inc_…`) |

**Responses**

- `200`: [`Incident`](#schema-incident)
- Errors: `401` `unauthenticated`; `403` `forbidden`, `billing_inactive`, `org_suspended`, `org_deleting`, `account_suspended`; `404` `not_found`; `409` `incident_resolved`; `429` `rate_limited`; `503` `unavailable`

### Add a note to an incident

`POST /orgs/{org}/incidents/{inc}/notes` · token role: `member`

Add a note (1-2000 characters) to an incident's timeline.

**Path parameters**

| Name | Type | Description |
|---|---|---|
| `org` | id `org_…` | Organisation id (`org_…`); `GET /token` returns it |
| `inc` | id `inc_…` | Incident id (`inc_…`) |

**Request body** ([`IncidentNoteInput`](#schema-incidentnoteinput))

| Field | Type | Description |
|---|---|---|
| `body` | string | 1-2000 characters. |

**Responses**

- `201`: [`IncidentEvent`](#schema-incidentevent)
- Errors: `400` `bad_request`; `401` `unauthenticated`; `403` `forbidden`, `billing_inactive`, `org_suspended`, `org_deleting`, `account_suspended`; `404` `not_found`; `413` `payload_too_large`; `415` `unsupported_media_type`; `422` `validation`; `429` `rate_limited`

## Contacts

### List contacts

`GET /orgs/{org}/contacts` · token role: `viewer`

The organisation's contacts (email, SMS, phone calls, Pushover, Discord, PagerDuty, Slack, webhooks), oldest first. Secrets are never returned.

**Path parameters**

| Name | Type | Description |
|---|---|---|
| `org` | id `org_…` | Organisation id (`org_…`); `GET /token` returns it |

**Query parameters**

| Name | Type | Description |
|---|---|---|
| `kind` (optional) | string | `email \| sms \| voice \| pushover \| discord \| pagerduty \| slack \| webhook \| teams`. |
| `status` (optional) | string | `unverified \| active \| disabled`. |
| `limit` (optional) | integer | 1-200, default 50. |
| `cursor` (optional) | string | `next_cursor` of the previous page. |

**Responses**

- `200`: page of [`Contact`](#schema-contact)
- Errors: `400` `bad_request`; `401` `unauthenticated`; `403` `forbidden`, `org_suspended`, `org_deleting`, `account_suspended`; `404` `not_found`; `422` `validation`; `429` `rate_limited`

### Create a contact

`POST /orgs/{org}/contacts` · token role: `member`

Create a contact. Email, SMS and voice contacts start `unverified` and are sent a 6-digit code (an SMS code uses one SMS credit; a voice contact gets a call that reads the code, which uses a call's credits); Pushover and Discord contacts are validated with the provider and start `active`; PagerDuty contacts (an Events API v2 integration key) are checked offline and start `active`; Slack contacts (an incoming webhook URL) are checked with Slack and start `active`; webhook contacts start `active` when their host has the organisation's TXT record (`config.txt_name`, `config.txt_value`), else `unverified` until a re-check finds it; Microsoft Teams contacts (a Workflows webhook URL) start `unverified` and get a 6-digit code posted into the channel.

**Path parameters**

| Name | Type | Description |
|---|---|---|
| `org` | id `org_…` | Organisation id (`org_…`); `GET /token` returns it |

**Headers**

| Name | Type | Description |
|---|---|---|
| `Idempotency-Key` (optional) | string or null | 8-64 characters [A-Za-z0-9_-] |

**Request body** ([`ContactInput`](#schema-contactinput))

| Field | Type | Description |
|---|---|---|
| `auto_attach` (optional) | boolean or null | Attach to monitors created without `contact_ids`. Default `false`. |
| `config` (optional) | object | Per kind: email `{"address"}`, sms and voice `{"phone"}` (E.164; a voice contact may be a landline), pushover `{"device", "priority_down", "priority_up", "sound"}`, discord `{}`, pagerduty `{"region"}` (`us` or `eu`, default `us`), slack `{}`, webhook `{}`, teams `{}`. |
| `kind` | `email` \| `sms` \| `pushover` \| `discord` \| `pagerduty` \| `slack` \| `webhook` \| `voice` \| `teams` |  |
| `name` (optional) | string | 1-100 characters. |
| `notify_on` (optional) | array of `down` \| `recovered` \| `degraded` or null | Non-empty subset of `down`, `recovered`, `degraded`; default `["down", "recovered"]`. |
| `secret` (optional) | object or null | Write-only: pushover `{"user_key"}`, discord `{"webhook_url"}`, pagerduty `{"routing_key"}` (an Events API v2 integration key), slack `{"webhook_url"}` (an incoming webhook), webhook `{"url"}` (`https://` on a hostname whose `_justmonitorit.<host>` TXT record is the org's `txt_value`), teams `{"webhook_url"}` (a Workflows webhook on `*.logic.azure.com` or `*.environment.api.powerplatform.com`); none for email, sms and voice. |

**Responses**

- `201`: [`Contact`](#schema-contact), header `ETag`
- Errors: `400` `bad_request`; `401` `unauthenticated`; `403` `forbidden`, `plan_limit`, `billing_inactive`, `org_suspended`, `org_deleting`, `account_suspended`; `404` `not_found`; `409` `sms_allowance_exhausted`, `idempotency_in_progress`; `413` `payload_too_large`; `415` `unsupported_media_type`; `422` `validation`, `idempotency_mismatch`; `429` `rate_limited`; `503` `unavailable`

### Get a contact

`GET /orgs/{org}/contacts/{con}` · token role: `viewer`

One contact. The `ETag` header carries `version` for `If-Match`.

**Path parameters**

| Name | Type | Description |
|---|---|---|
| `org` | id `org_…` | Organisation id (`org_…`); `GET /token` returns it |
| `con` | id `con_…` | Contact id (`con_…`) |

**Responses**

- `200`: [`Contact`](#schema-contact), header `ETag`
- Errors: `401` `unauthenticated`; `403` `forbidden`, `org_suspended`, `org_deleting`, `account_suspended`; `404` `not_found`; `429` `rate_limited`

### Update a contact

`PATCH /orgs/{org}/contacts/{con}` · token role: `member`

Change any field except `kind` (JSON merge patch). Changing an email address, phone number or Teams webhook URL makes the contact `unverified` again and sends a new code. A `secret` that is omitted is kept.

**Path parameters**

| Name | Type | Description |
|---|---|---|
| `org` | id `org_…` | Organisation id (`org_…`); `GET /token` returns it |
| `con` | id `con_…` | Contact id (`con_…`) |

**Headers**

| Name | Type | Description |
|---|---|---|
| `If-Match` (optional) | string or null | "<version>" |

**Request body** ([`ContactPatch`](#schema-contactpatch))

| Field | Type | Description |
|---|---|---|
| `auto_attach` (optional) | boolean or null |  |
| `config` (optional) | object or null |  |
| `kind` (optional) | `email` \| `sms` \| `pushover` \| `discord` \| `pagerduty` \| `slack` \| `webhook` \| `voice` \| `teams` or null | Not allowed (`422 not_allowed`). |
| `name` (optional) | string or null |  |
| `notify_on` (optional) | array of `down` \| `recovered` \| `degraded` or null |  |
| `secret` (optional) | object or null |  |

**Responses**

- `200`: [`Contact`](#schema-contact), header `ETag`
- Errors: `400` `bad_request`; `401` `unauthenticated`; `403` `forbidden`, `billing_inactive`, `org_suspended`, `org_deleting`, `account_suspended`; `404` `not_found`; `409` `conflict`, `sms_allowance_exhausted`; `412` `precondition_failed`; `413` `payload_too_large`; `415` `unsupported_media_type`; `422` `validation`; `429` `rate_limited`; `503` `unavailable`

### Delete a contact

`DELETE /orgs/{org}/contacts/{con}` · token role: `member`

Delete a contact; it is removed from every monitor and its pending notifications are dropped.

**Path parameters**

| Name | Type | Description |
|---|---|---|
| `org` | id `org_…` | Organisation id (`org_…`); `GET /token` returns it |
| `con` | id `con_…` | Contact id (`con_…`) |

**Headers**

| Name | Type | Description |
|---|---|---|
| `If-Match` (optional) | string or null | "<version>" |

**Responses**

- `204`: no body (deleted)
- Errors: `401` `unauthenticated`; `403` `forbidden`, `org_suspended`, `org_deleting`, `account_suspended`; `404` `not_found`; `412` `precondition_failed`; `422` `validation`; `429` `rate_limited`

### Disable a contact

`POST /orgs/{org}/contacts/{con}/disable` · token role: `member`

Stop sending to a contact (`disabled_reason = "user"`) without deleting it.

**Path parameters**

| Name | Type | Description |
|---|---|---|
| `org` | id `org_…` | Organisation id (`org_…`); `GET /token` returns it |
| `con` | id `con_…` | Contact id (`con_…`) |

**Responses**

- `200`: [`Contact`](#schema-contact), header `ETag`
- Errors: `401` `unauthenticated`; `403` `forbidden`, `org_suspended`, `org_deleting`, `account_suspended`; `404` `not_found`; `429` `rate_limited`

### Enable a contact

`POST /orgs/{org}/contacts/{con}/enable` · token role: `member`

Re-enable a contact disabled by a user or after repeated delivery failures. Other disabled states (bounce, complaint, unsubscribe, invalid) return through verification or a signed-in owner or admin.

**Path parameters**

| Name | Type | Description |
|---|---|---|
| `org` | id `org_…` | Organisation id (`org_…`); `GET /token` returns it |
| `con` | id `con_…` | Contact id (`con_…`) |

**Responses**

- `200`: [`Contact`](#schema-contact), header `ETag`
- Errors: `401` `unauthenticated`; `403` `forbidden` (also: a bounced address and the caller is not an owner or admin), `billing_inactive`, `org_suspended`, `org_deleting`, `account_suspended`; `404` `not_found`; `409` `conflict` (also: the address bounced before it was verified, or the email provider lists it for a complaint); `429` `rate_limited` (also: a bounced address was reactivated less than 24 h ago); `503` `unavailable` (the email provider could not reactivate the address)

### Set a contact's monitors

`PUT /orgs/{org}/contacts/{con}/monitors` · token role: `member`

Replace the set of monitors a contact is notified for.

**Path parameters**

| Name | Type | Description |
|---|---|---|
| `org` | id `org_…` | Organisation id (`org_…`); `GET /token` returns it |
| `con` | id `con_…` | Contact id (`con_…`) |

**Headers**

| Name | Type | Description |
|---|---|---|
| `If-Match` (optional) | string or null | "<version>" of the contact |

**Request body** ([`ContactMonitors`](#schema-contactmonitors))

| Field | Type | Description |
|---|---|---|
| `monitor_ids` | array of id `mon_…` | At most 1,000; replaces the contact's monitor links. |

**Responses**

- `200`: [`ContactMonitors`](#schema-contactmonitors), header `ETag`
- Errors: `400` `bad_request`; `401` `unauthenticated`; `403` `forbidden`, `billing_inactive`, `org_suspended`, `org_deleting`, `account_suspended`; `404` `not_found`; `412` `precondition_failed`; `413` `payload_too_large`; `415` `unsupported_media_type`; `422` `validation`; `429` `rate_limited`

### Send a test notification

`POST /orgs/{org}/contacts/{con}/test` · token role: `member`

Queue a test message to an active contact and return the notification id; follow it with `GET /orgs/{org}/notifications/{ntf}`. `409 contact_unverified`, `contact_disabled` or `sms_allowance_exhausted` when it cannot be sent.

**Path parameters**

| Name | Type | Description |
|---|---|---|
| `org` | id `org_…` | Organisation id (`org_…`); `GET /token` returns it |
| `con` | id `con_…` | Contact id (`con_…`) |

**Headers**

| Name | Type | Description |
|---|---|---|
| `Idempotency-Key` (optional) | string or null | 8-64 characters [A-Za-z0-9_-] |

**Responses**

- `202`: [`TestSent`](#schema-testsent)
- Errors: `401` `unauthenticated`; `403` `forbidden`, `billing_inactive`, `org_suspended`, `org_deleting`, `account_suspended`; `404` `not_found`; `409` `contact_unverified`, `contact_disabled`, `sms_allowance_exhausted`, `idempotency_in_progress`, `conflict`; `422` `validation`, `idempotency_mismatch`; `429` `rate_limited`

### Verify a contact

`POST /orgs/{org}/contacts/{con}/verify` · token role: `member`

Verify an email, SMS, voice or Teams contact with the 6-digit code it received. A wrong code is `422 validation` with `errors[0].code = "invalid_code"`; after 5 wrong codes the code is void.

**Path parameters**

| Name | Type | Description |
|---|---|---|
| `org` | id `org_…` | Organisation id (`org_…`); `GET /token` returns it |
| `con` | id `con_…` | Contact id (`con_…`) |

**Request body** ([`VerifyInput`](#schema-verifyinput))

| Field | Type | Description |
|---|---|---|
| `code` | string | The 6-digit code. |

**Responses**

- `200`: [`Contact`](#schema-contact), header `ETag`
- Errors: `400` `bad_request`; `401` `unauthenticated`; `403` `forbidden`, `billing_inactive`, `org_suspended`, `org_deleting`, `account_suspended`; `404` `not_found`; `409` `conflict`; `413` `payload_too_large`; `415` `unsupported_media_type`; `422` `validation` (invalid_code, with attempts_left); `429` `rate_limited`

### Send a verification code

`POST /orgs/{org}/contacts/{con}/verify/send` · token role: `member`

Send a new code to an email, SMS, voice or Teams contact, or validate a Pushover, Discord, PagerDuty or Slack contact again, or look for a webhook's TXT record again (it becomes `active` on success).

**Path parameters**

| Name | Type | Description |
|---|---|---|
| `org` | id `org_…` | Organisation id (`org_…`); `GET /token` returns it |
| `con` | id `con_…` | Contact id (`con_…`) |

**Responses**

- `202`: no body (code sent, or validation passed)
- Errors: `401` `unauthenticated`; `403` `forbidden`, `billing_inactive`, `org_suspended`, `org_deleting`, `account_suspended`; `404` `not_found`; `409` `conflict`, `sms_allowance_exhausted`; `422` `validation` (pushover_invalid, `discord_webhook_invalid)`; `429` `rate_limited`; `503` `unavailable`

## Notifications

### List notifications

`GET /orgs/{org}/notifications` · token role: `viewer`

The organisation's notification log, newest first, with each delivery attempt. Filter by contact, incident, monitor, status or kind.

**Path parameters**

| Name | Type | Description |
|---|---|---|
| `org` | id `org_…` | Organisation id (`org_…`); `GET /token` returns it |

**Query parameters**

| Name | Type | Description |
|---|---|---|
| `contact_id` (optional) | string | `con_…`: the delivery log of one contact. |
| `incident_id` (optional) | string | `inc_…` |
| `monitor_id` (optional) | string | `mon_…` |
| `status` (optional) | string | `pending \| sending \| sent \| failed \| cancelled \| suppressed` |
| `kind` (optional) | string | A notification kind. |
| `cursor` (optional) | string | Opaque cursor from `next_cursor`. |
| `limit` (optional) | integer | 1-200, default 50. |

**Responses**

- `200`: page of [`Notification`](#schema-notification)
- Errors: `400` `bad_request`; `401` `unauthenticated`; `403` `org_suspended`, `org_deleting`, `account_suspended`; `404` `not_found` (not a member); `422` `validation` (bad filter or cursor); `429` `rate_limited`

### Get a notification

`GET /orgs/{org}/notifications/{ntf}` · token role: `viewer`

One notification with its delivery attempts and provider status.

**Path parameters**

| Name | Type | Description |
|---|---|---|
| `org` | id `org_…` | Organisation id (`org_…`); `GET /token` returns it |
| `ntf` | id `ntf_…` | Notification id (`ntf_…`) |

**Responses**

- `200`: [`Notification`](#schema-notification)
- Errors: `401` `unauthenticated`; `403` `org_suspended`, `org_deleting`, `account_suspended`; `404` `not_found`; `429` `rate_limited`

## Maintenance windows

### List maintenance windows

`GET /orgs/{org}/maintenance` · token role: `viewer`

Active and upcoming maintenance windows, and those that ended in the last 30 days, by start. While a window is active, the monitors it covers keep checking and record incidents, but their alerts are held until it ends.

**Path parameters**

| Name | Type | Description |
|---|---|---|
| `org` | id `org_…` | Organisation id (`org_…`); `GET /token` returns it |

**Responses**

- `200`: list of [`MaintenanceWindow`](#schema-maintenancewindow)
- Errors: `401` `unauthenticated`; `403` `forbidden`, `org_suspended`, `org_deleting`; `404` `not_found`; `429` `rate_limited`

### Schedule a maintenance window

`POST /orgs/{org}/maintenance` · token role: `member`

Hold the alerts of all monitors (`all_monitors: true`, including monitors added later) or of `monitor_ids` from `starts_at` (default now) until `ends_at`, or for `duration_minutes`; at most 7 days. When it ends, each incident still open alerts its contacts once; incidents that ended inside the window send nothing.

**Path parameters**

| Name | Type | Description |
|---|---|---|
| `org` | id `org_…` | Organisation id (`org_…`); `GET /token` returns it |

**Request body** ([`CreateMaintenanceRequest`](#schema-createmaintenancerequest))

| Field | Type | Description |
|---|---|---|
| `all_monitors` (optional) | boolean | Cover every monitor, including ones added later. |
| `duration_minutes` (optional) | integer or null | 1-10080, instead of `ends_at`. |
| `ends_at` (optional) | timestamp or null | After `starts_at`, at most 7 days later. |
| `monitor_ids` (optional) | array of string | The monitors to cover when `all_monitors` is false (at least one). |
| `name` | string | 1-100 characters, e.g. "Postgres upgrade". |
| `starts_at` (optional) | timestamp or null | Absent = now. At most a year ahead. |

**Responses**

- `201`: [`MaintenanceWindow`](#schema-maintenancewindow)
- Errors: `400` `bad_request`; `401` `unauthenticated`; `403` `forbidden`, `org_suspended`, `org_deleting`; `404` `not_found`; `409` `conflict` (50 windows that have not ended); `413` `payload_too_large`; `415` `unsupported_media_type`; `422` `validation`; `429` `rate_limited`

### Get a maintenance window

`GET /orgs/{org}/maintenance/{mnt}` · token role: `viewer`

One maintenance window, with `state` (`upcoming`, `active` or `ended`) and the number of incidents whose alerts it holds now.

**Path parameters**

| Name | Type | Description |
|---|---|---|
| `org` | id `org_…` | Organisation id (`org_…`); `GET /token` returns it |
| `mnt` | string | Window id (`mnt_…`) |

**Responses**

- `200`: [`MaintenanceWindow`](#schema-maintenancewindow)
- Errors: `401` `unauthenticated`; `403` `forbidden`, `org_suspended`, `org_deleting`; `404` `not_found`; `429` `rate_limited`

### Change a maintenance window

`PATCH /orgs/{org}/maintenance/{mnt}` · token role: `member`

Rename a window that has not ended or move its end (for example to extend it); the start can only move before the window starts.

**Path parameters**

| Name | Type | Description |
|---|---|---|
| `org` | id `org_…` | Organisation id (`org_…`); `GET /token` returns it |
| `mnt` | string | Window id (`mnt_…`) |

**Request body** ([`UpdateMaintenanceRequest`](#schema-updatemaintenancerequest))

| Field | Type | Description |
|---|---|---|
| `ends_at` (optional) | timestamp or null |  |
| `name` (optional) | string or null |  |
| `starts_at` (optional) | timestamp or null |  |

**Responses**

- `200`: [`MaintenanceWindow`](#schema-maintenancewindow)
- Errors: `400` `bad_request`; `401` `unauthenticated`; `403` `forbidden`, `org_suspended`, `org_deleting`; `404` `not_found`; `409` `conflict` (the window has ended); `415` `unsupported_media_type`; `422` `validation`; `429` `rate_limited`

### Delete a maintenance window

`DELETE /orgs/{org}/maintenance/{mnt}` · token role: `member`

Delete a window that has not started. To stop an active window, end it instead.

**Path parameters**

| Name | Type | Description |
|---|---|---|
| `org` | id `org_…` | Organisation id (`org_…`); `GET /token` returns it |
| `mnt` | string | Window id (`mnt_…`) |

**Responses**

- `204`: no body (deleted)
- Errors: `401` `unauthenticated`; `403` `forbidden`, `org_suspended`, `org_deleting`; `404` `not_found`; `409` `conflict` (started: end it instead); `429` `rate_limited`

### End a maintenance window now

`POST /orgs/{org}/maintenance/{mnt}/end` · token role: `member`

End an active window now. Within seconds, each incident still open alerts its contacts once.

**Path parameters**

| Name | Type | Description |
|---|---|---|
| `org` | id `org_…` | Organisation id (`org_…`); `GET /token` returns it |
| `mnt` | string | Window id (`mnt_…`) |

**Responses**

- `200`: [`MaintenanceWindow`](#schema-maintenancewindow)
- Errors: `401` `unauthenticated`; `403` `forbidden`, `org_suspended`, `org_deleting`; `404` `not_found`; `409` `conflict` (not started: delete it; or already ended); `429` `rate_limited`

## Billing

### List plans

`GET /billing/plans` · token role: none (public)

The plans on sale with their prices and limits. No authentication needed.

**Responses**

- `200`: list of `PlanOffer`
- Errors: `404` `billing_disabled` (`[billing] mode = "disabled"`); `429` `rate_limited`; `503` `unavailable:` Stripe not configured, unreachable, or a catalogue price is missing

### Billing state

`GET /orgs/{org}/billing` · token role: `viewer`

The organisation's plan, subscription state, entitlements and usage. Checkout and the customer portal need a signed-in owner.

**Path parameters**

| Name | Type | Description |
|---|---|---|
| `org` | string | Organisation id (`org_…`); `GET /token` returns it |

**Responses**

- `200`: [`Billing`](#schema-billing)
- Errors: `401` `unauthenticated`; `403` `account_suspended`, `org_deleting`; `404` `not_found` (unknown org or not a member), `billing_disabled` (`[billing] mode = "disabled"`; use `GET /orgs/{org}/entitlements`); `429` `rate_limited`

## Schemas {#schemas}

Response and request objects referenced above. Every field of a response is present; absent values are `null`.

### ApiToken {#schema-apitoken}

An API token (never its secret).

| Field | Type | Description |
|---|---|---|
| `created_at` | timestamp |  |
| `created_by` | [`UserRef`](#schema-userref) | The member the token acts for. |
| `expires_at` | timestamp or null | `null` = never expires. |
| `id` | id `tok_…` |  |
| `last_used_at` | timestamp or null |  |
| `last_used_ip` | string or null |  |
| `name` | string |  |
| `prefix` | string | The first 8 characters after `jmi_`, for recognising the token. |
| `role` | `admin` \| `member` \| `viewer` |  |

### Assertion {#schema-assertion}

An `http_adv` assertion.

With `type` = `body_contains`:

| Field | Type | Description |
|---|---|---|
| `type` | `body_contains` |  |
| `value` | string |  |

With `type` = `body_not_contains`:

| Field | Type | Description |
|---|---|---|
| `type` | `body_not_contains` |  |
| `value` | string |  |

With `type` = `body_regex`:

| Field | Type | Description |
|---|---|---|
| `pattern` | string |  |
| `type` | `body_regex` |  |

With `type` = `json_path`:

| Field | Type | Description |
|---|---|---|
| `op` | `exists` \| `not_exists` \| `equals` \| `not_equals` \| `contains` \| `lt` \| `gt` |  |
| `path` | string |  |
| `type` | `json_path` |  |
| `value` | any | Any JSON value; required except for `exists` and `not_exists`. |

With `type` = `header`:

| Field | Type | Description |
|---|---|---|
| `name` | string |  |
| `op` | `exists` \| `not_exists` \| `equals` \| `contains` \| `regex` |  |
| `type` | `header` |  |
| `value` | string or null |  |

### Billing {#schema-billing}

An org's subscription, entitlements and usage.

| Field | Type | Description |
|---|---|---|
| `billing_interval` | `month` \| `year` or null |  |
| `cancel_at_period_end` | boolean |  |
| `current_period_end` | timestamp or null |  |
| `entitlements` | [`Entitlements`](#schema-entitlements) |  |
| `grace_ends_at` | timestamp or null |  |
| `past_due_since` | timestamp or null |  |
| `plan` | `trial` \| `starter` \| `pro` \| `business` |  |
| `status` | `trialing` \| `active` \| `past_due` \| `canceled` | Effective status: a lapsed trial or grace period is `canceled`. |
| `trial_ends_at` | timestamp or null |  |
| `usage` | [`BillingUsage`](#schema-billingusage) |  |

### BillingUsage {#schema-billingusage}

Current usage of an org (`Billing.usage`).

| Field | Type | Description |
|---|---|---|
| `checks_per_minute` | integer |  |
| `contacts` | integer |  |
| `monitors` | integer |  |
| `seats` | integer |  |
| `sms_credits_limit` | integer |  |
| `sms_credits_used` | integer |  |
| `sms_period_end` | timestamp | Start of the next UTC month, when the credits reset. |
| `sms_state` | `ok` \| `warning` \| `exhausted` |  |

### CheckResult {#schema-checkresult}

One check result.

| Field | Type | Description |
|---|---|---|
| `error_detail` | string or null |  |
| `error_kind` | `dns_failure` \| `blocked_address` \| `connect_timeout` \| `connection_refused` \| `connection_reset` \| `host_unreachable` \| `tls_error` \| `cert_expired` \| `timeout` \| `protocol_error` \| `status_mismatch` \| `assertion_failed` \| `too_many_redirects` \| `redirect_blocked` \| `packet_loss` \| `slow_response` \| `cert_expiring` \| `domain_expiring` \| `domain_expired` \| `domain_not_found` \| `rdap_unavailable` \| `heartbeat_missed` \| `heartbeat_failed` \| `network_unreachable` \| `icmp_unavailable` \| `resource_exhausted` \| `probe_overloaded` \| `internal` or null |  |
| `latency_ms` | integer or null |  |
| `location` | string | Code of the location that ran the check. |
| `metrics` | object or null |  |
| `outcome` | `up` \| `degraded` \| `down` \| `error` |  |
| `resolved_ip` | string or null |  |
| `slot_location` | string or null | Code of the configured location it stood in for, if substituted. |
| `status_code` | integer or null |  |
| `timings` | [`CheckTimings`](#schema-checktimings) |  |
| `tls_not_after` | timestamp or null |  |
| `ts` | timestamp |  |
| `work_kind` | `round` \| `recheck` \| `shadow` \| `canary` \| `preview` \| `trace` |  |

### CheckTimings {#schema-checktimings}

Phase timings of a result.

| Field | Type | Description |
|---|---|---|
| `connect_ms` | integer or null |  |
| `dns_ms` | integer or null |  |
| `tls_ms` | integer or null |  |
| `total_ms` | integer or null |  |
| `ttfb_ms` | integer or null |  |

### Contact {#schema-contact}

A contact (secrets are never returned; `secret_set` says whether one is stored).

| Field | Type | Description |
|---|---|---|
| `auto_attach` | boolean |  |
| `config` | object | Stored config: email `{"address"}`, sms and voice `{"phone_e164", "country_code"}`, pushover `{"device", "priority_down", "priority_up", "sound", "devices"}`, discord `{"webhook_id", "webhook_name", "channel_id", "guild_id"}`, pagerduty `{"region", "key_hint"}`, slack `{"team_id", "webhook_id"}`, webhook `{"host", "txt_name", "txt_value"}` (the TXT record that verifies the host), teams `{"host", "workflow_id"}`. |
| `consecutive_failures` | integer |  |
| `created_at` | timestamp |  |
| `credits_per_call` | integer or null | Voice only: the SMS credits each call to this number uses, from what the number costs to call. `null` for other kinds. |
| `disabled_reason` | `user` \| `bounced` \| `complaint` \| `opted_out` \| `invalid` \| `unsubscribed` \| `failing` \| `abuse` or null |  |
| `id` | id `con_…` |  |
| `kind` | `email` \| `sms` \| `pushover` \| `discord` \| `pagerduty` \| `slack` \| `webhook` \| `voice` \| `teams` |  |
| `last_delivery_at` | timestamp or null |  |
| `last_error` | string or null |  |
| `monitor_count` | integer |  |
| `name` | string |  |
| `notify_on` | array of `down` \| `recovered` \| `degraded` |  |
| `secret_set` | boolean |  |
| `status` | `unverified` \| `active` \| `disabled` |  |
| `updated_at` | timestamp |  |
| `verified_at` | timestamp or null |  |
| `version` | integer | Optimistic-concurrency version (also the `ETag`). |

### ContactInput {#schema-contactinput}

`POST /orgs/{org}/contacts` body.

| Field | Type | Description |
|---|---|---|
| `auto_attach` (optional) | boolean or null | Attach to monitors created without `contact_ids`. Default `false`. |
| `config` (optional) | object | Per kind: email `{"address"}`, sms and voice `{"phone"}` (E.164; a voice contact may be a landline), pushover `{"device", "priority_down", "priority_up", "sound"}`, discord `{}`, pagerduty `{"region"}` (`us` or `eu`, default `us`), slack `{}`, webhook `{}`, teams `{}`. |
| `kind` | `email` \| `sms` \| `pushover` \| `discord` \| `pagerduty` \| `slack` \| `webhook` \| `voice` \| `teams` |  |
| `name` (optional) | string | 1-100 characters. |
| `notify_on` (optional) | array of `down` \| `recovered` \| `degraded` or null | Non-empty subset of `down`, `recovered`, `degraded`; default `["down", "recovered"]`. |
| `secret` (optional) | object or null | Write-only: pushover `{"user_key"}`, discord `{"webhook_url"}`, pagerduty `{"routing_key"}` (an Events API v2 integration key), slack `{"webhook_url"}` (an incoming webhook), webhook `{"url"}` (`https://` on a hostname whose `_justmonitorit.<host>` TXT record is the org's `txt_value`), teams `{"webhook_url"}` (a Workflows webhook on `*.logic.azure.com` or `*.environment.api.powerplatform.com`); none for email, sms and voice. |

### ContactMonitors {#schema-contactmonitors}

`PUT .../monitors` body and response.

| Field | Type | Description |
|---|---|---|
| `monitor_ids` | array of id `mon_…` | At most 1,000; replaces the contact's monitor links. |

### ContactPatch {#schema-contactpatch}

`PATCH /orgs/{org}/contacts/{con}` body (JSON merge patch; `kind` is immutable, `config` is merged, `secret` replaces the stored one).

| Field | Type | Description |
|---|---|---|
| `auto_attach` (optional) | boolean or null |  |
| `config` (optional) | object or null |  |
| `kind` (optional) | `email` \| `sms` \| `pushover` \| `discord` \| `pagerduty` \| `slack` \| `webhook` \| `voice` \| `teams` or null | Not allowed (`422 not_allowed`). |
| `name` (optional) | string or null |  |
| `notify_on` (optional) | array of `down` \| `recovered` \| `degraded` or null |  |
| `secret` (optional) | object or null |  |

### ContactRef {#schema-contactref}

`{ "id": "con_…", "name": "…", "kind": "sms" }`.

| Field | Type | Description |
|---|---|---|
| `id` | id `con_…` |  |
| `kind` | `email` \| `sms` \| `pushover` \| `discord` \| `pagerduty` \| `slack` \| `webhook` \| `voice` \| `teams` |  |
| `name` | string |  |

### CreateInviteRequest {#schema-createinviterequest}

`{email, role}`.

| Field | Type | Description |
|---|---|---|
| `email` | string |  |
| `role` | `owner` \| `admin` \| `member` \| `viewer` |  |

### CreateMaintenanceRequest {#schema-createmaintenancerequest}

`POST /orgs/{org}/maintenance` body. Give `ends_at` or `duration_minutes`, and `all_monitors: true` or `monitor_ids`.

| Field | Type | Description |
|---|---|---|
| `all_monitors` (optional) | boolean | Cover every monitor, including ones added later. |
| `duration_minutes` (optional) | integer or null | 1-10080, instead of `ends_at`. |
| `ends_at` (optional) | timestamp or null | After `starts_at`, at most 7 days later. |
| `monitor_ids` (optional) | array of string | The monitors to cover when `all_monitors` is false (at least one). |
| `name` | string | 1-100 characters, e.g. "Postgres upgrade". |
| `starts_at` (optional) | timestamp or null | Absent = now. At most a year ahead. |

### DeliveryView {#schema-deliveryview}

One delivery attempt of a notification.

| Field | Type | Description |
|---|---|---|
| `attempt` | integer |  |
| `error` | string or null |  |
| `error_code` | string or null |  |
| `http_status` | integer or null |  |
| `id` | id `dlv_…` |  |
| `latency_ms` | integer |  |
| `provider_status` | `delivered` \| `undelivered` \| `failed` \| `bounced` \| `complaint` \| `acknowledged` or null |  |
| `provider_status_at` | timestamp or null |  |
| `started_at` | timestamp |  |
| `status` | `sent` \| `failed_retryable` \| `failed_permanent` \| `ambiguous` |  |

### DomainExpirySpec {#schema-domainexpiryspec}

`domain_expiry`.

| Field | Type | Description |
|---|---|---|
| `domain` | string | The registered domain, IDNA A-label, lower case. |
| `expiry_warn_days` | integer | Degraded this many days before expiry, 1-365 (default 30). |
| `timeout_ms` | integer | 1000-30000 (default 10000). |

### Entitlements {#schema-entitlements}

The single answer to "what may this org do now".

| Field | Type | Description |
|---|---|---|
| `history_retention_days` | integer |  |
| `max_checks_per_minute` | integer |  |
| `max_contacts` | integer |  |
| `max_locations_per_monitor` | integer |  |
| `max_monitors` | integer |  |
| `max_seats` | integer |  |
| `min_interval_s` | integer |  |
| `min_locations_per_monitor` | integer |  |
| `monitors_may_run` | boolean |  |
| `plan` | `trial` \| `starter` \| `pro` \| `business` |  |
| `raw_retention_days` | integer |  |
| `sms_credits_per_month` | integer |  |
| `status` | `trialing` \| `active` \| `past_due` \| `canceled` | Effective: a lapsed trial or grace period counts as `canceled`. |
| `writes_allowed` | boolean |  |

### HeaderSpec {#schema-headerspec}

One request header of an `http_adv` check.

| Field | Type | Description |
|---|---|---|
| `name` | string |  |
| `secret` | boolean |  |
| `value` | string or null | The header value. With `secret`, write-only: sent in requests, `null` in responses (the value lives in the secrets blob). |

### HeartbeatSpec {#schema-heartbeatspec}

`heartbeat`. The period between pings is the monitor's `interval_s`.

| Field | Type | Description |
|---|---|---|
| `grace_s` | integer | How late a ping may be before the monitor is down, 60-604800 s (default 300). |

### HttpAdvSpec {#schema-httpadvspec}

`http_adv` spec in responses. A secret header has `"secret": true, "value": null`.

| Field | Type | Description |
|---|---|---|
| `assertions` | array of [`Assertion`](#schema-assertion) |  |
| `auth` | [`HttpAuthView`](#schema-httpauthview) |  |
| `body` | string or null |  |
| `body_max_bytes` | integer |  |
| `cert_expiry_warn_days` | integer or null |  |
| `expected_status` | array of string |  |
| `follow_redirects` | boolean |  |
| `headers` | array of [`HeaderSpec`](#schema-headerspec) |  |
| `ip_family` | `auto` \| `ipv4` \| `ipv6` |  |
| `max_redirects` | integer |  |
| `method` | `GET` \| `HEAD` \| `POST` \| `PUT` \| `PATCH` \| `DELETE` \| `OPTIONS` |  |
| `timeout_ms` | integer |  |
| `url` | string |  |
| `verify_tls` | boolean |  |

### HttpAdvSpecInput {#schema-httpadvspecinput}

`http_adv` spec in requests. A header with `secret: true` carries its write-only value in `value`.

| Field | Type | Description |
|---|---|---|
| `assertions` (optional) | array of [`Assertion`](#schema-assertion) | At most 20. |
| `auth` (optional) | [`HttpAuthInput`](#schema-httpauthinput) or null | Default `{"type": "none"}`. |
| `body` (optional) | string or null | Not with `GET` or `HEAD`; at most 16 KiB. |
| `body_max_bytes` (optional) | integer | Default 65536. |
| `cert_expiry_warn_days` (optional) | integer or null | Default 14; `null` disables the warning. |
| `expected_status` (optional) | array of string | Codes (`"200"`) or ranges (`"200-299"`); default `["200-299"]`. |
| `follow_redirects` (optional) | boolean | Default `true`. |
| `headers` (optional) | array of [`HeaderSpec`](#schema-headerspec) | At most 32; a secret header needs a unique name. |
| `ip_family` (optional) | `auto` \| `ipv4` \| `ipv6` |  |
| `max_redirects` (optional) | integer | 0-10, default 5. |
| `method` (optional) | `GET` \| `HEAD` \| `POST` \| `PUT` \| `PATCH` \| `DELETE` \| `OPTIONS` | Default `GET`. |
| `timeout_ms` (optional) | integer | Default 10000. |
| `url` | string |  |
| `verify_tls` (optional) | boolean | Default `true`. |

### HttpAuthInput {#schema-httpauthinput}

`auth` of an `http_adv` spec in requests. A secret that is absent or `null` keeps the stored value; `""` clears it.

With `type` = `none`:

| Field | Type | Description |
|---|---|---|
| `type` | `none` |  |

With `type` = `basic`:

| Field | Type | Description |
|---|---|---|
| `password` (optional) | string or null | Write-only. |
| `type` | `basic` |  |
| `username` | string |  |

With `type` = `bearer`:

| Field | Type | Description |
|---|---|---|
| `token` (optional) | string or null | Write-only. |
| `type` | `bearer` |  |

### HttpAuthView {#schema-httpauthview}

`auth` of an `http_adv` spec in responses: whether each secret is stored, never its value.

With `type` = `none`:

| Field | Type | Description |
|---|---|---|
| `type` | `none` |  |

With `type` = `basic`:

| Field | Type | Description |
|---|---|---|
| `password_set` | boolean |  |
| `type` | `basic` |  |
| `username` | string |  |

With `type` = `bearer`:

| Field | Type | Description |
|---|---|---|
| `token_set` | boolean |  |
| `type` | `bearer` |  |

### HttpSpec {#schema-httpspec}

`http`.

| Field | Type | Description |
|---|---|---|
| `ip_family` | `auto` \| `ipv4` \| `ipv6` |  |
| `keyword` | string or null | Text the body must contain, 1-128 bytes. |
| `timeout_ms` | integer | 1000-30000 (default 10000). |
| `url` | string | `http` or `https` URL (normalised, fragment dropped). |

### IcmpSpec {#schema-icmpspec}

`icmp`.

| Field | Type | Description |
|---|---|---|
| `count` | integer | Pings per check, 1-10 (default 3). |
| `host` | string | Host name or public IP address. |
| `interval_ms` | integer | Between pings, 200-1000 (default 500). |
| `ip_family` | `auto` \| `ipv4` \| `ipv6` |  |
| `loss_threshold_pct` | integer | Packet loss that counts as down, 1-100 (default 100). |
| `payload_bytes` | integer | 16-1024 (default 56). |
| `timeout_ms` | integer | Per ping, 500-10000 (default 2000). |

### Incident {#schema-incident}

An incident.

| Field | Type | Description |
|---|---|---|
| `acknowledged_at` | timestamp or null |  |
| `acknowledged_by` | [`UserRef`](#schema-userref) or null | `null` with `acknowledged_at` set = acknowledged via Pushover. |
| `cause` | string |  |
| `cause_detail` | object | `RootCause` with location ids expanded to `{id, code, name}`. |
| `current_status` | `up` \| `down` \| `degraded` \| `no_data` |  |
| `duration_s` | integer | From the first failure to resolution (or now). |
| `first_failure_at` | timestamp |  |
| `flapping` | boolean |  |
| `id` | id `inc_…` |  |
| `monitor` | [`IncidentMonitor`](#schema-incidentmonitor) |  |
| `notification_count` | integer | Notification jobs of this incident with status `sent`. |
| `opened_at` | timestamp |  |
| `reminders_sent` | integer |  |
| `resolution` | `recovered` \| `paused` or null |  |
| `resolved_at` | timestamp or null |  |
| `severity` | `down` \| `degraded` | Worst status reached. |
| `status` | `open` \| `resolved` |  |

### IncidentDetail {#schema-incidentdetail}

An incident with its timeline and notifications.

Every field of [`Incident`](#schema-incident), and:

| Field | Type | Description |
|---|---|---|
| `events` | array of [`IncidentEvent`](#schema-incidentevent) |  |
| `notifications` | array of [`Notification`](#schema-notification) |  |

### IncidentEvent {#schema-incidentevent}

One timeline entry of an incident.

| Field | Type | Description |
|---|---|---|
| `actor` | [`UserRef`](#schema-userref) or null | The user who acted, `null` for system events. |
| `data` | object | Per type. |
| `id` | id `iev_…` |  |
| `ts` | timestamp |  |
| `type` | `check_failed` \| `opened` \| `status_changed` \| `location_failed` \| `location_recovered` \| `no_data` \| `data_resumed` \| `flapping_started` \| `flapping_ended` \| `acknowledged` \| `reminder_sent` \| `reminders_exhausted` \| `note` \| `resolved` \| `no_contacts` \| `notification_sent` \| `notification_failed` \| `notification_suppressed` \| `notification_undelivered` |  |

### IncidentMonitor {#schema-incidentmonitor}

`{ "id": "mon_…", "name": "…" }`.

| Field | Type | Description |
|---|---|---|
| `id` | id `mon_…` |  |
| `name` | string |  |

### IncidentNoteInput {#schema-incidentnoteinput}

`POST .../notes` body.

| Field | Type | Description |
|---|---|---|
| `body` | string | 1-2000 characters. |

### Invite {#schema-invite}

An open invitation.

| Field | Type | Description |
|---|---|---|
| `created_at` | timestamp |  |
| `email` | string |  |
| `expires_at` | timestamp |  |
| `id` | id `inv_…` |  |
| `invited_by` | [`UserRef`](#schema-userref) or null | `null` if the inviter's account was deleted. |
| `role` | `owner` \| `admin` \| `member` \| `viewer` |  |

### Location {#schema-location}

A customer-selectable location.

| Field | Type | Description |
|---|---|---|
| `available` | boolean | Status `active` and at least one eligible probe. |
| `code` | string |  |
| `country_code` | string |  |
| `id` | id `loc_…` |  |
| `ipv6` | boolean |  |
| `name` | string |  |
| `region` | `eu` \| `na` \| `sa` \| `as` \| `oc` \| `af` |  |

### LocationsResponse {#schema-locationsresponse}

`GET /locations` response.

| Field | Type | Description |
|---|---|---|
| `locations` | array of [`Location`](#schema-location) |  |
| `regions` | array of [`RegionView`](#schema-regionview) |  |

### MailSpec {#schema-mailspec}

`imap` and `smtp`.

| Field | Type | Description |
|---|---|---|
| `cert_expiry_warn_days` | integer or null | Degraded this many days before the certificate expires, 1-90 (default 14); `null` disables the warning; always `null` with `tls = none`. |
| `host` | string | Host name or public IP address. |
| `ip_family` | `auto` \| `ipv4` \| `ipv6` |  |
| `port` | integer | 1-65535. |
| `timeout_ms` | integer | 1000-30000 (default 10000). |
| `tls` | `implicit` \| `starttls` \| `none` |  |
| `verify_tls` | boolean | Default `true`; always `true` with `tls = none`. |

### MaintenanceWindow {#schema-maintenancewindow}

A maintenance window: while it is active, the monitors it covers keep checking and record incidents, but their alerts are held. When it ends, each incident still open alerts its contacts once; incidents that ended inside the window send nothing.

| Field | Type | Description |
|---|---|---|
| `all_monitors` | boolean | Every monitor of the org, including ones added later. |
| `created_at` | timestamp |  |
| `created_by` | [`UserRef`](#schema-userref) or null | `null` if the member left the org. |
| `ended_early` | boolean | Ended before its scheduled end. |
| `ends_at` | timestamp |  |
| `held_incidents` | integer | Incidents whose alerts the window is holding now. |
| `id` | id `mnt_…` |  |
| `monitor_ids` | array of id `mon_…` | The monitors covered when `all_monitors` is false. |
| `name` | string |  |
| `starts_at` | timestamp |  |
| `state` | `upcoming` \| `active` \| `ended` |  |

### Member {#schema-member}

One member of an org.

| Field | Type | Description |
|---|---|---|
| `joined_at` | timestamp |  |
| `last_active_at` | timestamp or null | Latest activity of any of the user's sessions. |
| `role` | `owner` \| `admin` \| `member` \| `viewer` |  |
| `user` | [`MemberUser`](#schema-memberuser) |  |

### MemberUser {#schema-memberuser}

The user part of a member.

| Field | Type | Description |
|---|---|---|
| `email` | string |  |
| `id` | id `usr_…` |  |
| `name` | string |  |
| `totp_enabled` | boolean |  |

### Monitor {#schema-monitor}

A monitor. Secret values are never returned.

| Field | Type | Description |
|---|---|---|
| `contact_ids` | array of id `con_…` |  |
| `created_at` | timestamp |  |
| `degraded_after_ms` | integer or null |  |
| `failure_threshold` | integer |  |
| `id` | id `mon_…` |  |
| `interval_s` | integer |  |
| `kind` | `icmp` \| `http` \| `http_adv` \| `tcp` \| `ssl_expiry` \| `domain_expiry` \| `imap` \| `smtp` \| `heartbeat` |  |
| `labels` | array of string |  |
| `locations` | [`MonitorLocations`](#schema-monitorlocations) or null | `null` for `domain_expiry`: each lookup runs from one location, chosen each day; and for `heartbeat`, which runs no checks. |
| `name` | string |  |
| `ping_url` | string or null | `heartbeat` only: the URL its job requests (`GET`, `HEAD` or `POST`) each time it runs. `null` for every other kind, and for a viewer: the URL can keep the monitor UP. |
| `recovery_threshold` | integer |  |
| `reminder_interval_s` | integer or null |  |
| `run_state` | `active` \| `paused` \| `system_paused` |  |
| `spec` | [`MonitorSpec`](#schema-monitorspec) | The per-kind spec without the type tag (the `MonitorSpec` schema of `kind`); secrets show as `password_set` / `token_set` / `"value": null`. Fields per kind: [Monitor kinds](../monitor-kinds.md). |
| `state` | [`MonitorState`](#schema-monitorstate) |  |
| `system_pause_reason` | `billing` \| `plan_limit` \| `org_suspended` \| `org_deleting` \| `admin` or null |  |
| `timeout_ms` | integer | Read-only execution budget derived from the spec. |
| `updated_at` | timestamp |  |
| `uptime_24h` | number or null | Uptime over the last 24 h; `null` when nothing was observed. |
| `version` | integer |  |
| `warnings` | array of string | Create and update responses only, e.g. `does_not_resolve`. |

### MonitorContactIds {#schema-monitorcontactids}

`PUT .../contacts` body and response.

| Field | Type | Description |
|---|---|---|
| `contact_ids` | array of id `con_…` | At most 100 contacts of the org; replaces the set. |

### MonitorInput {#schema-monitorinput}

`POST /orgs/{org}/monitors` body.

| Field | Type | Description |
|---|---|---|
| `contact_ids` (optional) | array of id `con_…` or null | At most 100; omitted = every `auto_attach` contact. |
| `degraded_after_ms` (optional) | integer or null | `http_adv` only; 100-30000 and below the time budget; `null` = off. |
| `failure_threshold` (optional) | integer or null | 1-10, default 2; for `domain_expiry` 1, the default (a second probe confirms each change instead); for `heartbeat` 1. |
| `interval_s` | integer | 30, 60, 120, 300, 600, 900, 1800 or 3600, at least the plan minimum. `domain_expiry`: 86400 only. `heartbeat`: the period between pings, 60-604800 (no plan minimum). |
| `kind` | `icmp` \| `http` \| `http_adv` \| `tcp` \| `ssl_expiry` \| `domain_expiry` \| `imap` \| `smtp` \| `heartbeat` | Immutable after create. |
| `labels` (optional) | array of string | At most 10, each 1-32 characters of `[a-z0-9-_.]`. |
| `locations` (optional) | [`MonitorLocationsInput`](#schema-monitorlocationsinput) or null | Required, except for `domain_expiry`, which must omit it: each of its lookups runs from one location, a different one each day; and for `heartbeat`, which runs no checks. |
| `name` | string | 1-100 characters. |
| `paused` (optional) | boolean |  |
| `recovery_threshold` (optional) | integer or null | 1-10, default 2; for `domain_expiry` and `heartbeat` 1, the default. |
| `reminder_interval_s` (optional) | integer or null | `null` = no reminders; 900-86400. |
| `spec` | [`MonitorSpecInput`](#schema-monitorspecinput) | The per-kind spec without the type tag: the `MonitorSpecInput` schema of `kind`; secret fields are write-only. Fields per kind: [Monitor kinds](../monitor-kinds.md). |

### MonitorLocationState {#schema-monitorlocationstate}

One location of a monitor's live state.

| Field | Type | Description |
|---|---|---|
| `code` | string |  |
| `error_kind` | `dns_failure` \| `blocked_address` \| `connect_timeout` \| `connection_refused` \| `connection_reset` \| `host_unreachable` \| `tls_error` \| `cert_expired` \| `timeout` \| `protocol_error` \| `status_mismatch` \| `assertion_failed` \| `too_many_redirects` \| `redirect_blocked` \| `packet_loss` \| `slow_response` \| `cert_expiring` \| `domain_expiring` \| `domain_expired` \| `domain_not_found` \| `rdap_unavailable` \| `heartbeat_missed` \| `heartbeat_failed` \| `network_unreachable` \| `icmp_unavailable` \| `resource_exhausted` \| `probe_overloaded` \| `internal` or null |  |
| `last_result_at` | timestamp or null |  |
| `latency_ms` | integer or null |  |
| `status` | `up` \| `down` \| `degraded` \| `no_data` |  |
| `substitute` | string or null | Code of the location currently standing in for this one: the location that ran the slot's newest fresh result when it was not the slot itself, or `null`. |

### MonitorLocations {#schema-monitorlocations}

The location selection of a monitor.

| Field | Type | Description |
|---|---|---|
| `codes` | array of string or null | Set when `mode = locations`. |
| `mode` | `regions` \| `locations` |  |
| `regions` | array of `eu` \| `na` \| `sa` \| `as` \| `oc` \| `af` or null | Set when `mode = regions`. |
| `resolved` | array of string | The materialised location codes, by `sort_order, code`. |

### MonitorLocationsInput {#schema-monitorlocationsinput}

Location selection of a monitor (`MonitorInput.locations`).

| Field | Type | Description |
|---|---|---|
| `codes` (optional) | array of string or null | With `mode = locations`: 2-12 location codes. |
| `mode` | `regions` \| `locations` |  |
| `regions` (optional) | array of `eu` \| `na` \| `sa` \| `as` \| `oc` \| `af` or null | With `mode = regions`: 1-6 region codes. |

### MonitorPatch {#schema-monitorpatch}

`PATCH /orgs/{org}/monitors/{mon}` body: any subset of `MonitorInput` except `kind` (JSON merge patch; `spec` is merged per RFC 7396).

| Field | Type | Description |
|---|---|---|
| `contact_ids` (optional) | array of id `con_…` or null |  |
| `degraded_after_ms` (optional) | integer or null |  |
| `failure_threshold` (optional) | integer or null |  |
| `interval_s` (optional) | integer or null |  |
| `kind` (optional) | `icmp` \| `http` \| `http_adv` \| `tcp` \| `ssl_expiry` \| `domain_expiry` \| `imap` \| `smtp` \| `heartbeat` or null | Not allowed (`422 not_allowed`): the kind is immutable. |
| `labels` (optional) | array of string or null |  |
| `locations` (optional) | [`MonitorLocationsInput`](#schema-monitorlocationsinput) or null |  |
| `name` (optional) | string or null |  |
| `paused` (optional) | boolean or null | `false` runs the same checks as `POST .../resume`. |
| `recovery_threshold` (optional) | integer or null |  |
| `reminder_interval_s` (optional) | integer or null |  |
| `spec` (optional) | object or null | Merged into the stored spec (RFC 7396): any subset of the `MonitorSpecInput` schema of the monitor's kind. Fields per kind: [Monitor kinds](../monitor-kinds.md). |

### MonitorSpec {#schema-monitorspec}

A monitor's `spec` in responses: the schema of its `kind`, with every field present (defaults applied, hosts and URLs normalised). Secrets of `http_adv` show as flags (`HttpAdvSpec`).

The schema of the monitor's `kind`:

| `kind` | Schema |
|---|---|
| `domain_expiry` | [`DomainExpirySpec`](#schema-domainexpiryspec) |
| `heartbeat` | [`HeartbeatSpec`](#schema-heartbeatspec) |
| `http` | [`HttpSpec`](#schema-httpspec) |
| `http_adv` | [`HttpAdvSpec`](#schema-httpadvspec) |
| `icmp` | [`IcmpSpec`](#schema-icmpspec) |
| `imap` | [`MailSpec`](#schema-mailspec) |
| `smtp` | [`MailSpec`](#schema-mailspec) |
| `ssl_expiry` | [`SslExpirySpec`](#schema-sslexpiryspec) |
| `tcp` | [`TcpSpec`](#schema-tcpspec) |

### MonitorSpecInput {#schema-monitorspecinput}

A monitor's `spec` in requests: the schema of its `kind` (`imap` and `smtp` share `MailSpec`), without a type tag. Secret values of `http_adv` are sent inline (`HttpAdvSpecInput`).

The schema of the monitor's `kind`:

| `kind` | Schema |
|---|---|
| `domain_expiry` | [`DomainExpirySpec`](#schema-domainexpiryspec) |
| `heartbeat` | [`HeartbeatSpec`](#schema-heartbeatspec) |
| `http` | [`HttpSpec`](#schema-httpspec) |
| `http_adv` | [`HttpAdvSpecInput`](#schema-httpadvspecinput) |
| `icmp` | [`IcmpSpec`](#schema-icmpspec) |
| `imap` | [`MailSpec`](#schema-mailspec) |
| `smtp` | [`MailSpec`](#schema-mailspec) |
| `ssl_expiry` | [`SslExpirySpec`](#schema-sslexpiryspec) |
| `tcp` | [`TcpSpec`](#schema-tcpspec) |

### MonitorState {#schema-monitorstate}

A monitor's live state.

| Field | Type | Description |
|---|---|---|
| `last_ping_at` | timestamp or null | `heartbeat` only: when the newest ping arrived. |
| `last_result_at` | timestamp or null |  |
| `locations` | array of [`MonitorLocationState`](#schema-monitorlocationstate) | One per configured location; empty for `domain_expiry`, whose lookups run from a different location each day (the newest result says where), and for `heartbeat`. |
| `open_incident_id` | id `inc_…` or null |  |
| `status` | `unknown` \| `up` \| `down` \| `degraded` \| `no_data` \| `paused` |  |
| `status_since` | timestamp |  |

### Notification {#schema-notification}

One `notification_jobs` row.

| Field | Type | Description |
|---|---|---|
| `attempts` | integer |  |
| `channel` | `email` \| `sms` \| `pushover` \| `discord` \| `pagerduty` \| `slack` \| `webhook` \| `voice` \| `teams` |  |
| `contact` | [`ContactRef`](#schema-contactref) or null | `null` for account notices sent to a user. |
| `created_at` | timestamp |  |
| `deliveries` | array of [`DeliveryView`](#schema-deliveryview) |  |
| `finished_at` | timestamp or null |  |
| `id` | id `ntf_…` |  |
| `incident_id` | id `inc_…` or null |  |
| `kind` | `down` \| `recovered` \| `degraded` \| `flapping` \| `reminder` \| `test` \| `fallback` \| `account_notice` \| `pushover_cancel` |  |
| `last_error` | string or null |  |
| `monitor_id` | id `mon_…` or null |  |
| `next_attempt_at` | timestamp or null |  |
| `status` | `pending` \| `sending` \| `sent` \| `failed` \| `cancelled` \| `suppressed` \| `held` |  |
| `status_reason` | `sms_cap` \| `anomaly_brake` \| `notify_hold` \| `contact_disabled` \| `monitor_deleted` \| `monitor_paused` \| `permanent_failure` \| `gave_up` \| `maintenance` or null |  |

### Org {#schema-org}

An organisation as its member sees it.

| Field | Type | Description |
|---|---|---|
| `billing` | [`OrgBilling`](#schema-orgbilling) |  |
| `created_at` | timestamp |  |
| `id` | id `org_…` |  |
| `my_role` | `owner` \| `admin` \| `member` \| `viewer` |  |
| `name` | string |  |
| `purge_after` | timestamp or null | Set while deleting or canceled. |
| `status` | `active` \| `suspended` \| `deleting` |  |
| `timezone` | string | IANA name. |

### OrgBilling {#schema-orgbilling}

`{plan, status}`; `status` is effective (a lapsed trial or grace period counts as `canceled`).

| Field | Type | Description |
|---|---|---|
| `plan` | `trial` \| `starter` \| `pro` \| `business` |  |
| `status` | `trialing` \| `active` \| `past_due` \| `canceled` |  |

### OrgEntitlements {#schema-orgentitlements}

The org's effective entitlements and current usage (`GET /orgs/{org}/entitlements`): what forms and limit messages need, in either billing mode.

| Field | Type | Description |
|---|---|---|
| `entitlements` | [`Entitlements`](#schema-entitlements) |  |
| `usage` | [`BillingUsage`](#schema-billingusage) |  |

### OrgPatch {#schema-orgpatch}

`PATCH /orgs/{org}` (merge patch).

| Field | Type | Description |
|---|---|---|
| `name` (optional) | string or null | 1-100 characters. |
| `timezone` (optional) | string or null | IANA timezone. |

### Overview {#schema-overview}

The org dashboard.

| Field | Type | Description |
|---|---|---|
| `monitors` | [`OverviewMonitors`](#schema-overviewmonitors) |  |
| `open_incidents` | array of [`Incident`](#schema-incident) | Newest first, at most 20. |
| `recent_changes` | array of [`OverviewChange`](#schema-overviewchange) | The last 20 status periods started. |
| `usage` | [`OverviewUsage`](#schema-overviewusage) |  |

### OverviewChange {#schema-overviewchange}

One status change.

| Field | Type | Description |
|---|---|---|
| `at` | timestamp |  |
| `from` | `unknown` \| `up` \| `down` \| `degraded` \| `no_data` \| `paused` or null | `null` for a monitor's first status. |
| `incident_id` | id `inc_…` or null |  |
| `monitor` | [`IncidentMonitor`](#schema-incidentmonitor) |  |
| `to` | `unknown` \| `up` \| `down` \| `degraded` \| `no_data` \| `paused` |  |

### OverviewMonitors {#schema-overviewmonitors}

Monitors per status; `paused` counts every monitor not running.

| Field | Type | Description |
|---|---|---|
| `degraded` | integer |  |
| `down` | integer |  |
| `no_data` | integer |  |
| `paused` | integer |  |
| `total` | integer |  |
| `unknown` | integer |  |
| `up` | integer |  |

### OverviewUsage {#schema-overviewusage}

Plan usage.

| Field | Type | Description |
|---|---|---|
| `monitors_limit` | integer |  |
| `monitors_used` | integer |  |
| `sms_credits_limit` | integer |  |
| `sms_credits_used` | integer |  |
| `sms_period_end` | timestamp | End of the current SMS period (next UTC month start). |

### PathTrace {#schema-pathtrace}

A path trace.

| Field | Type | Description |
|---|---|---|
| `completed_at` | timestamp or null |  |
| `created_at` | timestamp |  |
| `error_detail` | string or null |  |
| `error_kind` | string or null | `failed` only: `no_probe`, `expired`, `lost`, `invalid`, or the probe's error kind (`dns_failure`, `blocked_address`, ...). |
| `hops` | array of [`TraceHop`](#schema-tracehop) | By TTL from 1 (`done` only). Responders in private, shared and reserved ranges have no `ip`. |
| `id` | id `trc_…` |  |
| `incident_id` | id `inc_…` or null | The incident it was run for (`trigger = incident`). |
| `location` | [`TraceLocation`](#schema-tracelocation) | Where it ran from. |
| `mode` | `traceroute` \| `mtr` | `traceroute`: 3 probes per hop; `mtr`: 10. |
| `monitor_id` | id `mon_…` |  |
| `port` | integer or null | The TCP port, for `tcp`. |
| `protocol` | `icmp` \| `tcp` |  |
| `reached` | boolean or null | Whether the target answered (`done` only). |
| `requested_by` | [`UserRef`](#schema-userref) or null | Who asked for it (`trigger = manual`; `null` once the user is gone). |
| `status` | `pending` \| `done` \| `failed` |  |
| `target` | string | The monitor's target host. |
| `target_ip` | string or null | The address traced to. |
| `trigger` | `incident` \| `manual` |  |

### PathTraceInput {#schema-pathtraceinput}

`POST .../traces` body.

| Field | Type | Description |
|---|---|---|
| `location` | string | Location code to trace from (any active location). |
| `mode` (optional) | `traceroute` \| `mtr` or null | Default `mtr`. |
| `protocol` (optional) | `icmp` \| `tcp` or null | Default: `tcp` to the target's port when it has one (falling back to `icmp` when no probe there can trace over TCP), else `icmp`. |

### PreviewCheckInput {#schema-previewcheckinput}

`POST .../preview-check` body.

| Field | Type | Description |
|---|---|---|
| `kind` | `icmp` \| `http` \| `http_adv` \| `tcp` \| `ssl_expiry` \| `domain_expiry` \| `imap` \| `smtp` \| `heartbeat` |  |
| `location` | string | Location code. |
| `spec` | [`MonitorSpecInput`](#schema-monitorspecinput) | The spec as in `MonitorInput` (secrets inline). Fields per kind: [Monitor kinds](../monitor-kinds.md). |

### RegionView {#schema-regionview}

A region with at least one active location.

| Field | Type | Description |
|---|---|---|
| `code` | `eu` \| `na` \| `sa` \| `as` \| `oc` \| `af` |  |
| `name` | string |  |

### RoleRequest {#schema-rolerequest}

`{role}`.

| Field | Type | Description |
|---|---|---|
| `role` | `owner` \| `admin` \| `member` \| `viewer` |  |

### Rollup {#schema-rollup}

One rollup bucket.

| Field | Type | Description |
|---|---|---|
| `bucket_start` | timestamp |  |
| `checks_degraded` | integer |  |
| `checks_down` | integer |  |
| `checks_total` | integer |  |
| `checks_up` | integer |  |
| `latency_avg_ms` | number or null |  |
| `latency_max_ms` | integer or null |  |
| `latency_min_ms` | integer or null |  |
| `latency_p50_ms` | integer or null |  |
| `latency_p95_ms` | integer or null |  |
| `latency_p99_ms` | integer or null |  |
| `location` | string |  |

### RollupsResponse {#schema-rollupsresponse}

`GET .../rollups` response.

| Field | Type | Description |
|---|---|---|
| `data` | array of [`Rollup`](#schema-rollup) |  |
| `granularity` | `hour` \| `day` |  |

### Series {#schema-series}

The response-time chart.

| Field | Type | Description |
|---|---|---|
| `bands` | array of [`SeriesBand`](#schema-seriesband) |  |
| `from` | timestamp |  |
| `range` | string |  |
| `series` | array of [`SeriesLine`](#schema-seriesline) |  |
| `step_s` | integer |  |
| `to` | timestamp |  |

### SeriesBand {#schema-seriesband}

A non-`up` status period inside the range.

| Field | Type | Description |
|---|---|---|
| `from` | timestamp |  |
| `status` | `unknown` \| `up` \| `down` \| `degraded` \| `no_data` \| `paused` |  |
| `to` | timestamp |  |

### SeriesLine {#schema-seriesline}

One location's latency points.

| Field | Type | Description |
|---|---|---|
| `location` | string |  |
| `points` | array of array of integer or null | `[unix_s, latency_ms \| null]` for every step. |

### SslExpirySpec {#schema-sslexpiryspec}

`ssl_expiry`.

| Field | Type | Description |
|---|---|---|
| `cert_expiry_warn_days` | integer | Degraded this many days before expiry, 1-90 (default 14). |
| `host` | string | Host name or public IP address. |
| `ip_family` | `auto` \| `ipv4` \| `ipv6` |  |
| `port` | integer | 1-65535 (default 443). |
| `timeout_ms` | integer | 1000-30000 (default 10000). |
| `verify_tls` | boolean | Default `true`. |

### TcpSpec {#schema-tcpspec}

`tcp`.

| Field | Type | Description |
|---|---|---|
| `host` | string | Host name or public IP address. |
| `ip_family` | `auto` \| `ipv4` \| `ipv6` |  |
| `port` | integer | 1-65535. |
| `timeout_ms` | integer | 500-30000 (default 5000). |

### TestSent {#schema-testsent}

`POST .../test` response.

| Field | Type | Description |
|---|---|---|
| `notification_id` | id `ntf_…` |  |

### TokenInfo {#schema-tokeninfo}

`GET /token`: the calling token, its org and effective role.

| Field | Type | Description |
|---|---|---|
| `effective_role` | `admin` \| `member` \| `viewer` | `min(token role, creator's current role)`: what the token may do. |
| `org` | [`TokenOrg`](#schema-tokenorg) |  |
| `token` | [`ApiToken`](#schema-apitoken) |  |

### TokenOrg {#schema-tokenorg}

`{ "id": "org_…", "name": "…" }`.

| Field | Type | Description |
|---|---|---|
| `id` | id `org_…` |  |
| `name` | string |  |

### TraceHop {#schema-tracehop}

One TTL of a trace.

| Field | Type | Description |
|---|---|---|
| `addrs` | array of [`TraceHopAddr`](#schema-tracehopaddr) | Distinct responders, most replies first (at most 4); several mean load-balanced paths. |
| `received` | integer |  |
| `rtt_avg_us` | integer or null |  |
| `rtt_last_us` | integer or null |  |
| `rtt_max_us` | integer or null |  |
| `rtt_min_us` | integer or null |  |
| `rtt_stdev_us` | integer or null |  |
| `sent` | integer |  |
| `ttl` | integer |  |
| `unreachable` | `net` \| `host` \| `protocol` \| `port` \| `prohibited` \| `other` or null |  |

### TraceHopAddr {#schema-tracehopaddr}

One responder of a hop.

| Field | Type | Description |
|---|---|---|
| `ip` | string or null | `null` when the server withheld it: private, shared and reserved ranges (the probe host's own network) are never shown. |
| `name` | string or null | Reverse DNS name, when there is one. |
| `replies` | integer | Replies from this address. |

### TraceLocation {#schema-tracelocation}

`{ "code": "lon1", "name": "London" }`.

| Field | Type | Description |
|---|---|---|
| `code` | string |  |
| `id` | id `loc_…` |  |
| `name` | string |  |

### UpdateMaintenanceRequest {#schema-updatemaintenancerequest}

`PATCH /orgs/{org}/maintenance/{mnt}` body: rename, or move the end of a window that has not ended (to end it now, use `…/end`). The start can only change before the window starts.

| Field | Type | Description |
|---|---|---|
| `ends_at` (optional) | timestamp or null |  |
| `name` (optional) | string or null |  |
| `starts_at` (optional) | timestamp or null |  |

### UserRef {#schema-userref}

`{ "id": "usr_…", "name": "…" }`.

| Field | Type | Description |
|---|---|---|
| `id` | id `usr_…` |  |
| `name` | string |  |

### VerifyInput {#schema-verifyinput}

`POST .../verify` body.

| Field | Type | Description |
|---|---|---|
| `code` | string | The 6-digit code. |

