# Create a monitor — API reference

> Starts checking a website, port, DNS record or host.

Source: https://upfour.io/docs/api/monitors/create-a-monitor

[Developers](https://upfour.io/docs/api.md)/[Monitors](https://upfour.io/docs/api/monitors.md)

Starts checking a website, port, DNS record or host.

POST https://app.upfour.io/v1/monitors

scope monitor.write stable [Idempotency-Key supported](https://upfour.io/docs/api/idempotency.md) returns 201

## Body

The 6 fields most calls use come first; the rest are grouped below.

`application/json` required 56 fields

urlstring

Target for http and ping monitors (ping takes a host)

In the app: **URL to watch**

monitor_typestring

Default http

httptcppingdns

In the app: **Monitor type**

namestring

Display name

In the app: **Name**

interval_secondsinteger

Down to 10, default 60; POST/PUT/PATCH/DELETE monitors need 60 or more

In the app: **Check every**

regionsstring[]

eu (Germany) · us · asia (Singapore) · au (Australia), default eu; each region runs and counts its own checks

euusasiaaudefault

In the app: **Regions**

notification_channel_idsuuid[]

Alert channels to attach

What counts as up5 fields

alert_typestring

status (default: the final response must be 2xx) · keyword (down when the text is missing) · keyword_absence (down when the text is found) · expected_status_code

""statuskeywordkeyword_absenceexpected_status_code

In the app: **Counts as down when**

keywordstring

The text to look for; required for keyword and keyword_absence, which read the body with GET

In the app: **Text to look for**

keyword_case_insensitiveboolean · nullable

Ignore letter case (default true)

In the app: **Ignore case**

expected_status_codeinteger · nullable

100–599; only used with alert_type expected_status_code, on its own it does nothing

In the app: **Expected status code**

ssl_verificationboolean · nullable

Fail on broken certificate chains (default true)

In the app: **Verify the TLS certificate**

Request15 fields

http_methodstring

GET · HEAD · POST · PUT · PATCH · DELETE (default HEAD)

In the app: **HTTP method**

request_headersRequestHeader[]

Custom request headers, at most 20 and 8 KB in total. On update they are kept unless request_headers_action is replace or clear.

In the app: **Headers**

Nested fields 2 fields

| Field | Type | Description |
| --- | --- | --- |
| `name` optional | string | |
| `value` optional | string | |

request_headers_actionstring

On update: keep · replace · clear

keepreplaceclear

request_bodystring

Sent with methods that carry one; up to 8 KB

In the app: **Request body**

request_timeout_sinteger

Per-check budget, default 30, at most 60; a timeout is a failed check

In the app: **Request timeout**

follow_redirectsboolean · nullable

Follow 3xx (default true); with redirects off a 3xx is down unless alert_type is expected_status_code

In the app: **Follow redirects**

keep_cookiesboolean

Persist cookies across a redirect chain

In the app: **Keep cookies when redirecting**

auth_usernamestring

HTTP basic auth; password stored encrypted, never returned

In the app: **Username**

auth_passwordstring

Basic-auth password. Write-only: responses carry has_auth_password instead.

In the app: **Password**

auth_password_actionstring

On update: keep (default) · replace · clear

""keepreplaceclear

proxy_hoststring

Check through your proxy; the host is a plain host name or IP address

In the app: **Proxy**

proxy_portinteger · nullable

Check through your proxy; the host is a plain host name or IP address

ip_versionstring

"4" or "6" to force IPv4 or IPv6, empty for either

""46

In the app: **IP version**

Alerting11 fields

notification_modestring

immediate (default): alert once three checks confirm the outage · quiet: alert only after 3 minutes of downtime in total

quietimmediate

In the app: **When should we alert you?**

alert_channel_namesstring[]

Batch import only (POST /monitors/batch): names resolved to ids

escalation_policy_iduuid · nullable

Attach an alert plan

In the app: **Alert plan**

alert_plan_namestring

Batch import only (POST /monitors/batch): names resolved to ids

default_alert_planboolean

Without escalation_policy_id, attach the default plan when one of the channels is your own email

confirmation_periodinteger

Extra seconds a failure has to last, on top of the three confirming checks, before the monitor goes down; 0 adds none. Only the way down is delayed

In the app: **Extra confirmation time**

recovery_periodinteger

Seconds the monitor has to stay up after it recovers before its incident closes and the all-clear goes out; 0 closes it with the next good check

In the app: **Recovery period**

notify_recoveryboolean · nullable

Default true; the all-clear for an outage is sent in both notification modes

degraded_alert_enabledboolean

Slowdown warnings for this monitor; off by default

degraded_threshold_msinteger · nullable

Slow from this response time; omit to let up4 learn one from the monitor’s own history

capture_on_incident_openboolean · nullable

Take an incident screenshot when an incident opens; omit to use the company default

In the app: **Incident screenshots**

Port and DNS checks6 fields

tcp_hoststring

Target for tcp monitors; port 1–65535

In the app: **Host**

tcp_portinteger · nullable

Target for tcp monitors; port 1–65535

In the app: **Port**

dns_querystring

Name to look up (dns monitors)

In the app: **Domain to look up**

dns_typestring

Default A

""AAAAAMXCNAMETXTNS

In the app: **Record type**

dns_serverstring

Resolver to ask, a plain host name or IP address

In the app: **DNS server**

dns_expectedstring

The answer the record must contain

In the app: **Expected answer**

Expiry warnings2 fields

ssl_expiry_alert_daysinteger · nullable

Warn this many days before certificate / domain expiry

In the app: **Certificate expiry warning**

domain_expiry_alert_daysinteger · nullable

Warn this many days before certificate / domain expiry

In the app: **Domain expiry warning**

Maintenance window4 fields

maintenance_fromstring · nullable

Recurring maintenance window, HH:MM

In the app: **From**

maintenance_tostring · nullable

Recurring maintenance window, HH:MM

In the app: **Until**

maintenance_weekdaysstring[]

Default every day

montuewedthufrisatsun

In the app: **On these days**

maintenance_timezonestring

IANA zone for the window (default UTC)

In the app: **Timezone**

Organizing9 fields

client_iduuid · nullable

Create inside a client (agency mode)

client_namestring

Batch import only (POST /monitors/batch): names resolved to ids

monitor_group_iduuid · nullable

Put the monitor into a group

monitor_group_namestring

Batch import only (POST /monitors/batch): names resolved to ids

public_namestring · nullable

Shown on status pages and client share links instead of name; up to 120 characters

pronounceable_namestring

Spoken name

metadatamap of stringmax 100 keyseach value max length 4096

On update, omitted or null leaves the stored metadata unchanged, {} removes all keys and any other object replaces them.

statestring

UP, DOWN or PAUSED (any case); empty creates the monitor running.

""UPDOWNPAUSED

creation_keyuuid · nullable

Import key: a repeat create with the same key returns the existing monitor; a key that belongs to another owner or client is refused

## Returns

A successful request returns `201` with application/json.

Response fields 1 top-level fields

| Field | Type | Description |
| --- | --- | --- |
| `data` | Monitor | |
| Monitor · 70 fields FieldTypeDescription `alert_type` string one of`status` `keyword` `keyword_absence` `expected_status_code` `auth_username` string `capture_on_incident_open` boolean `confirmation_period` integer `created_at` string (date-time) `degraded_alert_enabled` boolean `degraded_threshold_ms` integer  · nullable `domain_expiry_alert_days` integer  · nullable `escalation_policy_id` uuid  · nullable `expected_status_code` integer  · nullable `feature_pause_reasons` string[]  · nullable `follow_redirects` boolean `has_auth_password` boolean `has_request_headers` boolean `http_method` string `id` uuid `interval_seconds` integer `ip_version` string one of`""` `4` `6` `is_degraded` boolean `is_pending` boolean `keep_cookies` boolean `keyword` string `keyword_case_insensitive` boolean `maintenance_from` string  · nullable `maintenance_timezone` string `maintenance_to` string  · nullable `maintenance_weekdays` string[]  · nullable one of`mon` `tue` `wed` `thu` `fri` `sat` `sun` `monitor_type` string one of`http` `tcp` `ping` `dns` `name` string `next_check_at` string (date-time)  · nullable `notification_mode` string one of`quiet` `immediate` `notify_recovery` boolean `pronounceable_name` string `proxy_host` string `proxy_port` integer  · nullable `public_name` string `recovery_period` integer `regions` string[]  · nullable one of`eu` `us` `asia` `au` `default` `request_body` string `request_headers` any[]  · nullable Always empty: header values are never returned. has_request_headers says whether the monitor sends any. `request_timeout_s` integer `ssl_expiry_alert_days` integer  · nullable `ssl_verification` boolean `state` string one of`UP` `DOWN` `PAUSED` `url` string `user_id` uuid `badge_token` string `client_id` uuid `dns_expected` string `dns_query` string `dns_server` string `dns_type` string one of`A` `AAAA` `MX` `CNAME` `TXT` `NS` `freshness_timeout_seconds` integer `last_checked_at` string (date-time) `last_result_at` string (date-time) `last_state_change_at` string (date-time) `latency_p50_ms` integer `latency_p95_ms` integer `latency_samples_24h` integer `latest_result_successful` boolean `monitor_group_id` uuid `paused_until` string (date-time) `recent_incidents` MonitorRecentIncident[] Each item · 5 fields FieldTypeDescription `id` uuid `resolved_at` string (date-time)  · nullable `started_at` string (date-time) `status` string `resolved_by_type` string `tcp_host` string `tcp_port` integer | | |

## Errors

Only the errors this operation can return. Every error uses the [same envelope](https://upfour.io/docs/api/responses.md).

| Status | Code | Meaning |
| --- | --- | --- |
| 400 | [bad_request](https://upfour.io/docs/api/errors#code-bad_request) | The request is malformed or fails validation. |
| 403 | [check_capacity_exceeded](https://upfour.io/docs/api/errors#code-check_capacity_exceeded) | The change would push the subscription's projected monthly check load over its included budget. **When:** Creating a monitor, directly or through a client template, or changing one, when the projected cost including the change exceeds the budget; running monitors are not affected, and details carries projected_cents, included_cents and change_cents. **Fix:** Use a longer interval, fewer regions or fewer monitors, or raise the included budget. `check_capacity_exceeded` (subscriptions only): the change would raise the projected 30-day list usage of all active monitors above the included check budget. `error.details` carries `projected_cents`, `included_cents` and `change_cents`; a longer interval, fewer regions or fewer monitors fit. Running monitors and changes that lower the load are never refused. |
| 403 | [feature_disabled](https://upfour.io/docs/api/errors#code-feature_disabled) | The monitor uses a feature that is currently switched off. **When:** Saving a monitor whose configuration needs a feature the platform has disabled; the saved configuration stays as it was. **Fix:** Remove that feature from the monitor, or try again once the feature is back. |
| 404 | [not_found](https://upfour.io/docs/api/errors#code-not_found) | The requested resource does not exist or is outside the caller's scope. |
| 402 | [payment_required](https://upfour.io/docs/api/errors#code-payment_required) | The account does not have the credit or plan required for this action. |
| 403 | [permission_denied](https://upfour.io/docs/api/errors#code-permission_denied) | The credentials do not permit this action. |

Every call can also return `unauthorized` (401), `rate_limit_exceeded` (429) or `internal_error` (500 / 502). See [Responses and errors](https://upfour.io/docs/api/responses.md).

## Related

[guideWatch a website](https://upfour.io/docs/watch-a-website.md)[guideChecks and regions](https://upfour.io/docs/how/checks-and-regions.md)[guideCheck page content](https://upfour.io/docs/check-page-content.md)[guideWhen you get alerted](https://upfour.io/docs/how/when-you-get-alerted.md)

[PreviousList monitors](https://upfour.io/docs/api/monitors/list-monitors.md) [NextGet a monitor](https://upfour.io/docs/api/monitors/get-a-monitor.md)

## Request

### cURL

```bash
curl -s -X POST "https://app.upfour.io/v1/monitors" \
  -H "Authorization: Bearer $UP4_API_KEY" \
  -H "Content-Type: application/json" \
  -d '{
    "interval_seconds": 60,
    "monitor_type": "http",
    "name": "Marketing website",
    "url": "https://example.com"
  }'
```

### JavaScript

```javascript
const res = await fetch("https://app.upfour.io/v1/monitors", {
  method: "POST",
  headers: {
    Authorization: `Bearer ${process.env.UP4_API_KEY}`,
    "Content-Type": "application/json",
  },
  body: JSON.stringify({
    interval_seconds: 60,
    monitor_type: "http",
    name: "Marketing website",
    url: "https://example.com",
  }),
});
console.log(res.status, await res.json());
```

### Python

```python
import os
import requests

res = requests.post(
    "https://app.upfour.io/v1/monitors",
    headers={"Authorization": f"Bearer {os.environ['UP4_API_KEY']}"},
    json={
        "interval_seconds": 60,
        "monitor_type": "http",
        "name": "Marketing website",
        "url": "https://example.com",
    },
)
print(res.status_code, res.json())
```

### Go

```go
package main

import (
	"fmt"
	"io"
	"net/http"
	"os"
	"strings"
)

func main() {
	payload := strings.NewReader(`{
  "interval_seconds": 60,
  "monitor_type": "http",
  "name": "Marketing website",
  "url": "https://example.com"
}`)
	req, _ := http.NewRequest("POST", "https://app.upfour.io/v1/monitors", payload)
	req.Header.Set("Authorization", "Bearer "+os.Getenv("UP4_API_KEY"))
	req.Header.Set("Content-Type", "application/json")
	res, err := http.DefaultClient.Do(req)
	if err != nil {
		panic(err)
	}
	defer res.Body.Close()
	out, _ := io.ReadAll(res.Body)
	fmt.Println(res.StatusCode, string(out))
}
```

### PHP

```php
<?php
$payload = <<<'JSON'
{
  "interval_seconds": 60,
  "monitor_type": "http",
  "name": "Marketing website",
  "url": "https://example.com"
}
JSON;

$ch = curl_init("https://app.upfour.io/v1/monitors");
curl_setopt_array($ch, [
    CURLOPT_CUSTOMREQUEST => "POST",
    CURLOPT_RETURNTRANSFER => true,
    CURLOPT_HTTPHEADER => [
        "Authorization: Bearer " . getenv("UP4_API_KEY"),
        "Content-Type: application/json",
    ],
    CURLOPT_POSTFIELDS => $payload,
]);
$response = curl_exec($ch);
echo curl_getinfo($ch, CURLINFO_RESPONSE_CODE), " ", $response, "\n";
curl_close($ch);
```

## Response

### 201

```json
{
  "data": {
    "alert_type": "status",
    "auth_username": "",
    "capture_on_incident_open": false,
    "confirmation_period": 0,
    "created_at": "2026-10-27T09:00:00Z",
    "degraded_alert_enabled": false,
    "degraded_threshold_ms": null,
    "dns_type": "A",
    "domain_expiry_alert_days": null,
    "escalation_policy_id": null,
    "expected_status_code": null,
    "feature_pause_reasons": null,
    "follow_redirects": true,
    "freshness_timeout_seconds": 120,
    "has_auth_password": false,
    "has_request_headers": false,
    "http_method": "HEAD",
    "id": "00000000-0000-4000-8000-000000000001",
    "interval_seconds": 60,
    "ip_version": "",
    "is_degraded": false,
    "is_pending": true,
    "keep_cookies": false,
    "keyword": "",
    "keyword_case_insensitive": true,
    "maintenance_from": null,
    "maintenance_timezone": "UTC",
    "maintenance_to": null,
    "maintenance_weekdays": [
      "mon",
      "tue",
      "wed",
      "thu",
      "fri",
      "sat",
      "sun"
    ],
    "monitor_type": "http",
    "name": "Marketing website",
    "next_check_at": "2026-10-27T09:00:00Z",
    "notification_mode": "immediate",
    "notify_recovery": true,
    "pronounceable_name": "",
    "proxy_host": "",
    "proxy_port": null,
    "public_name": "",
    "recovery_period": 0,
    "regions": [
      "eu"
    ],
    "request_body": "",
    "request_headers": [],
    "request_timeout_s": 30,
    "ssl_expiry_alert_days": null,
    "ssl_verification": true,
    "state": "UP",
    "url": "https://example.com",
    "user_id": "00000000-0000-4000-8000-000000000002"
  }
}
```

### 400

```json
{
  "error": {
    "code": "bad_request",
    "message": "The request is malformed or fails validation.",
    "request_id": "req_…"
  }
}
```

### 402

```json
{
  "error": {
    "code": "payment_required",
    "message": "The account does not have the credit or plan required for this action.",
    "request_id": "req_…"
  }
}
```

### 403

```json
{
  "error": {
    "code": "check_capacity_exceeded",
    "message": "The change would push the subscription's projected monthly check load over its included budget.",
    "request_id": "req_…"
  }
}
```

### 404

```json
{
  "error": {
    "code": "not_found",
    "message": "The requested resource does not exist or is outside the caller's scope.",
    "request_id": "req_…"
  }
}
```
