List servers
const url = 'https://dev.tunnelhq.com/api/v1/hosts?status=online&monitor_id=srv_81&sort=status&order=asc';const options = {method: 'GET', headers: {'X-Project-Id': 'prj_12', 'X-API-Key': '<X-API-Key>'}};
try { const response = await fetch(url, options); const data = await response.json(); console.log(data);} catch (error) { console.error(error);}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.
Authorizations
Section titled “Authorizations”Parameters
Section titled “ Parameters ”Header Parameters
Section titled “Header Parameters”The project, as prj_12 or 12. A workspace key can only reach its own workspace’s projects.
Example
prj_12Query Parameters
Section titled “Query Parameters”Only servers in this status.
Only servers in this group (case doesn’t matter).
Only servers with this tag, or with any of several separated by commas.
Part of a name, hostname or address.
Only the servers this tunnel or check is linked to.
Example
srv_81status puts offline servers first. A server with no figure goes last either way.
Responses
Section titled “ Responses ”The servers and the counts.
object
A server as the list shows it. users, protocols and services are null on a server with no VPN services.
object
The name a person gave it, else its hostname.
Online: a report in the last 60 seconds. Stale: 60 to 90 seconds. Offline: more than 90 seconds, or never.
The agent version TunnelHQ hands out now.
A newer release than this server’s agent is out.
The agent has the updater, so TunnelHQ can update it. False for one installed before updates came, or with --no-updates.
TunnelHQ updates this agent by itself, a day after each release.
The version on its way
How the last update went, as the agent reported it.
object
The country of the server’s own address, as the agent reports it (agent 1.4.0 and later).
0 to 100, where 0 is best: 0 means no sign of trouble for users, 100 that users are suffering now.
0 to 100: how little room is left for more users (100: none).
0 to 100: how busy the server is overall.
Users per VPN protocol.
The VPN services it watches.
Web servers, databases and the like, with their state (agent 1.6.0 and later).
Its linked tunnels and checks that you can see.
object
How it was linked: by hand, by address, or by hostname.
Every server of the project by status, before the filters.
object
Examples
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 }}Headers
Section titled “Headers”Requests the workspace’s plan allows per minute.
Requests left in the current minute.
Unix time, in seconds, when the current minute ends.
A filter has a value it doesn’t take.
object
The HTTP status, repeated.
What went wrong.
More detail, on some errors.
Seconds to wait, on the 429 for wrong keys.
Examples
{ "error": true, "code": 400, "message": "status must be online, stale or offline."}{ "error": true, "code": 400, "message": "sort must be one of status, name, cpu, memory, disk, last_report, users."}{ "error": true, "code": 400, "message": "order must be asc or desc."}{ "error": true, "code": 400, "message": "monitor_id must be a tunnel or check id, such as srv_81."}The API key is missing, wrong, expired, switched off, or no longer valid.
object
The HTTP status, repeated.
What went wrong.
More detail, on some errors.
Seconds to wait, on the 429 for wrong keys.
Examples
{ "error": true, "code": 401, "message": "API key is required. Provide X-API-Key header or Authorization: Bearer <key>"}{ "error": true, "code": 401, "message": "Invalid or expired API key"}{ "error": true, "code": 401, "message": "API key has expired"}{ "error": true, "code": 401, "message": "API key owner account is inactive"}{ "error": true, "code": 401, "message": "This API key's workspace access has been removed"}No active project has this ID.
object
The HTTP status, repeated.
What went wrong.
More detail, on some errors.
Seconds to wait, on the 429 for wrong keys.
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.
object
Seconds to wait. Absent for the monthly limit.
object
The HTTP status, repeated.
What went wrong.
More detail, on some errors.
Seconds to wait, on the 429 for wrong keys.
Examples
{ "error": "Rate limit exceeded", "message": "Per-minute limit of 60 requests exceeded", "retry_after": 60}{ "error": "Daily limit exceeded", "message": "Daily limit of 25000 requests exceeded", "retry_after": 3600}{ "error": "Monthly limit exceeded", "message": "Monthly limit of 250000 requests exceeded"}{ "error": true, "code": 429, "message": "Too many failed API key attempts from this address. Wait a few seconds and try again.", "retry_after": 6}Headers
Section titled “Headers”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.
object
The HTTP status, repeated.
What went wrong.
More detail, on some errors.
Seconds to wait, on the 429 for wrong keys.
Example
{ "error": true, "code": 500, "message": "Internal server error"}