Skip to content

Update monitor

PUT
/servers/{id}
curl --request PUT \
--url https://app.tunnelhq.com/api/v1/servers/srv_42 \
--header 'Content-Type: application/json' \
--header 'X-API-Key: <X-API-Key>' \
--header 'X-Project-Id: prj_12' \
--data '{ "active": false }'

Changes only the fields you send. Needs a role that can edit monitors (Owner, Admin, or Manager).

  • tags replaces the monitor’s tags; [] removes them all.
  • Sending any configuration field (url, config, host, username, password, port, proto, remote_id, ip, servers, wg, wireguard_config, wireguard_ip) re-validates the configuration, with the fields you send merged over the stored ones.
  • Sending a different protocol replaces the configuration: send every field the new protocol needs.
  • Setting active to true can be refused: when the plan has no room, when the monitor’s subscription is switched off, or when the provider no longer lists the server.

Need an API key? See Getting an API key.

id
required
string

The monitor, as srv_42 or 42.

Example
srv_42
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

Every field is optional; send only what changes.

object
name
string
interval

Seconds between scheduled checks, at least the plan’s minimum.

integer
<= 86400
timeout

0 sets the default, 60.

integer
>= 10 <= 120
maxretries

0 sets the default, 2.

integer
>= 1 <= 10
active

false pauses the monitor; true resumes it.

boolean
tags

Replaces the monitor’s tags.

Array<string>
protocol

A different protocol replaces the configuration; send every field it needs.

string
Allowed values: wireguard amneziawg openvpn openconnect ikev2 vless vmess trojan shadowsocks hysteria2 tuic
url
string
<= 8192 characters
config
string
<= 102400 characters
host
string
<= 253 characters
username
string
<= 256 characters
password
string
<= 4096 characters
port
integer
proto
string
Allowed values: udp tcp
remote_id
string
<= 256 characters
ip
string
<= 64 characters
servers
Array<object>
object
wg
string
ip
string
Examples

Pause

{
"active": false
}

The monitor after the change.

Media typeapplication/json
object
id
required
string
name
required
string
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

Latency of the latest check, in milliseconds.

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
Example
{
"id": "srv_42",
"name": "fra-wg-01",
"protocol": "wireguard",
"status": "healthy",
"aggregate_status": "healthy",
"active": true,
"interval": 180,
"latency_ms": 212,
"region": "any",
"locations": [
"any"
],
"disabled_locations": [],
"regional_status": {
"any": {
"status": "up",
"last_check": "2026-09-25T14:30:02.000Z",
"ping_ms": 212
}
},
"notify_on_partial": false,
"tags": [
"production"
],
"last_checked": "2026-09-25 14:30:02"
}
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 invalid.

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": "Invalid server ID"
}

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 or the plan 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 edit this monitor"
}

The project has no such monitor, or the project doesn’t exist.

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": 404,
"message": "Server not found"
}

The monitor can’t be changed this way right now.

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": 409,
"message": "This monitor's subscription is disabled. Re-enable the subscription (PUT /api/v1/subscriptions/:id with {\"enabled\": true}) before activating its monitors."
}

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 'password' exceeds max length (4096 bytes). Got 5000 bytes."
}

The new 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
Example
{
"error": true,
"code": 422,
"message": "Invalid VPN configuration: …"
}

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"
}