Skip to content

Check a custom domain

POST
/status-pages/{id}/domain/check
curl --request POST \
--url https://app.tunnelhq.com/api/v1/status-pages/sp_12/domain/check \
--header 'X-API-Key: <X-API-Key>' \
--header 'X-Project-Id: prj_12'

Looks the custom domain up now. Once it points to TunnelHQ, the check also gets its HTTPS certificate and answers when that is done or has failed. check holds the state (a domain_state value) and a note that says what to change, and status_page is the page with its updated domain_state. When the domain answers through Cloudflare’s proxy, the note says so by name. After 4 failed HTTPS attempts for a domain in an hour, the check stops trying for that hour, because Let’s Encrypt refuses a domain for a while after 5. It needs a role that can edit the project (Owner, Admin and Manager have it by default).

Need an API key? See Getting an API key.

id
required
string

The page’s id (sp_12), from List status pages. The bare number (12) works too.

Example
sp_12
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 check and the page.

Media typeapplication/json
object
check
required
object
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
note
required

What the check found

string | null
status_page
required
object
id
required
string
name
required
string
slug
required
string
address
required

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

string
url
required

Where visitors find the page. It uses the custom domain only when the page is published and domain_state is active.

string
published
required
boolean
published_at
required
string | null format: date-time
changes_pending
required

A published page has changes that wait for a publish.

boolean
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
domain_note
required

What the last check found

string | null
dns_records
required

The record the custom domain needs, one CNAME to cname.tunnelhq.net; empty without a custom domain. For a bare domain it’s named @: add it as your DNS provider’s CNAME flattening, ALIAS or ANAME record.

Array<object>

The record a custom domain needs, named as DNS providers name it: relative to your zone, status for status.example.com and @ for example.com itself.

object
type
required
string
Allowed values: CNAME
name
required
string
value
required
string
private
required

Whether the page is behind a password.

boolean
website_url
string | null
contact_url
string | null
description
string | null
page_title
string | null
brand_color

Lowercase #rrggbb, or null.

string | null
/^#[0-9a-f]{6}$/
theme
string
Allowed values: auto light dark
header

logo: the logo and Subscribe. site: with Status, History and Maintenance tabs too.

string
Allowed values: logo site
layout

bars: 90 days of bars under each service. folded: one line each, bars on a click.

string
Allowed values: bars folded
show_past_incidents
boolean
logo_height
integer
Allowed values: 24 32 40 48
logo_with_name
required

Whether the name shows beside the logo now: your choice, or until you make one, the logo’s shape.

boolean
language
string
Allowed values: en fr de es pt-br nl pl sv ru tr fa ar zh-cn
languages

Other languages visitors can choose.

Array<string>
Allowed values: en fr de es pt-br nl pl sv ru tr fa ar zh-cn
search_visible
boolean
auto_incidents
boolean
monitoring_operational
boolean
templates

The text a new update starts with, for each status. Sending it replaces the whole set; a status left out is empty.

object
investigating
string
<= 1000 characters
identified
string
<= 1000 characters
monitoring
string
<= 1000 characters
resolved
string
<= 1000 characters
has_logo
boolean
has_dark_logo
boolean
has_favicon
boolean
has_share_image
boolean
service_count
required

Services as visitors see them, hidden ones included: a server’s tunnels on one host count once, a group counts its members.

integer
service_limit
required
integer
sections
required
Array<object>
object
id
required
integer
name
required

The section’s heading; empty for none.

string
services
required
Array<object>
object
id
required
string
monitor_id
required
string
name
required

The name shown on the page.

string
hidden
required

On the page’s setup, but not shown to visitors.

boolean
monitor_name
required

The monitor’s own name.

string
type
required

The monitor’s type, such as vpn, http or group.

string
protocol
required

A tunnel’s protocol, such as wireguard; null for other monitors.

string | null
services
required

A group’s members, one level deep. Empty for anything else.

Array<object>
object
key
additional properties
any
Examples

No record yet

{
"check": {
"state": "waiting",
"note": "No record found for status.larkspur.example yet. DNS changes can take a few minutes."
},
"status_page": {
"id": "sp_12",
"name": "Larkspur VPN",
"custom_domain": "status.larkspur.example",
"domain_state": "waiting",
"domain_note": "No record found for status.larkspur.example yet. DNS changes can take a few minutes."
}
}
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 page has no custom domain to check.

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": 400,
"message": "Add a domain first."
}

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

The role can’t edit 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
Example
{
"error": true,
"code": 403,
"message": "Changing status pages needs a role that can edit this project."
}

The project has no status page with 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": "No such status page."
}

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