Run a test
const url = 'https://app.tunnelhq.com/api/v1/test';const options = { method: 'POST', headers: { 'X-Project-Id': 'prj_12', 'X-API-Key': '<X-API-Key>', 'Content-Type': 'application/json' }, body: '{"server_id":"srv_42","region":"pk"}'};
try { const response = await fetch(url, options); const data = await response.json(); console.log(data);} catch (error) { console.error(error);}curl --request POST \ --url https://app.tunnelhq.com/api/v1/test \ --header 'Content-Type: application/json' \ --header 'X-API-Key: <X-API-Key>' \ --header 'X-Project-Id: prj_12' \ --data '{ "server_id": "srv_42", "region": "pk" }'Tests one monitor now, outside its schedule, and waits up to 75 seconds for the result. Needs a role that can edit monitors (Owner, Admin, or Manager) and uses one on-demand test from the plan’s monthly quota.
If the result arrives in time, the response is 200 with it. Otherwise it’s 202 with
status: "pending": the test keeps running, and its result appears in the monitor’s
results (List a monitor’s results) and status. test_id can’t be
looked up through the API.
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_12Request Bodyrequired
Section titled “Request Bodyrequired”object
The monitor, as srv_42 or 42.
Where to test from: any, or a lowercase country code such as pk. A country needs the
Pro plan or above. List regions shows the
locations available now.
What happens when the location has no checker available. any runs the test from the
general pool instead, and the response doesn’t say so; fail refuses it. Any other value
counts as any.
Example
{ "server_id": "srv_42", "region": "pk"}Responses
Section titled “ Responses ”The test finished within 75 seconds.
object
degraded means the test had no verdict.
When it finished, in UTC, as YYYY-MM-DD HH:MM:SS with no time zone marker.
Example
{ "test_id": "manual-42-1790346600000", "server_id": "srv_42", "status": "healthy", "latency_ms": 208, "error_message": null, "tested_at": "2026-09-25 14:30:41"}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.
The test is still running after 75 seconds.
object
When the test started, in ISO 8601 UTC.
Example
{ "test_id": "manual-42-1790346600000", "server_id": "srv_42", "status": "pending", "tested_at": "2026-09-25T14:30:00.000Z"}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.
server_id or region is invalid, or the X-Project-Id header is.
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": "Missing or invalid server_id"}{ "error": true, "code": 400, "message": "Invalid region: \"germany\"."}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"}Testing from a specific location needs the Pro plan or above.
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": 402, "message": "Region targeting requires the Pro plan or above."}The role or the quota doesn’t allow it, or the key can’t reach the project.
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": 403, "message": "This action requires edit permission — your role is read-only"}{ "error": true, "code": 403, "message": "Monthly on-demand test limit reached (50/50)"}{ "error": true, "code": 403, "message": "On-demand tests are not available on your plan"}The project has no such monitor, or the project doesn’t exist.
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": 404, "message": "Server not found"}{ "error": true, "code": 404, "message": "Project not found or inactive"}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"}