Services
The real shape of an endpoint's call and assertions, including the few leaves the reference documents as opaque objects.
View as MarkdownPOST/PATCH /v1/services accept full endpoint definitions - most of it is typed exactly in the
API reference (see EndpointInput, MonitoringCallInput, and
MonitoringAssertionDef). A handful of deeply-nested leaves fall back to opaque objects in the
generated schema because they can’t be expressed as fixed OpenAPI types: this page is their real
shape.
The call
Today only Http is supported (GRPC/DNS are accepted and rejected with a clear error, not
silently ignored):
{
"url": "https://example.com/health",
"name": "Health check",
"call": {
"Http": {
"method": "GET",
"body": null,
"headers": { "Authorization": { "value": "Bearer secret-token", "sensitive": true } },
"max_redirects_to_follow": 3,
"with_dns": true,
"timeout": null
}
},
"assertions": [{ "StatusCode": { "expected": { "Eq": 200 } } }]
}
method:"GET"or"POST".body/ a header value:{ "value": "...", "sensitive": false }. Set"sensitive": trueto have HeimPulse encrypt it at rest - once saved, a sensitive value is never redisplayed (not even its ciphertext) in any read response; only a{"key_id": "..."}marker comes back so you know one is set.timeout:null, or{"secs": <u64>, "nanos": <u32>}- this is Rust’sstd::time::Durationserialized by serde’s own default, not a plain millisecond/second number. A 30-second timeout is{"secs": 30, "nanos": 0}.
Assertions
assertions is an array of tagged objects, one of five kinds:
[
{ "StatusCode": { "expected": { "Eq": 200 } } },
{ "Latency": { "l_type": "TotalLatency", "expected": { "LessThan": { "secs": 1, "nanos": 0 } } } },
{ "DnsResoltion": { "expected": { "LessOrEqual": { "secs": 0, "nanos": 200000000 } } } },
{ "Header": { "name": "content-type", "matcher": { "Contains": "application/json" } } },
{ "Body": { "matcher": { "Regex": "\"status\"\\s*:\\s*\"ok\"" } } }
]
The comparison operators
StatusCode’s expected field is a Comparison - one of:
| Variant | Shape | Meaning |
|---|---|---|
Eq / NotEq |
{"Eq": 200} |
equals / does not equal |
GreaterThan / GreaterOrEqual / LessThan / LessOrEqual |
{"GreaterThan": 500} |
ordering |
Between |
{"Between": [200, 299]} |
inclusive range (two values) |
OneOf |
{"OneOf": [200, 201, 204]} |
any of a set |
Latency and DnsResoltion use a narrower DurationComparison instead - a status code is a
discrete set of legal values (Eq/OneOf make sense there), but a timing measurement is
continuous, so only the ordering-style comparisons apply:
| Variant | Shape |
|---|---|
LessThan / LessOrEqual |
{"LessThan": {"secs": 1, "nanos": 0}} |
Between |
{"Between": [{"secs": 0, "nanos": 0}, {"secs": 2, "nanos": 0}]} |
l_type (on Latency only) is one of HeadersLatency, BodyLatency, TotalLatency.
String matching
Header and Body assertions both use a StringMatcher:
| Variant | Shape |
|---|---|
Equals |
{"Equals": "ok"} |
Contains |
{"Contains": "application/json"} |
StartsWith / EndsWith |
{"StartsWith": "2."} |
Regex |
{"Regex": "^ok$"} |
Why these particular pieces are opaque in the reference
The generated OpenAPI spec derives real, named schemas for almost everything above - the one
exception is the specific comparison value itself (Comparison<T>’s operand and
DurationComparison, both shown as a bare object in the reference). Comparison<T> is a Rust
generic used with a single concrete type parameter (u16, for a status code) mixing single-value
and array-shaped variants in one enum - a real limitation of the OpenAPI-generation tooling for
that specific shape, not a gap in what the API itself accepts. DurationComparison and the raw
timeout field both bottom out in std::time::Duration, which has no fixed schema representation
of its own beyond the {secs, nanos} shape documented above. Everything else in a service or
endpoint definition - including which five assertion kinds exist and every other field on each -
is fully typed in the reference itself.