Skip to content

Run a test

POST
/test
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.

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
object
server_id
required

The monitor, as srv_42 or 42.

string | integer
region

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.

string
default: any /^([a-z]{2}|any)$/
region_fallback

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.

string
default: any
Allowed values: any fail
Example
{
"server_id": "srv_42",
"region": "pk"
}

The test finished within 75 seconds.

Media typeapplication/json
object
test_id
required
string
server_id
required
string
status
required

degraded means the test had no verdict.

string
Allowed values: healthy down degraded
latency_ms
required
integer | null
error_message
required
string | null
tested_at
required

When it finished, in UTC, as YYYY-MM-DD HH:MM:SS with no time zone marker.

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

The test is still running after 75 seconds.

Media typeapplication/json
object
test_id
required
string
server_id
required
string
status
required
string
Allowed value: pending
tested_at
required

When the test started, in ISO 8601 UTC.

string
Example
{
"test_id": "manual-42-1790346600000",
"server_id": "srv_42",
"status": "pending",
"tested_at": "2026-09-25T14:30:00.000Z"
}
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.

server_id or region is 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 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>"
}

Testing from a specific location needs the Pro plan or above.

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": 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.

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": "This action requires edit permission — your role is read-only"
}

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

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