Skip to content

List status pages

GET
/status-pages
curl --request GET \
--url https://app.tunnelhq.com/api/v1/status-pages \
--header 'X-API-Key: <X-API-Key>' \
--header 'X-Project-Id: prj_12'

The project’s status pages, drafts included. Any role in the project can call it.

Each page lives at <slug>.<domain>, for example larkspur-vpn.tunnelhq.net, and on its custom domain once that is active. host is the older address, status.tunnelhq.com/<slug>, which forwards (301) to the new one.

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

The status pages.

Media typeapplication/json
object
status_pages
required
Array<object>
object
id
required
string
name
required
string
slug
required
string
address
required

The page’s own address, without https://.

string
custom_domain
required
string | null
domain_state
required
  • none: no custom domain is set.
  • waiting: set, but its record doesn’t point to TunnelHQ yet. domain_note says what the check found.
  • issuing: the record points here and the HTTPS certificate is being made.
  • active: live, with HTTPS.
  • failed: the certificate failed. domain_note says why.
string
Allowed values: none waiting issuing active failed
published
required
boolean
services
required

Services on the page that are not hidden. A group counts once, without its members.

integer
updated_at
required
string format: date-time
host
required

The older address host. status.tunnelhq.com/<slug> forwards to the page.

string
domain
required

The domain every page has its own name under, as <slug>.<domain>.

string | null
Example
{
"status_pages": [
{
"id": "sp_12",
"name": "Larkspur VPN",
"slug": "larkspur-vpn",
"address": "larkspur-vpn.tunnelhq.net",
"custom_domain": "status.larkspur.example",
"domain_state": "active",
"published": true,
"services": 4,
"updated_at": "2026-11-07T09:41:00.000Z"
}
],
"host": "status.tunnelhq.com",
"domain": "tunnelhq.net"
}
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 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 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.

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