Skip to content

Assertion language reference

View as Markdown

The assertion language lets a Website (HTTP) or API monitor check more than “did the server answer 2xx”: the status, headers, body text, JSON/XML/HTML/YAML content, timings, redirects, DNS answers and TLS details. Each rule is one line; all rules must pass for the check to pass, and a failing check names every rule that failed. This page is the complete reference. For building rules in the editor, see Assertions and validation rules; for ready-made examples, see the recipes below.

Monitor type Status
Website / HTTP (http) Supported.
API (api) Supported (same subjects as HTTP).
SNMP (snmp) Rules can be saved against the SNMP subjects (value, value.type); evaluation is being rolled out.
Database, Counter Use their own condition fields in the monitor editor; the same engine evaluates them.
Ping, Port The language defines their subjects (below); rules cannot be saved on them yet.
[label:] subject [not] predicate [value] [nocase] # comment
  • One line = one rule. All rules are ANDed; there is no and, or or parentheses.
  • Label (optional): homepage-ok: status eq 200 - the label is what a failure names.
  • not negates: body not contains "Error". != is the same as not eq.
  • nocase makes a text comparison case-insensitive: body contains "welcome" nocase. Text comparisons are case-sensitive by default.
  • A subject alone means exists: body.json saves as body.json exists.
  • # starts a comment. Identifiers are case-sensitive.
Literal Example Notes
Number 200, 1.5 Comparing to a number parses the subject as a number; a value that does not parse fails the rule with a clear message.
String "OK" Always double-quoted. "true" and "null" are strings.
Boolean true, false Strict: matches a real JSON boolean only; equality only (eq, ne). "true" (quoted) matches the text.
Null null eq null tests for a JSON null; exists tests presence.
Size 1kb = 1024, 10kb, 1mb = 1048576 Converted to plain numbers when saved.
Duration 500ms, 2s = 2000, 1m, 1h Milliseconds; converted when saved.
Range (in lists) [200..299], [..299], [500..] Ends are inclusive.
Document json("{\"ok\":true}"), xml("..."), yaml("...") Structural comparison with eq (exact) or contains (subset); checked when saved.

The literal’s type picks the comparison: eq 200, eq "200", eq true and eq "true" are four different rules.

A subject reads a part of the check’s result. Unqualified subjects refer to the final response (after redirects).

Subject Type What it reads
status number HTTP status of the final response.
body text The response body as text.
body.size number (bytes) Body size.
body.hash text A hash of the body - compare with previous.body.hash to detect changes.
body.regex("pattern") text Regex over the body; named groups (?<name>...) can be read with bind("name").
body.json, body.xml, body.yaml, body.html text The body, only if it really is that format - otherwise not present. body.json exists = “is this JSON?”.
body.json.path("$.a.b") runtime-typed JSONPath (RFC 9535) into a JSON body.
body.yaml.path("$.a") runtime-typed JSONPath into a YAML body.
body.xml.path("//a") runtime-typed XPath into an XML body.
body.html.path("nav a") collection CSS selector into an HTML body; each element’s text by default.
body.html.a, body.html.script, body.html.link collection Links, scripts, link tags; elements have url (default, as written), absolute (resolved URL), text, rel.
body.<format>.regex("...") text Regex over the body, only when it is that format.
header("Name") text / set A response header (all values of a multi-valued header).
setCookie collection The Set-Cookie values.
hstsHeader text The Strict-Transport-Security header.
url url The final URL; url.host, url.port, url.path, url.query, url.scheme.
url.original url The requested URL (before redirects), with the same parts.
time number (ms) Total response time: connect + TLS + head + data (DNS excluded).
time.connect, time.tls, time.head, time.data, time.dns number (ms) The phases. time.dns is not part of time.
dns.ips collection The addresses the host resolved to.
dns.server text The DNS server that answered.
conn.ip, conn.ip.family text The address connected to, and its family.
conn.failed, conn.failedIps number / collection Addresses that failed before one succeeded.
tls.protocol, tls.cipher text The negotiated TLS version and cipher.
cert.days, cert.days.left number Certificate validity: total days and days left.
cert.issuer, cert.subject, cert.serial text Certificate fields.
cert.san collection Subject Alternative Names.
requests collection Every request the check made, in order, including the final one. Elements have status, url, header("..."), setCookie.
redirects collection Only the followed hops (requests minus the final one).
redirectCount number Number of redirects followed (0 = answered directly).
bind("name"), bind[0] text A value captured by a named regex group earlier in the list.

dns, conn, tls and cert are groups - a rule must name a field inside them (dns.ips, not dns).

