Monitor kinds | JustMonitorIt API
On this page

Monitor kinds

A monitor is created with POST /orgs/{org}/monitors and a body like this:

{
  "name": "Homepage",
  "kind": "http",
  "spec": { "url": "https://www.example.com/" },
  "interval_s": 60,
  "locations": { "mode": "regions", "regions": ["eu", "na"] },
  "failure_threshold": 2,
  "recovery_threshold": 2,
  "reminder_interval_s": 3600,
  "labels": ["prod"],
  "contact_ids": ["con_01j9..."],
  "paused": false
}

The common fields:

FieldRequiredRules
nameyes1-100 characters
kindyesone of the nine kinds below; cannot change later
specyesthe kind's fields (below); unknown fields are rejected
interval_syes30, 60, 120, 300, 600, 900, 1800 or 3600, and at least your plan's minimum (below_plan_minimum); domain_expiry must use 86400; for heartbeat, the period between pings, 60-604800 (no plan minimum)
locationsyes, except domain_expiry and heartbeat, which must leave it out{"mode": "regions", "regions": [...]} (1-6 of eu, na, sa, as, oc, af) or {"mode": "locations", "codes": [...]} (2-12 codes from GET /locations). Regions expand to their active locations, capped at your plan's maximum per monitor; more codes than the maximum are refused (too_many_locations). At least 2 locations after expansion (too_few_locations). Exactly 2 is accepted with the warning two_locations: with two, an outage seen from one location can never alert. Three or more is better
failure_thresholdno1-10, default 2: consecutive failed checks in a location before it counts as down
recovery_thresholdno1-10, default 2: consecutive good checks before it counts as up again
degraded_after_msnohttp_adv only: 100-30000 and below the check's time budget; a slower response makes the monitor degraded. null (default) is off
reminder_interval_sno900-86400: repeat the down notification while an incident stays open and unacknowledged; null (default) = no reminders
labelsnoat most 10, each 1-32 characters of a-z 0-9 - _ .
contact_idsnocontacts to notify (at most 100); omitted = every contact marked auto_attach
pausednotrue creates the monitor paused

