Status Pages
Create and configure status pages, post status reports and updates that subscribers are e-mailed about, manage subscribers — and read any public page as JSON.
Paths are relative to https://api.zharp.io/api/v1. Reading needs a read key; posting and updating status reports needs write, like incidents; changing a page itself needs admin. Every change needs the resources:manage permission. The public endpoints need no key at all. See Authentication.
/status-pagesList pages, with their live statusGET/status-pages/:idOne page's settingsPOST/status-pagesCreate a pagePUT/status-pages/:idChange a pageDELETE/status-pages/:idDelete a pagePOST/status-pages/logoUpload a logoPOST/status-pages/:id/domain/verifyCheck a custom domain's DNSGET/status-pages/:id/announcementsList reports and announcementsPOST/status-pages/:id/announcementsPost a report or announcementPOST/status-pages/:id/announcements/:announcementId/updatesPost an update to a reportPUT/status-pages/:id/announcements/:announcementIdEdit a reportDELETE/status-pages/:id/announcements/:announcementIdDelete a reportGET/status-pages/:id/maintenanceMaintenance shown on the pageGET/status-pages/:id/subscribersList subscribersPOST/status-pages/:id/subscribers/importInvite subscribers in bulkDELETE/status-pages/:id/subscribers/:subscriberIdRemove a subscriberGET/status-pages/public/:slugA public page as JSONGET/status-pages/public/:slug/historyIts past eventsPOST/status-pages/public/:slug/subscribeSubscribe an e-mail addressPOST/status-pages/public/:slug/unlockUnlock a password-protected pageStatuses
A page shows each monitor as one of these, and one headline for the whole page:
| Service status | When |
|---|---|
maintenance | In a maintenance window. |
down | The monitor is down. |
degraded | Named in an open status report. |
operational | The monitor is up. |
paused | The monitor is paused. |
unknown | Not checked yet. |
| overallStatus | When |
|---|---|
major_outage | Every service on the page is down. |
partial_outage | Some are down. |
degraded | None down, but one is degraded, a status report is open, or an incident is ongoing. |
maintenance | None of the above, and one is in maintenance. |
operational | Everything else — a page with no monitors too. |
Status reports move through INVESTIGATING, IDENTIFIED, MONITORING and RESOLVED. An announcement is a report without a status — a notice, not an incident.
Pages
/status-pagesoverallStatus as visitors see it right now.curl "https://api.zharp.io/api/v1/status-pages" \ -H "Authorization: Bearer $ZHARP_API_KEY"
{
"message": "Status pages retrieved successfully",
"data": [
{
"id": 12,
"workspaceId": 3,
"slug": "acme",
"title": "Acme Status",
"description": null,
"status": "PUBLIC",
"domain": "status.acme.com",
"domainStatus": "VERIFIED",
"themeConfig": { "design": "Modern", … },
"createdAt": "2026-09-01T10:00:00.000Z",
"updatedAt": "2026-10-20T14:12:00.000Z",
"_count": { "monitors": 2 },
"monitors": [{ "monitorId": 41, "monitor": { "id": 41, "name": "API", "status": "UP" } }],
"hasPassword": false,
"overallStatus": "operational"
}
]
}/status-pages/:iddomainSetup says what to put in DNS: a CNAME to cnameTarget, or — for an apex domain such as acme.com, which can't hold a CNAME — A/AAAA records for addresses.
idcurl "https://api.zharp.io/api/v1/status-pages/12" \ -H "Authorization: Bearer $ZHARP_API_KEY"
{
"message": "…",
"data": {
"id": 12,
"slug": "acme",
"title": "Acme Status",
"status": "PUBLIC",
"domain": "status.acme.com",
"domainStatus": "VERIFIED",
"domainCheckedAt": "2026-10-26T09:00:00.000Z",
"domainReason": null,
"themeConfig": { … },
"monitors": [{ "statusPageId": 12, "monitorId": 41, "order": 0, "monitor": { "id": 41, "name": "API", "type": "HTTP", "status": "UP", … } }],
"hasPassword": false,
"domainSetup": { "cnameTarget": "statuspage.zharp.io", "addresses": ["203.0.113.10"], "apex": false },
…
}
}404No such page in this workspace.
/status-pagesdomainStatus.titleslughttps://SLUG.zharp.io. A few words are reserved: www, app, api, docs, status, admin and the like.descriptionstatusPUBLIC (default), PRIVATE — only your team, signed in — or PASSWORD_PROTECTED. Switching away from PASSWORD_PROTECTED forgets the password.passwordPASSWORD_PROTECTED. Stored hashed; never returned.domainstatus.acme.com. Point a CNAME at statuspage.zharp.io; the page is served there once DNS checks out (see verify). "" or null removes it.monitorIdsthemeConfiglogothemeConfig sets none.curl -X POST "https://api.zharp.io/api/v1/status-pages" \
-H "Authorization: Bearer $ZHARP_API_KEY" \
-H "Content-Type: application/json" \
-d '{
"title": "Acme Status",
"slug": "acme",
"monitorIds": [
41,
42
],
"themeConfig": {
"design": "Modern",
"colorTheme": "Auto",
"sections": [
{
"name": "Core",
"resources": [
{
"monitorId": 41,
"name": "API"
},
{
"monitorId": 42,
"name": "Website"
}
]
}
]
}
}'{
"message": "Status page created successfully",
"data": {
"id": 12,
"workspaceId": 3,
"slug": "acme",
"title": "Acme Status",
"status": "PUBLIC",
"domain": null,
"domainStatus": null,
"themeConfig": { … },
"monitors": [{ "statusPageId": 12, "monitorId": 41, "order": 0 }, { "statusPageId": 12, "monitorId": 42, "order": 1 }],
"hasPassword": false,
…
}
}400A field fails validation, a reserved slug, a domain that can't be used, a password missing for a protected page, or a monitor from outside the workspace.403The plan's status page limit is reached —errorCodeislimitExceeded.409The slug is taken, or the domain is already used by another page.
Theme and sections
themeConfig sets how the page looks and how its services are grouped. Every key is optional:
| Key | Values |
|---|---|
design | Modern (default), Classic, Minimal |
colorTheme | Dark (default), Light, Auto — follows the visitor's system |
headerLayout | vertical (default) or horizontal |
logoLight, logoDark | Logo URLs for light and dark backgrounds — upload one with POST /status-pages/logo. |
logoUrl | Where clicking the logo goes. |
supportUrl | The “Get in touch” link: https://… or mailto:…. |
sections | Groups of services: [{ "name": "Core", "resources": [{ "monitorId": 41, "name": "API", "showHistory": true }] }]. name on a resource renames the monitor for visitors; showHistory shows its 90-day bars. A monitor must also be in monitorIds; one that's in no section is added to the last. |
/status-pages/:idmonitorIds and themeConfig replace what was there. A new password signs out everyone who unlocked the page; a new domain is checked again.idcurl -X PUT "https://api.zharp.io/api/v1/status-pages/12" \
-H "Authorization: Bearer $ZHARP_API_KEY" \
-H "Content-Type: application/json" \
-d '{
"title": "Acme System Status",
"domain": "status.acme.com"
}'{ "message": "Status page updated successfully", "data": { "id": 12, "title": "Acme System Status", "domain": "status.acme.com", "domainStatus": "PENDING", … } }400As for creating one.404No such page in this workspace.409The slug or domain is taken.
/status-pages/:ididcurl -X DELETE "https://api.zharp.io/api/v1/status-pages/12" \ -H "Authorization: Bearer $ZHARP_API_KEY"
{ "message": "Status page deleted successfully", "data": null }404No such page in this workspace.
/status-pages/logothemeConfig.logoLight or logoDark. Nothing on a page changes until you do.filecurl -X POST "https://api.zharp.io/api/v1/status-pages/logo" \ -H "Authorization: Bearer $ZHARP_API_KEY" \ -F "file=@logo.png"
{ "message": "Logo uploaded", "data": { "url": "https://res.cloudinary.com/…/status-pages/3/abc.png" } }400No file, or not an image of those types.413Over 2 MB.
/status-pages/:id/domain/verifyVERIFIED when it has a CNAME to the target or all its addresses are the target's; domainReason says what's wrong otherwise. Zharp also checks pending domains every few minutes by itself.idcurl -X POST "https://api.zharp.io/api/v1/status-pages/12/domain/verify" \ -H "Authorization: Bearer $ZHARP_API_KEY"
{
"message": "Domain verified",
"data": {
"domain": "status.acme.com",
"domainStatus": "VERIFIED",
"domainCheckedAt": "2026-10-26T10:02:00.000Z",
"domainReason": null,
"domainSetup": { "cnameTarget": "statuspage.zharp.io", "addresses": ["203.0.113.10"], "apex": false }
}
}400The page has no custom domain.429More than 12 checks a minute for this page.404No such page in this workspace.
Status reports and announcements
A status report tells visitors about a problem and is updated until it's resolved; the services it names show as degraded meanwhile. Subscribers are e-mailed when a report is posted and with each update.
/status-pages/:id/announcementsidcurl "https://api.zharp.io/api/v1/status-pages/12/announcements" \ -H "Authorization: Bearer $ZHARP_API_KEY"
{ "message": "Announcements retrieved successfully", "data": [ {
"id": 88,
"statusPageId": 12,
"title": "API latency",
"content": "We're investigating slow responses.",
"type": "INFO",
"status": "INVESTIGATING",
"affectedMonitorIds": [41],
"resolvedAt": null,
"scheduledFor": null,
"createdAt": "2026-10-26T09:41:00.000Z",
"updatedAt": "2026-10-26T09:41:00.000Z",
"updates": [
{ "id": 301, "announcementId": 88, "status": "INVESTIGATING", "message": "We're investigating slow responses.", "createdAt": "2026-10-26T09:41:00.000Z" }
]
} ] }404No such page in this workspace.
/status-pages/:id/announcementsstatus — or an announcement, without one. Every confirmed subscriber is e-mailed straight away — or, with scheduledFor in the future, when that time comes; until then it's not on the page.idtitlecontentstatusINVESTIGATING, IDENTIFIED, MONITORING or RESOLVED. Leave it out for an announcement.affectedMonitorIdsscheduledForcurl -X POST "https://api.zharp.io/api/v1/status-pages/12/announcements" \
-H "Authorization: Bearer $ZHARP_API_KEY" \
-H "Content-Type: application/json" \
-d '{
"title": "API latency",
"content": "We're investigating slow responses.",
"status": "INVESTIGATING",
"affectedMonitorIds": [
41
]
}'{ "message": "Status report created successfully", "data": {
"id": 88,
"statusPageId": 12,
"title": "API latency",
"content": "We're investigating slow responses.",
"type": "INFO",
"status": "INVESTIGATING",
"affectedMonitorIds": [41],
"resolvedAt": null,
"scheduledFor": null,
"createdAt": "2026-10-26T09:41:00.000Z",
"updatedAt": "2026-10-26T09:41:00.000Z",
"updates": [
{ "id": 301, "announcementId": 88, "status": "INVESTIGATING", "message": "We're investigating slow responses.", "createdAt": "2026-10-26T09:41:00.000Z" }
]
} }400A field fails validation, or an affected monitor isn't on this page.404No such page in this workspace.
/status-pages/:id/announcements/:announcementId/updatesRESOLVED closes it; any other status reopens it. Subscribers are e-mailed.idannouncementIdstatusINVESTIGATING, IDENTIFIED, MONITORING or RESOLVED.messagecurl -X POST "https://api.zharp.io/api/v1/status-pages/12/announcements/88/updates" \
-H "Authorization: Bearer $ZHARP_API_KEY" \
-H "Content-Type: application/json" \
-d '{
"status": "RESOLVED",
"message": "Response times are back to normal."
}'{ "message": "Update posted", "data": { "id": 88, "status": "RESOLVED", "resolvedAt": "2026-10-26T10:20:00.000Z", "updates": [ … ], … } }400A field fails validation.404No such page or report.
/status-pages/:id/announcements/:announcementIdidannouncementIdtitlecontentaffectedMonitorIdscurl -X PUT "https://api.zharp.io/api/v1/status-pages/12/announcements/88" \
-H "Authorization: Bearer $ZHARP_API_KEY" \
-H "Content-Type: application/json" \
-d '{
"title": "Slow API responses in the EU"
}'{ "message": "Status report updated", "data": { "id": 88, "title": "Slow API responses in the EU", … } }400A field fails validation.404No such page or report.
/status-pages/:id/announcements/:announcementIdidannouncementIdcurl -X DELETE "https://api.zharp.io/api/v1/status-pages/12/announcements/88" \ -H "Authorization: Bearer $ZHARP_API_KEY"
{ "message": "Announcement deleted successfully", "data": null }404No such page or report.
/status-pages/:id/maintenanceidcurl "https://api.zharp.io/api/v1/status-pages/12/maintenance" \ -H "Authorization: Bearer $ZHARP_API_KEY"
{ "message": "Maintenance retrieved", "data": [{ "id": 5, "name": "Database upgrade", "startTime": "…", "endTime": "…", "recurring": false, "monitors": [{ "monitorId": 41 }], … }] }404No such page in this workspace.
Subscribers
Visitors subscribe by e-mail and confirm with a link (double opt-in). Only confirmed subscribers (isActive: true) are e-mailed.
/status-pages/:id/subscribersidcurl "https://api.zharp.io/api/v1/status-pages/12/subscribers" \ -H "Authorization: Bearer $ZHARP_API_KEY"
{
"message": "Subscribers retrieved successfully",
"data": [
{ "id": 7, "statusPageId": 12, "email": "jo@example.com", "isActive": true, "confirmedAt": "…", "confirmationSentAt": "…", "createdAt": "…", "updatedAt": "…" }
]
}404No such page in this workspace.
/status-pages/:id/subscribers/importidemailscurl -X POST "https://api.zharp.io/api/v1/status-pages/12/subscribers/import" \
-H "Authorization: Bearer $ZHARP_API_KEY" \
-H "Content-Type: application/json" \
-d '{
"emails": [
"jo@example.com",
"sam@example.com"
]
}'{ "message": "2 confirmation e-mails sent", "data": { "invited": 2, "alreadyInvited": 0, "alreadySubscribed": 0, "invalid": 0 } }400emailsisn't a list, or holds more than 1,000.404No such page in this workspace.
/status-pages/:id/subscribers/:subscriberIdidsubscriberIdcurl -X DELETE "https://api.zharp.io/api/v1/status-pages/12/subscribers/7" \ -H "Authorization: Bearer $ZHARP_API_KEY"
{ "message": "Subscriber removed", "data": null }404No such page or subscriber.
Public pages
These need no key — they are what a status page itself calls, so you can build your own page or widget on them. A PASSWORD_PROTECTED page needs the token from unlock in an X-Status-Page-Token header; a PRIVATE page can't be read here — use GET /status-pages/:id with a key instead.
/status-pages/public/:slughistoryhas one entry per day:timestampis the start of the UTC day in seconds,uptimea percentage, or-1for a day without data.- Feed items (
activeReports,activeIncidents,upcomingMaintenance,updates) share one shape;kindisREPORT,ANNOUNCEMENT,INCIDENTorMAINTENANCE. Incidents are worded for visitors — “API is unavailable” — with nothing internal. domainis set only once the custom domain is verified.
slugacme in acme.zharp.io.curl "https://api.zharp.io/api/v1/status-pages/public/acme"
{
"message": "Status page details retrieved successfully",
"data": {
"id": 12,
"slug": "acme",
"title": "Acme Status",
"description": null,
"domain": "status.acme.com",
"visibility": "PUBLIC",
"theme": { "design": "Modern", "colorTheme": "Dark", "headerLayout": "vertical", "logoLight": "https://…", "logoDark": "https://…" },
"showBranding": false,
"overallStatus": "degraded",
"sections": [
{
"name": "Core",
"resources": [
{
"monitorId": 41,
"name": "API",
"status": "degraded",
"showHistory": true,
"history": [{ "timestamp": 1753488000, "uptime": 99.87 }, …],
"uptime": 99.91
}
]
}
],
"uptimeStats": { "24h": 100, "7d": 99.95, "30d": 99.9, "90d": 99.92 },
"activeReports": [ {
"kind": "REPORT",
"id": 88,
"title": "API latency",
"message": "We're investigating slow responses.",
"status": "INVESTIGATING",
"startedAt": "2026-10-26T09:41:00.000Z",
"endedAt": null,
"affected": ["API"],
"updates": [{ "id": 301, "status": "INVESTIGATING", "message": "We're investigating slow responses.", "createdAt": "2026-10-26T09:41:00.000Z" }]
} ],
"activeIncidents": [],
"upcomingMaintenance": [],
"updates": [ … ],
"period": { "days": 30, "from": "…", "to": "…" },
"generatedAt": "2026-10-26T10:05:00.000Z"
}
}404No page with that slug.403The page is private, or password-protected and no valid token was sent.
/status-pages/public/:slug/historyslugacme in acme.zharp.io.dayscurl "https://api.zharp.io/api/v1/status-pages/public/acme/history?days=30"
{ "message": "…", "data": { "title": "Acme Status", "theme": { … }, "showBranding": false, "period": { "days": 30, "from": "…", "to": "…" }, "items": [ … ] } }404No page with that slug.403Private, or locked without a token.
/status-pages/public/:slug/subscribeslugacme in acme.zharp.io.emailcurl -X POST "https://api.zharp.io/api/v1/status-pages/public/acme/subscribe" \
-H "Content-Type: application/json" \
-d '{ "email": "jo@example.com" }'{ "message": "Check your inbox to confirm your subscription", "data": null }400Not a valid e-mail address.429More than 20 sign-ups from one address in an hour.
/status-pages/public/:slug/unlockX-Status-Page-Token on the public endpoints above.slugacme in acme.zharp.io.passwordcurl -X POST "https://api.zharp.io/api/v1/status-pages/public/acme/unlock" \
-H "Content-Type: application/json" \
-d '{ "password": "…" }'{ "message": "Unlocked", "data": { "token": "eyJ…", "expiresIn": 604800 } }403Wrong password.400The page isn't password-protected.429Ten wrong tries in 15 minutes — wait a little.