Type Subjects
Port (TCP) banner (+ banner.size, banner.hash, banner.regex(...)), time (connect + TLS + read) with time.connect, time.tls, time.dns, ip, dns.ips, dns.server, conn.failed, and with TLS on: tls.protocol, tls.cipher, cert.*
Ping loss (packet loss %), replyCount, sentCount, time (average RTT), time.min, time.max, ttl, ip, dns.ips, dns.server, time.dns; reply collection with time, ttl, ip per reply
Database scalar (the single value), rows (collection), rowCount, time.query
Counter value
SNMP value, value.type
Predicate Example Meaning
eq / ==, ne / != status eq 200 Equal / not equal.
lt / <, le / <=, gt / >, ge / >= time lt 2s Numeric comparison.
contains body contains "Add to cart" Text contains the value; over a collection, an element equals it; with a document literal, structural containment.
startsWith, endsWith url.path startsWith "/api" Text begins / ends with.
matches body matches "Order #[0-9]{6}" Regular expression, searched anywhere (dot matches newlines; 4 s timeout; pattern up to 1 KB).
containsAny, containsAll body containsAny ["In stock", "Pre-order"] Any / all of the listed strings.
in status in [200, 301, 302], status in [200..299] The value is in the list or range; over a collection, every element is (subset).
exists body.json exists The subject is present (for a format namespace: the body is that format).
isNumber bind("id") isNumber The value parses as a number.
unique dns.ips unique A collection has no duplicates. (x.unique, with a dot, is the de-duplicating reducer.)

nocase matters only for textual comparisons (contains, startsWith, endsWith, matches, containsAny, containsAll, and eq/in with a string); elsewhere it is ignored with a warning.

Shortcuts are expanded when a rule is saved; the editor shows what you typed.

Shortcut Means
status isOk status in [200..299]
status isRedirect status in [300..399]
status isClientError status in [400..499]
status isServerError status in [500..599]
body isJson, body isXml, body isHtml The body is that format (body.json exists …).
header("Content-Type") isJson (also isXml, isHtml, isForm) The header names that media type family (including +json/+xml suffixes, ignoring parameters, case-insensitive).
x isEmpty Empty (for a format namespace: not that format).
x isNull x eq null
x absent x not exists
x between 10 and 20 x in [10..20]
bodySize, respTime body.size, time

Collections (requests, redirects, dns.ips, cert.san, setCookie, body.html.a, query results, reply, rows) compose four ways:

Form Result
requests[0] One element (0-based index).
requests.status A projection - the status of every element (still a collection).
requests.where(current.url contains "cart") A filter - the elements matching the condition (still a collection). Name the element with current.; no nesting, one condition.
requests.count, .min, .max, .sum, .avg, .first, .last, .unique A reducer (always last).

Rules for collections:

  • Text predicates are existential: requests.url matches "/login" passes if any element matches. contains over a collection is exact membership: dns.ips contains "203.0.113.10" does not match 203.0.113.100.
  • not means none match.
  • A plain comparison of a whole collection to one value is refused because it is ambiguous. Say which you mean: .first eq 5 (the first), in [5] (every element), or .count (how many).
  • “For all”: x.where(condition).count eq x.count. “There exists”: x.where(condition) exists.
  • On an empty collection .count and .sum are 0; .min, .max, .avg, .first, .last are null (so exists is false).

Comparing with the previous check (plan feature)

Section titled “Comparing with the previous check (plan feature)”
Form Meaning
previous.x The value of x in the previous check - body.hash ne previous.body.hash fails when the page changes.
delta(x) The absolute change of x since the previous check.
delta(x, s) (also ms, m, h) The rate of change per unit of time.
delta2(x) The change of the change (second derivative).

These need a previous value: the first check (two for delta2) is skipped as “warming” and counts as a pass. A current value that cannot be read still fails at once. The previous-value family is a plan feature; rules using it are refused on plans without it.

Rules can also compare two subjects: url.host eq url.original.host, dns.ips eq previous.dns.ips. Comparing a subject with itself is refused.

Monitor setting API field Meaning
Assertion rules settings.asserts (rows) or settings.assertsSource (text) Up to 20 rules; your plan may allow fewer.
Use assertions as the verdict settings.assertMode (default false) When on, the rules alone decide whether the response is up; the classic keyword and status fields are refused. Connection, DNS, TLS, timeout and redirect-loop failures still fail the check. When off, rules are checked in addition to the normal verdict.

Send rules as text with assertsSource - one rule per line, shortcuts allowed - and they are parsed, validated and stored:

{ "settings": { "assertMode": true,
"assertsSource": "status isOk\nbody.json.path(\"$.status\") eq \"ok\"\ntime lt 2s" } }

A rule that does not compile is refused with the line named. assertsText in responses shows the stored rules as text. A failing check records the error type AssertError (codename AssertFailed) and lists each failed rule.

  • Rules per monitor: at most 20; your plan’s own cap may be lower (403 package_limit when exceeded).
  • Label up to 128 characters; text values up to 2,048; document literals up to 16,384; lists up to 64 items; regex up to 1 KB.
  • When the body is larger than the monitor’s maximum response size, a rule that passes by finding something is trusted, while one that depends on something being absent is reported as inconclusive.
Rule Catches
status isOk Any non-2xx final response.
status in [200, 301, 302] A URL that may redirect but must never error.
body contains "Add to cart" A 200 page missing its key content.
body.size gt 1kb A blank or truncated page that still returns 200.
header("Content-Type") isJson An API that stops returning JSON.
body.json.path("$.status") eq "ok" A health endpoint reporting a problem.
body.json.path("$.items").count gt 0 An API returning an empty list.
time lt 2s A successful but slow response.
redirectCount le 2 A redirect chain that grew.
url.host eq url.original.host A redirect to a different domain.
cert.days.left gt 14 A certificate close to expiry (on an HTTPS check).
dns.ips eq previous.dns.ips DNS answers that changed (plan feature).
body.hash eq previous.body.hash A page whose content changed (plan feature).