Time budget. Each check has a time budget, computed from its spec and returned as timeout_ms on the monitor. It must be at most interval_s × 1000 − 10000 ms, so a 30-second monitor allows at most a 20-second budget (out_of_range on the spec's timeout_ms otherwise).

Hosts and URLs. A host is a hostname or an IP address. Hostnames are stored in their ASCII (punycode) form, lower case. A hostname needs at least two labels and may not end in localhost, local, internal, lan, home.arpa, localdomain, test, example, invalid or onion. When you save, the host is resolved: if every address is private, loopback, link-local or otherwise reserved, the monitor is refused (target_not_public); if it does not resolve at all it is accepted with the warning does_not_resolve. Across your organisation, the checks of all monitors with the same host may add up to at most 120 a minute (host_rate_exceeded).

ip_family (every kind except domain_expiry): auto (default) uses IPv6 and IPv4 as available (Happy Eyeballs; ICMP uses the first IPv4 address, or IPv6 when there is none), ipv4 or ipv6 only that family. ipv6 monitors can only use locations with IPv6 (ipv6: true in GET /locations).

Outcomes. Every check gives up, down, degraded or error. error means our probe could not run the check (it says nothing about your target) and never alerts. degraded makes a monitor degraded only for http_adv, ssl_expiry, domain_expiry, imap and smtp; for the other kinds it counts as up. A failing check gives an error_kind, listed per kind below.

Defaults are stored with the monitor, so a later change of a default never changes your existing monitors. Responses return the spec with every default filled in.

http

A GET request. Up when the final status is 200-399 and, if keyword is set, the keyword appears in the first 64 KiB of the body. Up to 5 redirects are followed; TLS certificates are always verified.

FieldRequiredDefaultRules
urlyeshttp or https, at most 2048 bytes, no user:pass@, port 1-65535; a #fragment is dropped
keywordnonull1-128 bytes, case-sensitive
timeout_msno100001000-30000
ip_familynoautoauto, ipv4, ipv6
{ "kind": "http", "spec": { "url": "https://www.example.com/health", "keyword": "ok" } }

Failures: dns_failure, blocked_address, connect_timeout, connection_refused, connection_reset, host_unreachable, tls_error, cert_expired, timeout, protocol_error, status_mismatch, assertion_failed (keyword missing), too_many_redirects, redirect_blocked.

http_adv

Any method, headers, body, authentication, expected statuses and assertions on the response.

FieldRequiredDefaultRules
urlyesas for http
methodnoGETGET, HEAD, POST, PUT, PATCH, DELETE, OPTIONS
headersno[]at most 32, 8 KiB together; each {"name", "value", "secret"} (below)
bodynonullat most 16 KiB; not allowed with GET or HEAD. Without a Content-Type header it is sent as application/json if it parses as JSON, else text/plain; charset=utf-8
authno{"type": "none"}{"type": "basic", "username", "password"} or {"type": "bearer", "token"}
expected_statusno["200-299"]1-16 entries: "200", "200-299" or "2xx", codes 100-599
assertionsno[]at most 20 (below); all must pass
follow_redirectsnotrue
max_redirectsno50-10
verify_tlsnotruefalse accepts any certificate chain
cert_expiry_warn_daysno141-90: degraded (cert_expiring) when the certificate expires sooner; null turns the warning off
timeout_msno100001000-30000
body_max_bytesno655361024-262144: how much of the body is read for assertions
ip_familynoauto

Headers. name is an HTTP token of at most 128 bytes; value at most 4096 bytes without CR, LF or NUL. Names must be unique, except Accept, Cache-Control and X- headers. You may not set Host, Content-Length, Transfer-Encoding, Connection, Keep-Alive, Upgrade, TE, Trailer, Proxy-Authorization, Proxy-Connection, or Authorization while auth is not none. Without your own User-Agent the probe sends JustMonitorIt-Probe/<version> (+https://justmonitorit.com/probe; loc=<location>).

Secrets. auth.password, auth.token and the value of a header with "secret": true are write-only. Responses show "password_set": true, "token_set": true and "secret": true, "value": null instead. When you update a monitor, omitting a secret or sending null keeps the stored one; "" clears it. A secret header's name must be unique.

Assertions:

AssertionPasses when
{"type": "body_contains", "value": "..."}the value (1-256 bytes, case-sensitive) is in the body
{"type": "body_not_contains", "value": "..."}the value is not in the body (only the part read, see body_max_bytes)
{"type": "body_regex", "pattern": "..."}the regular expression (at most 512 characters, Rust regex syntax) matches the body
{"type": "json_path", "path": "$.status", "op": "equals", "value": "ok"}the body is JSON and the value at path satisfies op
{"type": "header", "name": "content-type", "op": "contains", "value": "json"}the response header satisfies op

json_path paths use $, .name, ['name'] and [index] only (no wildcards or filters), at most 256 characters. Its op is exists, not_exists, equals, not_equals, contains (substring of a string, or an element of an array), lt or gt (numbers). exists and not_exists take no value; the others need one; equality is typed ("1" is not 1). header ops are exists, not_exists, equals, contains and regex; the name is case-insensitive, the value comparison case-sensitive.

Slow responses: set the monitor's degraded_after_ms to make it degraded (slow_response) when a response takes longer.

{
  "kind": "http_adv",
  "spec": {
    "url": "https://api.example.com/v1/health",
    "method": "GET",
    "headers": [ { "name": "X-Api-Key", "value": "s3cret", "secret": true } ],
    "expected_status": ["200"],
    "assertions": [
      { "type": "json_path", "path": "$.status", "op": "equals", "value": "ok" },
      { "type": "header", "name": "content-type", "op": "contains", "value": "application/json" }
    ]
  },
  "degraded_after_ms": 1500
}

Failures: as http, plus cert_expiring (degraded) and slow_response (degraded).

tcp

Up when a TCP connection to the port opens. Nothing is sent.

FieldRequiredDefaultRules
hostyeshostname or IP address
portyes1-65535
timeout_msno5000500-30000
ip_familynoauto
{ "kind": "tcp", "spec": { "host": "db.example.com", "port": 5432 } }

Failures: dns_failure, blocked_address, connect_timeout, connection_refused, host_unreachable.

icmp

Ping. Sends count echo requests; down when the share lost reaches loss_threshold_pct (the default 100 means down only when every echo is lost).

FieldRequiredDefaultRules
hostyeshostname or IP address
countno31-10
interval_msno500200-1000, between echo requests
timeout_msno2000500-10000, wait after the last request
payload_bytesno5616-1024
loss_threshold_pctno1001-100
ip_familynoauto

The time budget is 3000 + (count − 1) × interval_ms + timeout_ms.

{ "kind": "icmp", "spec": { "host": "203.0.113.10", "count": 5, "loss_threshold_pct": 60 } }

Failures: packet_loss, host_unreachable, dns_failure, blocked_address.

ssl_expiry

Connects, completes a TLS handshake and checks the certificate. Down if the handshake fails or the certificate has expired; degraded (cert_expiring) when it expires within cert_expiry_warn_days. Use it for TLS services that are not websites, or to watch a certificate on its own.

FieldRequiredDefaultRules
hostyeshostname (also sent as SNI) or IP address
portno4431-65535
cert_expiry_warn_daysno141-90
verify_tlsnotruefalse checks only the expiry of whatever certificate is served
timeout_msno100001000-30000
ip_familynoauto
{ "kind": "ssl_expiry", "spec": { "host": "www.example.com", "cert_expiry_warn_days": 21 } }

Failures: transport failures as tcp, tls_error, cert_expired, timeout, protocol_error; cert_expiring (degraded).

domain_expiry

Looks up the domain's registration at its registry over RDAP, once a day (interval_s must be 86400). Down when the registration has expired (domain_expired) or the domain is not registered (domain_not_found); degraded (domain_expiring) within expiry_warn_days. If the registry cannot be reached, the result is error (rdap_unavailable) and you are not alerted.

FieldRequiredDefaultRules
domainyesthe registered domain (example.com), not an IP address; its TLD must have an RDAP server (unsupported_tld otherwise, for example ch and de)
expiry_warn_daysno301-365
timeout_msno100001000-30000
{ "kind": "domain_expiry", "interval_s": 86400, "spec": { "domain": "example.com" } }

imap and smtp

Opens a session with a mail server, without logging in or sending mail: the greeting, EHLO (SMTP) or CAPABILITY (IMAP), optionally STARTTLS, then a polite goodbye. With TLS, the certificate is checked as for ssl_expiry.

FieldRequiredDefaultRules
hostyes
portyes1-65535 (IMAP usually 993 or 143; SMTP 25, 465 or 587)
tlsyesimplicit (TLS from the start), starttls or none
verify_tlsnotrueforced to true with tls: none
cert_expiry_warn_daysno141-90, or null for no warning; forced to null with tls: none
timeout_msno100001000-30000
ip_familynoauto
{ "kind": "smtp", "spec": { "host": "mx.example.com", "port": 587, "tls": "starttls" } }

SMTP on ports 25 and 465 runs only from probes whose provider allows outbound mail ports; where there is none, another location stands in.

Failures: transport and TLS failures as ssl_expiry, timeout, protocol_error (detail greeting, ehlo, starttls_unsupported, starttls_refused, capability or line_too_long); cert_expiring (degraded).

heartbeat

Checks nothing itself: your job (a cron script, a backup, a queue worker) requests the monitor's ping URL each time it runs, and the monitor goes down (heartbeat_missed) when no ping arrives within interval_s plus grace_s. The next ping brings it back up. A new heartbeat does not alert until its first ping. interval_s is the period between pings (60-604800); leave out locations, and the thresholds are 1.

FieldRequiredDefaultRules
grace_sno30060-604800: how late a ping may be
{ "kind": "heartbeat", "interval_s": 86400, "spec": { "grace_s": 1800 } }

The response's ping_url is the URL to request, with GET, HEAD or POST (a body is ignored), for example at the end of a crontab line:

0 3 * * * /usr/local/bin/backup.sh && curl -fsS -m 10 --retry 5 -o /dev/null https://dash.justmonitorit.com/ping/...

To report a failed run, request the same URL followed by /fail: the monitor goes down at once (heartbeat_failed), and the next successful ping brings it back up:

0 3 * * * if /usr/local/bin/backup.sh; then curl -fsS -m 10 --retry 5 -o /dev/null https://dash.justmonitorit.com/ping/...; else curl -fsS -m 10 --retry 5 -o /dev/null https://dash.justmonitorit.com/ping/.../fail; fi

GET /orgs/{org}/monitors/{mon}/pings lists the newest pings (100 are kept): when each arrived, success or fail, the method, the client's address and user agent.

Only members and above see ping_url; a viewer gets null. It answers 200 once the ping counted, 404 for an unknown URL, and 429 or 503 (with Retry-After) when it should be tried again. At most 60 pings a minute per URL count. The newest ping is the monitor's state.last_ping_at. Anyone with the URL can ping the monitor, so keep it private; to change it, delete the monitor and create a new one.

Trying a spec first

POST /orgs/{org}/monitors/preview-check runs a spec once from one location and returns the result without creating anything:

{ "kind": "http", "spec": { "url": "https://www.example.com/" }, "location": "lon1" }

It applies the same validation as creating a monitor. A heartbeat has nothing to try (not_allowed).