Skip to content

List servers

GET
/hosts
curl --request GET \
--url 'https://dev.tunnelhq.com/api/v1/hosts?status=online&monitor_id=srv_81&sort=status&order=asc' \
--header 'X-API-Key: <X-API-Key>' \
--header 'X-Project-Id: prj_12'

The project’s servers, the same ones as the dashboard’s Servers page, with how many are in each status. Any role in the project can call it. A server lists only the tunnels and checks you can see.

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
status
string
Allowed values: online stale offline

Only servers in this status.

group
string

Only servers in this group (case doesn’t matter).

tag
string

Only servers with this tag, or with any of several separated by commas.

q
string

Part of a name, hostname or address.

monitor_id
string

Only the servers this tunnel or check is linked to.

Example
srv_81
sort
string
default: status
Allowed values: status name cpu memory disk last_report users

status puts offline servers first. A server with no figure goes last either way.

order
string
default: asc
Allowed values: asc desc

The servers and the counts.

Media typeapplication/json
object
hosts
required
Array<object>

A server as the list shows it. users, protocols and services are null on a server with no VPN services.

object
id
string
name

The name a person gave it, else its hostname.

string
hostname
string | null
group
string | null
status

Online: a report in the last 60 seconds. Stale: 60 to 90 seconds. Offline: more than 90 seconds, or never.

string
Allowed values: online stale offline
os
string | null
agent_version
string | null
agent_latest

The agent version TunnelHQ hands out now.

string | null
agent_update

A newer release than this server’s agent is out.

boolean
agent_can_update

The agent has the updater, so TunnelHQ can update it. False for one installed before updates came, or with --no-updates.

boolean
agent_auto_update

TunnelHQ updates this agent by itself, a day after each release.

boolean
agent_updating

The version on its way

string | null
agent_update_result

How the last update went, as the agent reported it.

object
version
string
from
string
ok
boolean
message
string
at
string
public_ip
string | null
public_ip6
string | null
country

The country of the server’s own address, as the agent reports it (agent 1.4.0 and later).

string | null
last_report_at
string | null format: date-time
seconds_since_report
integer | null
cpu_percent
number | null
memory_percent
number | null
disk_percent
number | null
load_1
number | null
health_score

0 to 100, where 0 is best: 0 means no sign of trouble for users, 100 that users are suffering now.

number | null
balance_score

0 to 100: how little room is left for more users (100: none).

number | null
load_score

0 to 100: how busy the server is overall.

number | null
packet_drop_rate
number | null
users
integer | null
protocols

Users per VPN protocol.

object | null
services

The VPN services it watches.

object | null
hosting_services

Web servers, databases and the like, with their state (agent 1.6.0 and later).

object | null
monitors

Its linked tunnels and checks that you can see.

Array<object>
object
id
string
name
string
family
string
via

How it was linked: by hand, by address, or by hostname.

string
Allowed values: manual address hostname
open_incidents
integer
tags
Array<string>
counts
required

Every server of the project by status, before the filters.

object
all
integer
online
integer
stale
integer
offline
integer
Examples
Exampledefault

Two servers

{
"hosts": [
{
"id": "hst_9",
"name": "Paris 1",
"hostname": "par-1",
"group": "Europe",
"status": "offline",
"os": "Ubuntu 24.04",
"agent_version": "1.7.0",
"public_ip": "203.0.113.20",
"public_ip6": null,
"country": "FR",
"last_report_at": "2026-11-09T10:31:02Z",
"seconds_since_report": 640,
"cpu_percent": 12,
"memory_percent": 55,
"disk_percent": 40,
"load_1": 0.6,
"health_score": 4,
"balance_score": 20,
"load_score": 15,
"packet_drop_rate": 0,
"users": 0,
"protocols": {
"openconnect": 0
},
"services": {
"ocserv": "active"
},
"hosting_services": null,
"monitors": [
{
"id": "srv_82",
"name": "Paris OpenConnect",
"family": "tunnel",
"via": "hostname"
}
],
"open_incidents": 1,
"tags": [
"vpn"
]
},
{
"id": "hst_7",
"name": "London",
"hostname": "lon-1",
"group": "Europe",
"status": "online",
"os": "Ubuntu 24.04",
"agent_version": "1.7.0",
"public_ip": "203.0.113.11",
"public_ip6": null,
"country": "GB",
"last_report_at": "2026-11-09T10:41:42Z",
"seconds_since_report": 8,
"cpu_percent": 3,
"memory_percent": 41,
"disk_percent": 18,
"load_1": 0.2,
"health_score": 3,
"balance_score": 8,
"load_score": 9,
"packet_drop_rate": 0,
"users": 19,
"protocols": {
"xray": 19
},
"services": {
"xray": "active"
},
"hosting_services": null,
"monitors": [
{
"id": "srv_81",
"name": "London xray",
"family": "tunnel",
"via": "address"
}
],
"open_incidents": 0,
"tags": [
"vpn",
"europe"
]
}
],
"counts": {
"all": 2,
"online": 1,
"stale": 0,
"offline": 1
}
}
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 filter has a value it doesn’t take.

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": "status must be online, stale or offline."
}

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

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

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