Skip to content

Update a status page

PATCH
/status-pages/{id}
curl --request PATCH \
--url https://dev.tunnelhq.com/api/v1/status-pages/sp_12 \
--header 'Content-Type: application/json' \
--header 'X-API-Key: <X-API-Key>' \
--header 'X-Project-Id: prj_12' \
--data '{ "website_url": "https://larkspur.example", "brand_color": "#3b6fd4", "theme": "auto" }'

Changes the fields you send; the rest stay as they are. An unknown field is refused. It needs a role that can edit the project (Owner, Admin and Manager have it by default).

On a published page, changes to what it shows wait for a publish (changes_pending). search_visible, auto_incidents, monitoring_operational, templates and password take effect at once. A new slug moves the page: the old address stops answering.

Pictures (logo, logo_dark, favicon, share_image) are sent as data URIs, and null removes one. Or send a link instead (logo_url, logo_dark_url, favicon_url, share_image_url): the server fetches the picture and holds it to the same types and sizes. A link to a private address, or to one of TunnelHQ’s own, is not fetched. Send a picture or its link, not both. To take them from the page’s website, use Get the logo and icons from the website. A request body can be at most 1 MB.

logo_with_name is whether the page’s name shows beside the logo. null leaves it to the logo’s shape: a square mark gets the name, a wide logo that spells it does not. A new logo hands it back to the logo’s shape, unless the same call sends logo_with_name.

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
Media typeapplication/json
object
name
string
<= 100 characters
slug

A new address. The old one stops answering.

string
website_url

Your website, linked from the logo. http:// or https://.

string | null
contact_url

Get in touch: an https:// page or a mailto: address.

string | null
description

A line under the headline; also the text of search results and shared links.

string | null
<= 300 characters
page_title

The browser tab’s title. Empty is <name> status.

string | null
<= 100 characters
brand_color

Six hex digits as #rrggbb, for buttons and links. null for the default.

string | null
theme

auto follows each visitor’s device.

string
Allowed values: auto light dark
header
string
Allowed values: logo site
layout
string
Allowed values: bars folded
show_past_incidents
boolean
logo_height
integer
Allowed values: 24 32 40 48
logo_with_name

true or false to choose; null leaves it to the logo’s shape.

boolean | null
language
string
Allowed values: en fr de es pt-br nl pl sv ru tr fa ar zh-cn
languages
Array<string>
Allowed values: en fr de es pt-br nl pl sv ru tr fa ar zh-cn
search_visible

false asks search engines not to list the page. It is not privacy.

boolean
auto_incidents

Open an incident by itself when a service on the page goes down

boolean
monitoring_operational

While an incident is Monitoring, its services show what their checks show.

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
logo

A data URI: PNG, JPEG, WebP or SVG, 300 KB at most.

string | null
logo_dark

For the dark theme. A data URI: PNG, JPEG, WebP or SVG, 300 KB at most.

string | null
favicon

A data URI: PNG, SVG, ICO, GIF or JPEG, 50 KB at most.

string | null
share_image

A data URI: PNG or JPEG, 500 KB at most, best at 1200 by 630.

string | null
logo_url

A link to the logo; the server fetches it. Instead of logo.

string format: uri
logo_dark_url

A link to the logo for the dark theme. Instead of logo_dark.

string format: uri
favicon_url

A link to the favicon. Instead of favicon.

string format: uri
share_image_url

A link to the share image. Instead of share_image.

string format: uri
password

8 to 72 bytes makes the page private. null or "" makes it public again.

string | null
>= 8 characters
Examples

Colours and website

{
"website_url": "https://larkspur.example",
"brand_color": "#3b6fd4",
"theme": "auto"
}

The updated page.

Media typeapplication/json
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
Exampledefault

A draft status page

{
"id": "sp_12",
"name": "Larkspur VPN",
"slug": "larkspur-vpn",
"address": "larkspur-vpn.tunnelhq.net",
"url": "https://larkspur-vpn.tunnelhq.net",
"published": false,
"published_at": null,
"changes_pending": false,
"custom_domain": "status.larkspur.example",
"domain_state": "waiting",
"domain_note": null,
"dns_records": [
{
"type": "CNAME",
"name": "status",
"value": "cname.tunnelhq.net"
}
],
"private": false,
"website_url": null,
"contact_url": null,
"description": null,
"page_title": null,
"brand_color": null,
"theme": "auto",
"header": "site",
"layout": "bars",
"show_past_incidents": true,
"logo_height": 32,
"logo_with_name": false,
"language": "en",
"languages": [],
"search_visible": true,
"auto_incidents": true,
"monitoring_operational": false,
"templates": {
"investigating": "",
"identified": "",
"monitoring": "",
"resolved": ""
},
"has_logo": false,
"has_dark_logo": false,
"has_favicon": false,
"has_share_image": false,
"service_count": 2,
"service_limit": 400,
"sections": [
{
"id": 3,
"name": "",
"services": [
{
"id": "sps_31",
"monitor_id": "srv_40",
"name": "Europe",
"hidden": false,
"monitor_name": "Europe",
"type": "group",
"protocol": null,
"services": [
{
"id": "sps_32",
"monitor_id": "srv_42",
"name": "Frankfurt 1",
"hidden": false,
"monitor_name": "Frankfurt 1 WireGuard",
"type": "vpn",
"protocol": "wireguard",
"services": []
},
{
"id": "sps_33",
"monitor_id": "srv_43",
"name": "Frankfurt 1",
"hidden": false,
"monitor_name": "Frankfurt 1 OpenVPN",
"type": "vpn",
"protocol": "openvpn",
"services": []
}
]
},
{
"id": "sps_34",
"monitor_id": "srv_80",
"name": "larkspur.example",
"hidden": false,
"monitor_name": "larkspur.example",
"type": "http",
"protocol": null,
"services": []
}
]
}
]
}
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.

A field is unknown or its value isn’t allowed.

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": "Unknown field: x. The fields are name, slug, website_url, contact_url, description, page_title, brand_color, theme, header, layout, show_past_incidents, logo_height, language, languages, search_visible, auto_incidents, monitoring_operational, templates, logo, logo_dark, favicon, share_image, password, logo_with_name, logo_url, logo_dark_url, favicon_url, share_image_url."
}

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

The slug is taken by another status page.

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": 409,
"message": "That address is taken. Choose another."
}

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