---
title: Services
description: The real shape of an endpoint's call and assertions, including the few leaves the reference documents as opaque objects.
order: 4
---

`POST`/`PATCH /v1/services` accept full endpoint definitions - most of it is typed exactly in the
[API reference](/reference.html) (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):

```json
{
  "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": true` to
  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's `std::time::Duration`
  serialized 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:

```json
[
  { "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.
