# Monitor an API response (API monitor)

The **API** monitor (API type `api`, shown in the app as **API monitoring/text analysis**) is a
[Website / HTTPS check](/monitors/types/http/) that also reads the response body: it parses it as JSON, XML or
text, picks one value out of it and compares that value with what you expect. Use it when "the endpoint answered
200" is not proof enough - you want `$.status` to be `"ok"`, a queue length to stay under 100, or a price field
to exist.

## At a glance

| | |
|---|---|
| API type token | `api` |
| Runs from | HostTracker's public checkpoint fleet - you pick the locations |
| Intervals in the app | 1 minute to 24 hours, or a cron schedule |
| Default interval | 3 minutes |
| Plan gates | the API monitoring type is a package feature (`apiTask`); the Http options keep their own gates |

## How Down is decided

Everything the [Website monitor](/monitors/types/http/#how-down-is-decided) checks (DNS, connection, TLS,
status below 400, your status-code rules), and then:

1. The body must **parse** in the chosen **Response format**.
2. The **Selector** picks a value (empty selector = the whole body).
3. The **Condition** must hold for that value. If the selector matches nothing, every condition except
   `no` and `null` fails with "No value selected".

With **Assertion mode** on, the rule list replaces steps 1-3. A failure is re-checked from other locations
before the monitor turns Down - see [How down detection works](/monitors/down-detection/).

## Settings reference

