# Create a monitor

`POST https://evohub.io/api/v1/monitors`

Part of the [Uptime API](https://docs-dev.evohub.io/uptime.md) reference · operationId `createMonitor`.

Creates a monitor. `name`, `type`, `interval_seconds` and
`timeout_seconds` are required. `url` depends on the type:

| Type | `url` |
| --- | --- |
| `http`, `keyword` | An `http://` or `https://` URL. |
| `tcp` | `host:port`, `[v6]:port`, `tcp://host:port`, or an http(s) URL (port 80 or 443). |
| `icmp` | A host name or IP address. A port or URL is accepted and the host is used. |
| `heartbeat` | Not used. |

A new monitor is paused unless you send `"is_active": true`. Its
`last_status` is `unknown` until the first check.

A heartbeat monitor gets a `ping_token` at creation, which never
changes. Its job calls `https://evohub.io/ping/{ping_token}`.

Defaults when a field is left out or `0`: `expected_status` 200,
`failure_threshold` 1, `http_method` `GET`,
`ssl_expiry_threshold_days` 30, `re_alert_minutes` 15, and for a
heartbeat `grace_seconds` 60.

`team_id` must be a team the caller is in, or `""` for the whole
organization; otherwise **403** `NOT_IN_TEAM`.

Requires the `uptime:monitor:write` scope.

## Authorization

Any one of:

- `bearerKey`
- `headerKey`

Where:

- `bearerKey`: HTTP Bearer — An EvoHub API key (`evohub_…`) as a bearer token.
- `headerKey`: API key in the header `X-API-Key` — An EvoHub API key (`evohub_…`).

## Request body (required)

Content type: `application/json`

Type: `CreateMonitorRequest`

- `name` (string, required, min length 1, max length 255)
- `team_id` (string): A team the caller is in, or `""` (the default) for the whole organization.
- `type` (MonitorType, required, one of `http`, `keyword`, `tcp`, `icmp`, `heartbeat`)
- `url` (string): The target; see the table above. Not used by `heartbeat`.
- `interval_seconds` (integer, required, min 15, max 2592000)
- `timeout_seconds` (integer, required, min 1, max 60)
- `grace_seconds` (integer, min 0, max 86400): Heartbeat only. Defaults to 60.
- `locations` (array of string, max items 5)
- `keyword` (string)
- `expected_status` (integer, default `200`, min 100, max 599)
- `is_active` (boolean, default `false`): Send `true` to start checking at once.
- `failure_threshold` (integer, default `1`, min 1, max 10)
- `http_method` (string, one of `GET`, `POST`, `PUT`, `PATCH`, `DELETE`, `HEAD`, `OPTIONS`, default `GET`)
- `http_headers` (object): Request headers sent with every check, by name. Values are stored but never returned.
  - Other keys: string
- `http_body` (string)
- `http_auth_user` (string)
- `http_auth_password` (string, write-only): Basic Auth password sent with every check. Stored but never returned; responses carry `has_http_auth_password`.
- `check_ssl` (boolean)
- `ssl_expiry_threshold_days` (integer, default `30`, min 1, max 365)
- `tags` (array of string, max items 20)
- `group_id` (string)
- `response_time_threshold_ms` (integer, min 1)
- `oncall_policy_id` (string): The On-Call escalation policy its alerts go to.
- `re_alert_minutes` (integer, default `15`, min 1, max 1440)
- `assertions` (array of Assertion, max items 10): `http` monitors only.
  - `type` (string, required, one of `status_code`, `body`, `json`, `header`)
  - `field` (string, max length 1024)
  - `operator` (string, required, one of `in`, `contains`, `not_contains`, `equals`, `not_equals`, `exists`)
  - `value` (string, max length 1024)
- `degraded_threshold_ms` (integer, min 1, max 60000): `0` or absent means never degraded.
- `alert_on_degraded` (boolean)
- `sla_target` (number, max 100): `0` or absent means no target.

## Responses

### 201 — The monitor was created.

Content type: `application/json`

Type: `MonitorEnvelope`

- `data` (Monitor)
  - `id` (string): Starts with `mon_`.
  - `org_id` (string)
  - `team_id` (string): The owning team, or `""` for the whole organization.
  - `name` (string)
  - `url` (string): The target; `""` for a heartbeat monitor. Shown with credentials masked: userinfo becomes `****:****@` and the values of `token`, `key`, `secret`, `password`, `sig`, `signature`, `auth`, `api_key` and `access_token` query parameters (any case, or a name ending in `_` or `-` plus one of them) become `****`. Checks use the URL as entered.
  - `type` (MonitorType, one of `http`, `keyword`, `tcp`, `icmp`, `heartbeat`)
  - `interval_seconds` (integer): How often it is checked; for a heartbeat, how often a ping is expected.
  - `timeout_seconds` (integer)
  - `locations` (array of string): Probe locations. Empty means EvoHub chooses.
  - `keyword` (string): Text a `keyword` monitor looks for in the body.
  - `expected_status` (integer): The status code an HTTP check expects when there are no `status_code` assertions.
  - `is_active` (boolean): `false` while paused: no checks and no alerts.
  - `failure_threshold` (integer): Failed checks in a row before the monitor is down.
  - `consecutive_failures` (integer)
  - `last_checked_at` (string (date-time))
  - `last_status` (MonitorStatus, one of `up`, `down`, `degraded`, `unknown`)
  - `silenced_until` (string (date-time)): Present on the list while a silence window mutes the monitor; when it ends.
  - `http_method` (string)
  - `http_headers` (array of object): The request headers by name, sorted. A header's value is never returned, since it often carries a token. `has_value` says whether one is stored.
    - `name` (string)
    - `has_value` (boolean)
  - `http_body` (string)
  - `http_auth_user` (string)
  - `has_http_auth_password` (boolean): Whether a Basic Auth password is stored. The password itself is never returned.
  - `check_ssl` (boolean): Warn before the TLS certificate expires.
  - `ssl_expiry_threshold_days` (integer): Days before expiry to warn.
  - `ssl_expiry_days` (integer): Days until the certificate expires, from the last check.
  - `tags` (array of string)
  - `group_id` (string)
  - `response_time_threshold_ms` (integer)
  - `oncall_policy_id` (string): The On-Call escalation policy its alerts go to.
  - `re_alert_minutes` (integer): Minutes between repeat alerts while down.
  - `grace_seconds` (integer): Heartbeat only. How late a ping may be.
  - `ping_token` (string): Heartbeat only. The job calls `https://evohub.io/ping/{ping_token}`.
  - `last_ping_at` (string (date-time)): Heartbeat only.
  - `created_at` (string (date-time))
  - `updated_at` (string (date-time))
  - `assertions` (array of Assertion)
    - `type` (string, required, one of `status_code`, `body`, `json`, `header`)
    - `field` (string, max length 1024)
    - `operator` (string, required, one of `in`, `contains`, `not_contains`, `equals`, `not_equals`, `exists`)
    - `value` (string, max length 1024)
  - `degraded_threshold_ms` (integer | null): A passing check slower than this is degraded.
  - `alert_on_degraded` (boolean): Raise a lower-severity alert while degraded.
  - `consecutive_degraded` (integer)
  - `sla_target` (number | null): The uptime percentage the monitor is held to, e.g. 99.9.
  - `last_failure_reason` (string)
  - `last_failure_at` (string (date-time))
  - `uptime_percent` (object): Uptime percentage per window. A single monitor carries `24h`, `7d`, `30d` and `90d`; the list carries only `30d`. `null` for a window with no checks. Absent for heartbeat monitors.
    - Other keys: number | null
  - `sla_breached_30d` (boolean): The 30-day uptime is below `sla_target`. Absent without a target or 30-day data, and for heartbeat monitors.

### 400 — The body is not JSON (`INVALID_JSON`), or a field breaks a rule (`VALIDATION_ERROR`; the message names it): a missing required field, a value out of range, a `url` the type cannot probe, assertions on a non-http monitor or an invalid assertion, `degraded_threshold_ms` outside 1–60000, or `sla_target` outside (0, 100].

Content type: `application/json`

Type: `Error`

- `error` (object, required)
  - `code` (string, required, example `MONITOR_NOT_FOUND`)
  - `message` (string, required, example `monitor not found`)
  - `request_id` (string)

### 401 — No API key was sent, or it is unknown, revoked or expired (`UNAUTHORIZED`).

Content type: `application/json`

Type: `Error`

- `error` (object, required)
  - `code` (string, required, example `MONITOR_NOT_FOUND`)
  - `message` (string, required, example `monitor not found`)
  - `request_id` (string)

### 403 — The key lacks `uptime:monitor:write` (`FORBIDDEN`), or `team_id` names a team the caller is not in (`NOT_IN_TEAM`).

Content type: `application/json`

Type: `Error`

- `error` (object, required)
  - `code` (string, required, example `MONITOR_NOT_FOUND`)
  - `message` (string, required, example `monitor not found`)
  - `request_id` (string)

### 429 — Too many requests (`RATE_LIMITED`). Wait for `Retry-After` seconds.

Headers:

- `Retry-After` (integer): Seconds to wait before retrying.

Content type: `application/json`

Type: `Error`

- `error` (object, required)
  - `code` (string, required, example `MONITOR_NOT_FOUND`)
  - `message` (string, required, example `monitor not found`)
  - `request_id` (string)

### 500 — Something went wrong on EvoHub's side (`INTERNAL_ERROR`). Retry later.

Content type: `application/json`

Type: `Error`

- `error` (object, required)
  - `code` (string, required, example `MONITOR_NOT_FOUND`)
  - `message` (string, required, example `monitor not found`)
  - `request_id` (string)

## Example request

```bash
curl -X POST 'https://evohub.io/api/v1/monitors' \
  -H 'Content-Type: application/json' \
  -H 'Authorization: Bearer <TOKEN>' \
  -d '{
  "url": "https://api.acme.example/health",
  "name": "Checkout API",
  "tags": [
    "checkout",
    "production"
  ],
  "type": "http",
  "is_active": true,
  "assertions": [
    {
      "type": "status_code",
      "value": "200-299",
      "operator": "in"
    },
    {
      "type": "json",
      "field": "$.status",
      "value": "ok",
      "operator": "equals"
    }
  ],
  "sla_target": 99.9,
  "timeout_seconds": 10,
  "interval_seconds": 60,
  "failure_threshold": 2,
  "degraded_threshold_ms": 1500
}'
```
