Skip to content

Create monitor

POST
/servers
curl --request POST \
--url https://app.tunnelhq.com/api/v1/servers \
--header 'Content-Type: application/json' \
--header 'X-API-Key: <X-API-Key>' \
--header 'X-Project-Id: prj_12' \
--data '{ "name": "fra-wg-01", "protocol": "wireguard", "config": "[Interface]\nPrivateKey = <client-private-key>\nAddress = 10.0.0.2/32\n\n[Peer]\nPublicKey = <server-public-key>\nEndpoint = fra-wg-01.your-vpn.net:51820\nAllowedIPs = 0.0.0.0/0", "tags": [ "production" ] }'

Creates a VPN monitor or an Uptime check and queues its first check. Needs a role that can create monitors (Owner, Admin, or Manager). A VPN monitor also needs room under the plan’s tunnel limit. Uptime checks don’t count toward it while Uptime is in beta; only a plan that allows no monitors at all refuses them.

The new monitor tests from Any location (choose specific locations in the dashboard), and uses every active alert channel in the project.

The body is one of the shapes below.

  • VPN monitors: the configuration fields depend on the protocol. protocol can be left out when url starts with a known scheme (vless://, vmess://, trojan://, ss://, hysteria2://, hy2://, tuic://). Hosts in the IANA documentation ranges, such as example.com or 192.0.2.1, are refused with 422: use your own server’s address.
  • Uptime checks: send type (http, keyword, ping, port, dns, or push for a heartbeat) and that type’s fields. A field the type doesn’t have is refused with 400 and a message naming it, such as “port is for port checks.” Private and reserved addresses are refused. "type": "group" is refused with 400 “Groups are created in the dashboard for now.”
  • ssl_expiration and domain_expiration left out take the value of the project’s other checks on the same host, or for the host’s first check the default: 14 days for an https:// URL, and domain expiration off.
  • A heartbeat’s period below the plan’s shortest is refused with 403 “The shortest period on your plan is N seconds (requested M).” Its heartbeat_url is returned only to callers who can edit monitors. Anyone with the address can report the job up or down, so treat it as a secret.

Tags are saved in lowercase.

Need an API key? See Getting an API key.

X-Project-Id
required
string

The project, as prj_12 or 12. A workspace key can only reach its own workspace’s projects.

Example
prj_12
Media typeapplication/json
One of:
Website check
object
name

The check’s name.

string
interval

Seconds between checks. Refused below the shortest your plan allows. Left out, the recommended default.

integer
timeout

Seconds a request may take. See Working with checks for the choices.

integer
maxretries

Failed checks in a row before the check is down.

integer
tags
Array<string>
active

false creates it paused.

boolean
default: true
type
required
string
Allowed value: http
url
required

The address to request.

string
accepted_statuscodes

Status codes that count as up, as codes or ranges.

Array<string>
default: 200-299
ssl_expiration

Certificate: Days before it expires to alert: 1, 2, 3, 7, 14, 30, or 60. 0 or null turns it off. Website and keyword checks only. Needs an https:// URL.

integer | null
Allowed values: 0 1 2 3 7 14 30 60
domain_expiration

Domain registration: Days before it expires to alert: 1, 2, 3, 7, 14, 30, or 60. 0 or null turns it off. Website and keyword checks only.

integer | null
Allowed values: 0 1 2 3 7 14 30 60
ip_version

null for both.

string | null
Allowed values: ipv4 ipv6
Examples

WireGuard

{
"name": "fra-wg-01",
"protocol": "wireguard",
"config": "[Interface]\nPrivateKey = <client-private-key>\nAddress = 10.0.0.2/32\n\n[Peer]\nPublicKey = <server-public-key>\nEndpoint = fra-wg-01.your-vpn.net:51820\nAllowedIPs = 0.0.0.0/0",
"tags": [
"production"
]
}

The new monitor. Its first check is queued, so its status starts as unknown, or paused when it’s created with "active": false.

Media typeapplication/json
object
id
required
string
name
required
string
type
required

vpn for a tunnel, or the Uptime check’s type (push is a heartbeat).

string
Allowed values: vpn http keyword ping port dns push
protocol
required
One of:
string
Allowed values: wireguard amneziawg openvpn openconnect ikev2 vless vmess trojan shadowsocks hysteria2 tuic
status
required
  • healthy: the latest check passed. With several locations, at least one is up and none is down.
  • down: confirmed down. With several locations, at least one is down and none is up.
  • partial: several locations, some up and some down.
  • degraded: the latest check has no verdict yet (retrying, inconclusive, or a fault on TunnelHQ’s checker), or the server refused the monitor’s credentials. The dashboard shows the first case as Unknown and the second as Degraded.
  • paused: the monitor is paused.
  • unknown: the monitor hasn’t been checked yet. The dashboard shows it as Pending.
string
Allowed values: healthy down partial degraded paused unknown
aggregate_status
required

Always the same as status.

string
Allowed values: healthy down partial degraded paused unknown
active
required

false when the monitor is paused.

boolean
interval
required

Seconds between scheduled checks.

integer | null
latency_ms
required

The latest check’s time, in milliseconds. For a tunnel, one round trip through the tunnel. For an Uptime check: the response time (website and keyword), the average round trip (ping), the TCP connect time (TCP port), the query time (DNS, null unless the server answered every question), or the ping value the job sent (heartbeat). Null when that check failed or measured none.

integer | null
connect_ms

Tunnels only: check rows don’t have it. How long the tunnel took to come up in its latest check, in milliseconds. Null when that check failed or measured none. For VLESS, VMess, Trojan, Shadowsocks, Hysteria2, and TUIC, it’s the time to first contact with the server.

integer | null
region
required

Where the latest check ran from, as any or a lowercase country code. null before the first check.

string | null
locations
required

The locations the monitor tests from. ["any"] unless specific locations were chosen.

Array<string>
disabled_locations
required

Locations that are paused. Never includes any.

Array<string>
regional_status
required

The latest result per location, keyed by location code. {} before the first check.

object
key
additional properties
object
status
required

pending means no verdict yet; degraded, that the server refused the credentials; unavailable, that no checker is available there, so the location is left out of the status.

string
Allowed values: up down pending degraded unavailable unknown
last_check
required

When that location last checked, in ISO 8601 UTC. Empty if unknown.

string
ping_ms
required
number | null
error_category

Why the check failed, when it did.

string
proxy_used

Present only when the check ran through a relay.

boolean
notify_on_partial
required

Whether a partial outage alerts.

boolean
tags
required

Tag names.

Array<string>
last_checked
required

When the latest check ran, in UTC, as YYYY-MM-DD HH:MM:SS with no time zone marker.

string | null
subscription

Present only on monitors imported from a subscription URL. One monitor stands for every config in the URL that points at the same server.

object
subscription_url_id
string | null
server_address
string | null
server_name
string | null
config_count
integer
healthy_count
integer
configs

The latest result for each config.

Array<object>
object
tag

The config’s name in the subscription.

string
type

The config’s protocol, such as vless.

string
server
string
port
integer | string
success
boolean
total_ms
number
error
string
error_category
string
public_ip

The address traffic left from, when the check passed.

string
engine_used
string
region_results

The same fields per location.

object
url

Uptime checks only: the address a website or keyword check requests.

string | null
host

Uptime checks only: the host a ping or TCP port check reaches, or the DNS server a DNS check asks.

string | null
domain

Uptime checks only: the domain a DNS check asks about. null for other checks.

string | null
port

Uptime checks only: the TCP port check’s port, or 53 for DNS.

integer | null
keyword

Uptime checks only: the text a keyword check looks for, or the text a DNS answer must contain.

string | null
invert_keyword

Uptime checks only: true when a keyword check alerts on finding the keyword.

boolean
accepted_statuscodes

Uptime checks only: status codes that count as up, such as 200-299.

Array<string>
certificate

Uptime checks only: the certificate the latest check saw, or null before one was seen.

object
valid
boolean
days_remaining
integer
ssl_expiration

Uptime checks only: days before the certificate expires to alert. null is off.

integer | null
domain_expiration

Uptime checks only: days before the domain expires to alert. null is off.

integer | null
ip_version

Uptime checks only: ipv4 or ipv6, or null for both.

string | null
Allowed values: ipv4 ipv6
period

Heartbeats only: the longest time between two calls from the job, in seconds. interval shows the same value.

integer
grace

Heartbeats only: how late a call can be before the heartbeat is down, in seconds.

integer
heartbeat_url

Heartbeats only, and only from Create, Get, and Update monitor, to callers who can edit monitors: the address the job calls. Treat it as a secret.

string
Examples

VPN monitor

{
"id": "srv_43",
"name": "fra-wg-01",
"type": "vpn",
"protocol": "wireguard",
"status": "unknown",
"aggregate_status": "unknown",
"active": true,
"interval": 180,
"latency_ms": null,
"connect_ms": null,
"region": null,
"locations": [
"any"
],
"disabled_locations": [],
"regional_status": {},
"notify_on_partial": false,
"tags": [
"production"
],
"last_checked": null
}
X-RateLimit-Limit
integer

Requests the workspace’s plan allows per minute.

X-RateLimit-Remaining
integer

Requests left in the current minute.

X-RateLimit-Reset
integer

Unix time, in seconds, when the current minute ends.

A field is missing or invalid, or the X-Project-Id header is.

Media typeapplication/json
object
error
required
boolean
code
required

The HTTP status, repeated.

integer
message
required

What went wrong.

string
details

More detail, on some errors.

retry_after

Seconds to wait, on the 429 for wrong keys.

integer
Examples
{
"error": true,
"code": 400,
"message": "Missing or invalid name"
}

The API key is missing, wrong, expired, switched off, or no longer valid.

Media typeapplication/json
object
error
required
boolean
code
required

The HTTP status, repeated.

integer
message
required

What went wrong.

string
details

More detail, on some errors.

retry_after

Seconds to wait, on the 429 for wrong keys.

integer
Examples
{
"error": true,
"code": 401,
"message": "API key is required. Provide X-API-Key header or Authorization: Bearer <key>"
}

The role, the plan, or the key doesn’t allow it.

Media typeapplication/json
object
error
required
boolean
code
required

The HTTP status, repeated.

integer
message
required

What went wrong.

string
details

More detail, on some errors.

retry_after

Seconds to wait, on the 429 for wrong keys.

integer
Examples
{
"error": true,
"code": 403,
"message": "You do not have permission to create monitors in this project"
}

No active project has this ID.

Media typeapplication/json
object
error
required
boolean
code
required

The HTTP status, repeated.

integer
message
required

What went wrong.

string
details

More detail, on some errors.

retry_after

Seconds to wait, on the 429 for wrong keys.

integer
Example
{
"error": true,
"code": 404,
"message": "Project not found or inactive"
}

A configuration field is longer than its limit.

Media typeapplication/json
object
error
required
boolean
code
required

The HTTP status, repeated.

integer
message
required

What went wrong.

string
details

More detail, on some errors.

retry_after

Seconds to wait, on the 429 for wrong keys.

integer
Example
{
"error": true,
"code": 413,
"message": "Field 'config' exceeds max length (102400 bytes). Got 150000 bytes."
}

The protocol or its configuration can’t be used.

Media typeapplication/json
object
error
required
boolean
code
required

The HTTP status, repeated.

integer
message
required

What went wrong.

string
details

More detail, on some errors.

retry_after

Seconds to wait, on the 429 for wrong keys.

integer
Examples
{
"error": true,
"code": 422,
"message": "Unsupported protocol: pptp. Supported: wireguard, amneziawg, openvpn, openconnect, ikev2, vless, vmess, trojan, shadowsocks, hysteria2, tuic"
}

A usage limit was reached, or this IP address made too many requests with a wrong key.

Usage limits are per workspace and plan: per minute (Retry-After: 60), per day (Retry-After: 3600), and per month (no Retry-After). They use their own body shape. An IP address may send 10 wrong keys a minute; one more is allowed every 6 seconds. While that allowance is used up, every request from the address is refused, even one with a valid key.

Media typeapplication/json
One of:
object
error
required
string
Allowed values: Rate limit exceeded Daily limit exceeded Monthly limit exceeded
message
required
string
retry_after

Seconds to wait. Absent for the monthly limit.

integer
Examples
{
"error": "Rate limit exceeded",
"message": "Per-minute limit of 60 requests exceeded",
"retry_after": 60
}
Retry-After
integer

Seconds to wait. 60 for the per-minute limit, 3600 for the daily limit, 6 after wrong keys. Absent for the monthly limit.

Something failed on TunnelHQ’s side.

Media typeapplication/json
object
error
required
boolean
code
required

The HTTP status, repeated.

integer
message
required

What went wrong.

string
details

More detail, on some errors.

retry_after

Seconds to wait, on the 429 for wrong keys.

integer
Example
{
"error": true,
"code": 500,
"message": "Internal server error"
}