The API editor is the Website editor with a different **Response Validation** group. Everything in
[Common monitor fields](/monitors/types/http/#common-monitor-fields), **Main Settings**, **Request
Configuration** and **Monitoring Locations** works exactly as on the Website page, with these differences:

- The address field is labelled **API endpoint**.
- **Request body** offers **Raw body** (`settings.body`) and **POST parameters** (`settings.postParameters`, one
  `KEY=VALUE` per line, sent form-encoded).
- **Attached monitors** offers **Domain Expiration**, **Certificate Expiration** and **Web Risk**. DNSBL and
  Indexability are not available on API monitors.
- Keywords and **Successful content check condition** are not part of the API editor. The **HTTP Policies**
  picker is, but it has no API member yet.

### Response Validation (API-specific)

| Setting (app label) | API field | Type / allowed values | Default | Plan limits | What it does for you |
|---|---|---|---|---|---|
| **Response format** | `settings.contentType` | `application/json` (**JSON analysis**), `text/xml` (**XML analysis**), `text/plain` (**String analysis**) | `application/json` in the app; required in the API unless `assertMode` is on | - | How the body is parsed before the selector runs. Needs GET (or another method that returns a body). |
| **Selector** | `settings.valueSelector` | JSON: property selector or JSONPath (`$.data.status`); XML: XPath; text: a regular expression with a capture group (multiline, case-insensitive) | empty = the whole body | - | Picks the value to test. It is compiled when you save, so a broken selector is refused then, not at check time. |
| **What to check** | `settings.expectation.change`, `settings.expectation.cpt` | **Check the obtained value itself** (`change: 0`); **Check the value change between checks** (`change: 1`); **Change per second** (`change: 1, cpt: "s"`); **Change per minute** (`change: 1, cpt: "m"`) | the value itself | - | Judge the value, or how much a number moved since the previous check. Change modes work on numbers only. |
| **Condition** | `settings.expectation.func` + `settings.expectation.args` | see the table below | **any (no validation)** - no expectation | - | The comparison that decides Up or Down. |
| **Ignore HTTP Errors**, **Error on these HTTP statuses** | `settings.ignoredStatuses`, `settings.errorStatuses` | up to 20 codes each, 100-599 | none | - | As on the Website monitor. |
| **Assertion mode** | `settings.assertMode` + `settings.assertsSource` or `settings.asserts` | up to 20 rules; your package may allow fewer | off | assertion count and stateful rules are package features | A rule list instead of format/selector/condition. See [Assertions](/monitors/advanced/assertions/). |
| **TLS Handshake** switches, **Expected IPs Validation**, **Max response size** | as on the [Website monitor](/monitors/types/http/#response-validation) | | | | |

The whole `expectation` object is limited to 1024 characters.

**Condition** values:

| App option | `func` | `args` | The check fails when |
|---|---|---|---|
| **any (no validation)** | omit `expectation` | - | never on the value - only parsing and transport are judged |
| **equal to** | `eq` | one value | the value differs |
| **not equal to** | `neq` | one value | the value matches |
| **in set of** | `in` | one or more values | the value is none of them |
| **out of set** | `out` | one or more values | the value is one of them |
| **less than** / **less or equal** | `ls` / `le` | one number | the value is not below (or at most) it |
| **greater than** / **greater or equal** | `gt` / `ge` | one number | the value is not above (or at least) it |
| **in range** | `inr` | two ascending numbers | the value is outside `[args[0], args[1]]` |
| **out of range** | `outr` | two ascending numbers | the value is inside the range |
| **json null** | `null` | none | the selected value is not null (nothing selected counts as null) |
| **absent in content** | `no` | none | never - the check extracts the value and does not compare it |

`args` are strings in JSON (`["ok"]`, `["100"]`). `eq`, `neq`, `in` and `out` compare the value as it is; the
other conditions, and every condition in a change mode, compare numbers.

## Set it up in the app

1. On the **Sites** dashboard, click **Add Monitor** and choose **API monitoring/text analysis** in
   **Monitoring Type**.
2. Enter the **API endpoint** and a name.
3. In **Request Configuration**, set the **HTTP Method**, any **HTTP Headers** (for example an API key) and the
   **Request body** if the endpoint needs one.
4. In **Response Validation**, choose the **Response format**, type the **Selector**, pick **What to check** and a
   **Condition** with its value.
5. Review **Alert Subscriptions** and **Monitoring Locations**, then click **Save**.

## Do it with the API or MCP

`POST /monitor` (scope `monitor:write`), base URL `https://api2.host-tracker.com`:

```bash
curl -X POST https://api2.host-tracker.com/monitor \
  -H "Authorization: Bearer $HT_TOKEN" -H "Content-Type: application/json" \
  -d '{
    "type": "api",
    "url": "https://api.example.com/health",
    "interval": 60,
    "locations": { "pools": ["allworld"] },
    "settings": {
      "contentType": "application/json",
      "valueSelector": "$.status",
      "expectation": { "func": "eq", "args": ["ok"] }
    }
  }'
```

The same check written as assertion rules instead:

```json
{
  "type": "api",
  "url": "https://api.example.com/health",
  "interval": 60,
  "locations": { "pools": ["allworld"] },
  "settings": {
    "assertMode": true,
    "assertsSource": "status eq 200\nbody.json.path(\"$.status\") eq \"ok\""
  }
}
```

Update - for example, alert when a queue grows past 100:

```bash
curl -X PATCH https://api2.host-tracker.com/monitor/<monitor-id> \
  -H "Authorization: Bearer $HT_TOKEN" -H "Content-Type: application/json" \
  -d '{ "settings": { "valueSelector": "$.queue.length",
                      "expectation": { "func": "ls", "args": ["100"] } } }'
```

MCP (`interval` is passed to the API as-is, in seconds):

```text
create_monitor(type="api", url="https://api.example.com/health", interval=60, pools="allworld",
               settingsJson="{\"contentType\":\"application/json\",\"valueSelector\":\"$.status\",\"expectation\":{\"func\":\"eq\",\"args\":[\"ok\"]}}")
update_monitor(id="<monitor-id>", settingsJson="{\"expectation\":{\"func\":\"ls\",\"args\":[\"100\"]}}")
```

## Recipes

- **A JSON field equals a value** - `contentType: application/json`, `valueSelector: $.status`,
  `expectation: {func: eq, args: ["ok"]}`.
- **A number stays in range** - `valueSelector: $.latency_ms`, `expectation: {func: inr, args: ["0", "500"]}`.
- **A counter keeps growing** - `expectation: {func: gt, args: ["0"], change: 1}` (fails if it stops moving up).
- **An XML element** - `contentType: text/xml`, `valueSelector: /response/status`, `func: eq`.
- **A value in plain text** - `contentType: text/plain`, `valueSelector: version=(\d+)`, `func: ge`.
- **Several conditions at once** - use assertion mode; one rule per line.
- **Authenticated API** - add `headers: [{"name": "Authorization", "value": "Bearer ..."}]`.

## What happens next

Each check downloads the body (up to **Max response size**), parses it and records the selected value in the
check log, so you can see what was compared. A failed condition is re-checked from other locations, then opens
an incident and alerts the subscribed contacts.

## Limits and gotchas

- `403 package_limit` - your package does not include the API monitoring type.
- `422 invalid_settings` - a malformed selector, a wrong number of `args` for the `func` (two ascending
  numbers for `inr`/`outr`, at least one for `in`/`out`, exactly one for the comparisons), or `contentType`
  missing while assertion mode is off.
- `assertMode` refuses `contentType`, `valueSelector` and `expectation` in the same monitor, as well as the Http
  status and keyword fields.
- HEAD returns no body, so use GET (or POST/PUT/PATCH) for a format and selector check.
- The **absent in content** option (`no`) does not test absence; to fail when a field is missing, use any
  comparison - a missing value fails it.

## Related

- [Website / HTTPS monitor](/monitors/types/http/)
- [Assertions and validation rules](/monitors/advanced/assertions/)
- [Assertion language reference](/reference/assert-language/)
- [Transaction monitor](/monitors/types/transaction/)
