> ## Documentation Index
> Fetch the complete documentation index at: https://docs.uptimeio.com/llms.txt
> Use this file to discover all available pages before exploring further.

# Create Monitor

> Create a new monitor to start tracking uptime

## Overview

Create a monitor that checks an endpoint, server or scheduled job on a fixed interval. Six monitor types are supported: `HTTP`, `KEYWORD`, `ICMP` (ping), `TCP` (port), `DNS` and `HEARTBEAT`. Checking starts as soon as the monitor is created.

```
POST https://api.uptimeio.com/api/monitors
```

## Authentication

| Header | Required | Description |
| - | - | - |
| `X-API-Key` | Yes (or a Bearer token) | Your API key. It must have **read and write** access; read-only keys receive `READ_ONLY_API_KEY` (403). See [Authentication](/api-reference/authentication). |
| `Content-Type` | Yes | `application/json` |
| `X-Project-ID` | No | Project that will own the monitor. Defaults to your organization's default project. |

Creating a monitor also requires a verified email address on the account that owns the key (otherwise `EMAIL_NOT_VERIFIED`, 403) and a role that can create monitors (otherwise `INSUFFICIENT_PERMISSIONS`, 403).

## Request body

### Common fields

| Field | Type | Required | Description |
| - | - | - | - |
| `name` | string | Yes | Display name, 1-80 characters. |
| `type` | enum | Yes | `HTTP`, `KEYWORD`, `ICMP`, `TCP`, `DNS` or `HEARTBEAT`. **Cannot be changed later.** |
| `target` | string | Yes (except `HEARTBEAT`) | What to check. A full URL for `HTTP` and `KEYWORD`; a hostname or IP address for `ICMP`, `TCP` and `DNS` (`TCP` also accepts `host:port`). Not used for `HEARTBEAT`; a ping URL is generated for you. Maximum 2048 characters. |
| `interval_seconds` | integer | Yes | Seconds between checks, 30-86400, and never below your plan minimum (Free 300, Pro and Scale 60). |
| `monitoring_regions` | array of strings | Yes | `[]` lets UptimeIO choose the locations automatically. A non-empty array pins regular checks to those locations (see [Probe locations](#probe-locations) below). Maximum 10 entries. |
| `timeout_ms` | integer | No | Per-check timeout, 1000-60000. Must be shorter than the check interval. Default `10000`. |
| `tags` | array of strings | No | Free-form labels. Default `[]`. |
| `group_id` | UUID | No | Monitor group to place the monitor in. |
| `notification_target_ids` | array of UUIDs | No | Notification destinations that receive this monitor's alerts. |
| `ssl_monitoring` | object | No | Certificate expiry warnings. See [SSL certificate monitoring](#ssl-certificate-monitoring). |
| `domain_monitoring` | object | No | Domain registration expiry warnings. See [Domain expiry monitoring](#domain-expiry-monitoring). |
| `verify_ssl` | boolean | No | Validate the TLS certificate on `HTTP` and `KEYWORD` checks. Default `true`. |

### Probe locations

UptimeIO checks from multiple locations (see [Probe locations](/monitors/probe-locations)). The `monitoring_regions` values you can send are listed by the public `GET /api/regions` endpoint.

* Use `[]` to let UptimeIO pick among the available locations. This is the only option on the **Free** plan.
* Pro and Scale can pin regular checks to one or more locations. **At least one** location is enough.
* A region group code (`europe`, `north-america`, `asia`, `oceania`) selects every location in that group. `GET /api/regions` needs no authentication.
* A location with no active probe is rejected with `INVALID_REGIONS`.
* An incident is only opened after failures are confirmed from more than one location. The confirming checks use other locations automatically, so selecting a single location does not weaken that protection.

### HTTP monitor

Check a URL and expect a status code.

```json theme={null}
{
  "name": "Production API",
  "type": "HTTP",
  "target": "https://api.example.com/health",
  "interval_seconds": 60,
  "timeout_ms": 10000,
  "monitoring_regions": ["netherlands", "us-west"],
  "tags": ["production"],
  "http_config": {
    "method": "GET",
    "headers": { "Accept": "application/json" },
    "follow_redirects": true,
    "max_redirects": 5,
    "expected_status_codes": [200, 204],
    "slow_response_threshold_ms": 2000
  }
}
```

`http_config` fields:

| Field | Type | Required | Description |
| - | - | - | - |
| `method` | enum | Yes | `GET`, `POST`, `PUT`, `DELETE`, `HEAD`, `OPTIONS` or `PATCH`. |
| `headers` | object | No | Request headers (string values). |
| `body` | string | No | Request body. |
| `content_type` | string | No | Content type for the body. |
| `follow_redirects` | boolean | No | Default `true`. |
| `max_redirects` | integer | No | 0-10. Default `5`. |
| `expected_status_codes` | array of integers | No | Each 100-599. Default `[200]`. |
| `auth` | object | No | `{ "type": "basic" \| "digest", "username": "...", "password": "..." }`. |
| `slow_response_threshold_ms` | integer | No | Raise a slow-response alert above this response time. |

### Keyword monitor

Fetch a URL and check the response for words that must, or must not, be present.

```json theme={null}
{
  "name": "Storefront is in stock",
  "type": "KEYWORD",
  "target": "https://shop.example.com/products",
  "interval_seconds": 300,
  "monitoring_regions": [],
  "keyword_config": {
    "method": "GET",
    "keywords": [
      { "text": "Add to cart", "should_contain": true },
      { "text": "Out of stock", "should_contain": false }
    ],
    "match_mode": "all",
    "case_sensitive": false
  }
}
```

`keyword_config` fields:

| Field | Type | Required | Description |
| - | - | - | - |
| `method` | enum | Yes | `GET`, `POST`, `PUT`, `DELETE`, `OPTIONS` or `PATCH`. |
| `keywords` | array | One of `keywords` or `keyword` | 1-20 entries of `{ "text": string (1-500 chars), "should_contain": boolean }`. `should_contain: true` means the text must appear; `false` means the check fails if it appears. |
| `match_mode` | enum | No | `all` (default) or `any`. How the `should_contain: true` keywords combine: every one must appear, or at least one. Forbidden keywords (`should_contain: false`) always fail the check when found, regardless of mode. |
| `case_sensitive` | boolean | No | Default `false`. |
| `keyword` | string | No | Legacy single keyword. Ignored when `keywords` is present. |
| `should_contain` | boolean | No | Legacy polarity for `keyword`. Default `true`. |
| `slow_response_threshold_ms` | integer | No | Slow-response alert threshold. |

Set request headers, body, redirect handling and expected status codes for a keyword monitor through `http_config` (same fields as above).

### ICMP (ping) monitor

```json theme={null}
{
  "name": "Web server ping",
  "type": "ICMP",
  "target": "web01.example.net",
  "interval_seconds": 300,
  "monitoring_regions": [],
  "icmp_config": { "packet_count": 4, "packet_size": 64, "timeout_ms": 10000 }
}
```

| Field | Type | Default | Range |
| - | - | - | - |
| `packet_count` | integer | `4` | 1-100 |
| `packet_size` | integer | `64` | 1-65507 bytes |
| `timeout_ms` | integer | `10000` | 1-60000 |
| `ttl` | integer | none | 1-255 |

### TCP (port) monitor

```json theme={null}
{
  "name": "Mail server SMTP",
  "type": "TCP",
  "target": "mail.example.net",
  "interval_seconds": 300,
  "monitoring_regions": [],
  "tcp_config": {
    "port": 25,
    "protocol": "smtp",
    "protocol_validation": { "smtp": { "expect_banner": true } }
  }
}
```

| Field | Type | Required | Description |
| - | - | - | - |
| `port` | integer | Yes | 1-65535. |
| `protocol` | enum | No | `smtp`, `pop3`, `imap`, `ftp`, `ssh`, `http`, `https` or `generic` (default). A label stored with the monitor; `generic` is a plain connection test. |
| `connection_timeout_ms` | integer | No | 1-60000. Default `10000`. |
| `protocol_validation` | object | No | Optional extra checks: `smtp`: `{ expect_banner, test_command? }`; `pop3`, `ftp`, `imap`: `{ expect_banner }`; `http`: `{ path, expected_response? }`. `expect_banner: true` expects the server greeting (`220` for SMTP and FTP, `+OK` for POP3, `* OK` for IMAP). |

### DNS monitor

```json theme={null}
{
  "name": "WWW A record",
  "type": "DNS",
  "target": "www.example.net",
  "interval_seconds": 300,
  "monitoring_regions": [],
  "dns_config": {
    "record_entries": [
      {
        "id": "a-record",
        "record_type": "A",
        "expected_values": ["203.0.113.10"],
        "validation_mode": "exact"
      }
    ],
    "dns_server": "1.1.1.1",
    "resolution_timeout_ms": 5000
  }
}
```

| Field | Type | Required | Description |
| - | - | - | - |
| `record_entries` | array | Yes | 1-10 entries (below). |
| `record_entries[].id` | string | Yes | Your identifier for the entry. |
| `record_entries[].record_type` | enum | Yes | `A`, `AAAA`, `CNAME`, `MX`, `TXT` or `NS`. |
| `record_entries[].expected_values` | array of strings | Yes | At least one value. |
| `record_entries[].validation_mode` | enum | Yes | `exact`, `contains` or `regex`. |
| `dns_server` | string | No | Resolver to query. |
| `resolution_timeout_ms` | integer | No | 1-60000. Default `5000`. |

### Heartbeat monitor

UptimeIO waits for your job to ping a generated URL. `target` is not needed.

```json theme={null}
{
  "name": "Nightly backup",
  "type": "HEARTBEAT",
  "interval_seconds": 86400,
  "monitoring_regions": [],
  "heartbeat_config": {
    "expected_interval_seconds": 86400,
    "grace_period_seconds": 600
  }
}
```

| Field | Type | Required | Description |
| - | - | - | - |
| `expected_interval_seconds` | integer | Yes | How often the job should ping. At least 60. The monitor's `interval_seconds` is set to this value. |
| `grace_period_seconds` | integer | Yes | Extra time allowed before a missed ping counts. At most 3600. |
| `notification_delay_minutes` | integer | No | Default `5`. |
| `max_consecutive_misses` | integer | No | Default `3`. |

The response contains `heartbeat_token`. Your job pings the URL `/heartbeat/{heartbeat_token}`; the full ping URL, including host, is shown on the monitor in the dashboard. See [Heartbeat monitors](/monitors/heartbeat).

### SSL certificate monitoring

Applies to `HTTP` and `KEYWORD` monitors on `https://` targets.

```json theme={null}
{ "ssl_monitoring": { "enabled": true, "check_expiry": true, "expiry_warning_days": [30, 15, 7, 1] } }
```

| Field | Type | Default | Description |
| - | - | - | - |
| `enabled` | boolean | required | Turn SSL monitoring on or off. |
| `check_expiry` | boolean | `true` | Send warnings before the certificate expires. Warnings go to your notification channels and do not open an incident. |
| `check_validity` | boolean | `true` | An expired or invalid certificate opens an incident, which resolves automatically once a valid certificate is served. |
| `expiry_warning_days` | array | `[7, 1]` | Any of `30`, `15`, `7`, `1`. At least one is required when `enabled` and `check_expiry` are `true`. |
| `ignore_self_signed` | boolean | none | Do not alert on self-signed certificates. |

### Domain expiry monitoring

Warns you before the **registration** of the monitored domain expires.

```json theme={null}
{ "domain_monitoring": { "enabled": true, "expiry_warning_days": [30, 7] } }
```

| Field | Type | Required | Description |
| - | - | - | - |
| `enabled` | boolean | Yes | Turn domain expiry warnings on or off. |
| `expiry_warning_days` | array | No | Any of `30`, `15`, `7`, `1` (at most 4). Default `[30, 15, 7, 1]`. At least one is required when `enabled` is `true`. Stored de-duplicated and sorted, largest first. |

How it works:

* Registration data is looked up via RDAP and refreshed hourly; it is not re-read on every check.
* Only `HTTP`, `KEYWORD` and `DNS` monitors whose target is a publicly registered domain qualify. IP addresses, `localhost` and internal names (for example `db.internal`) are rejected with `VALIDATION_ERROR` (400), with `details.field` set to `domain_monitoring`.
* The registered domain is what is checked: `https://api.eu.example.co.uk/health` is checked as `example.co.uk`, and a hosted subdomain such as `myapp.herokuapp.com` as `herokuapp.com`.
* Some country-code registries do not publish an expiry date (many `.de` domains, for example). For those the expiry is reported as not published and no warnings are sent.

## Response

### 201 Created

```json theme={null}
{
  "success": true,
  "data": {
    "monitor": {
      "id": "b7a2c1de-3f54-4c21-9d0a-6e1f2a3b4c5d",
      "organization_id": "0c9e8d7f-1a2b-4c3d-8e9f-0a1b2c3d4e5f",
      "created_by": "5f4e3d2c-1b0a-4f9e-8d7c-6b5a4f3e2d1c",
      "name": "Production API",
      "target": "https://api.example.com/health",
      "type": "HTTP",
      "interval_seconds": 60,
      "status": "active",
      "configState": "active",
      "runtimeStatus": "unknown",
      "created_at": 1767000000,
      "updated_at": 1767000000,
      "method": "GET",
      "headers": { "Accept": "application/json" },
      "timeout_ms": 10000,
      "expected_status": [200, 204],
      "follow_redirects": true,
      "verify_ssl": true,
      "slow_response_threshold_ms": 2000
    }
  }
}
```

The monitor object carries the fields relevant to its type (for example `keyword_config`, `tcp_config`, `dns_config`, `icmp_config`, `ssl_monitoring`, `domain_monitoring`, `heartbeat_token`). `created_at` and `updated_at` are Unix timestamps in seconds. `monitoring_regions` and `tags` are accepted when you write a monitor but are not included in monitor responses. See [Get Monitor](/api-reference/monitors/get) for the full field list.

## Plan limits

| | Free | Pro | Scale |
| - | - | - | - |
| Monitors | 50 | 100 | 500 |
| Minimum interval | 300 s (5 min) | 60 s (1 min) | 60 s (1 min) |
| Pin probe locations | No (use `[]`) | Yes | Yes |
| Heartbeat, DNS, SSL, domain expiry, slow-response alerts | Yes | Yes | Yes |

## Errors

Errors use the standard envelope (see [Errors](/api-reference/errors)).

| Code | Status | Meaning |
| - | - | - |
| `VALIDATION_ERROR` | 400 | The body failed validation, the target is not allowed for the monitor type, or `domain_monitoring` was enabled on an ineligible monitor. `message` lists the failing fields, for example `Validation failed: interval_seconds: Interval must be at least 30 seconds`. |
| `VALIDATION_FAILED` | 400 | `heartbeat_config` is missing or out of range on a `HEARTBEAT` monitor. |
| `INVALID_REGIONS` | 400 | A region code is unknown or has no active probe. |
| `MONITOR_CREATION_FAILED` | 400 | The monitor could not be created. |
| `AUTHENTICATION_REQUIRED` / `MISSING_AUTH` / `INVALID_API_KEY` | 401 | Missing or invalid credentials. |
| `READ_ONLY_API_KEY` | 403 | The key has read-only access. |
| `EMAIL_NOT_VERIFIED` | 403 | Verify the account email first. |
| `INSUFFICIENT_PERMISSIONS` | 403 | Your role cannot create monitors. |
| `MONITOR_LIMIT_REACHED` | 403 | You have reached your plan's monitor limit. |
| `INTERVAL_TOO_SHORT` | 403 | `interval_seconds` is below your plan minimum. |
| `LOCATION_MONITORING_NOT_AVAILABLE` | 403 | A non-empty `monitoring_regions` was sent on the Free plan. |

Targets are rejected when they point at private or reserved IP ranges, `localhost` and `*.local`, `*.internal`, `*.lan` names, well-known third-party domains, or the apex of `example.com`, `example.org`, `example.net` and `test.com` for `HTTP` and `KEYWORD` monitors. Subdomains such as `api.example.com` are allowed.

## Example

<CodeGroup>
  ```bash cURL theme={null}
  curl -X POST https://api.uptimeio.com/api/monitors \
    -H "X-API-Key: YOUR_API_KEY" \
    -H "Content-Type: application/json" \
    -d '{
      "name": "Production API",
      "type": "HTTP",
      "target": "https://api.example.com/health",
      "interval_seconds": 60,
      "monitoring_regions": [],
      "http_config": { "method": "GET", "expected_status_codes": [200] },
      "ssl_monitoring": { "enabled": true, "expiry_warning_days": [30, 15, 7, 1] },
      "domain_monitoring": { "enabled": true }
    }'
  ```

  ```javascript JavaScript theme={null}
  const response = await fetch('https://api.uptimeio.com/api/monitors', {
    method: 'POST',
    headers: {
      'X-API-Key': process.env.UPTIMEIO_API_KEY,
      'Content-Type': 'application/json',
    },
    body: JSON.stringify({
      name: 'Production API',
      type: 'HTTP',
      target: 'https://api.example.com/health',
      interval_seconds: 60,
      monitoring_regions: [],
      http_config: { method: 'GET', expected_status_codes: [200] },
    }),
  });

  const { data } = await response.json();
  console.log(data.monitor.id);
  ```

  ```python Python theme={null}
  import os
  import requests

  response = requests.post(
      "https://api.uptimeio.com/api/monitors",
      headers={"X-API-Key": os.environ["UPTIMEIO_API_KEY"]},
      json={
          "name": "Production API",
          "type": "HTTP",
          "target": "https://api.example.com/health",
          "interval_seconds": 60,
          "monitoring_regions": [],
          "http_config": {"method": "GET", "expected_status_codes": [200]},
      },
  )
  print(response.json()["data"]["monitor"]["id"])
  ```
</CodeGroup>


This documentation is built and hosted on [Mintlify](https://mintlify.com), a developer documentation platform.