Skip to main content

Public Status Pages

SolidPing includes built-in public status pages that let you share service availability with your users, customers, or team members — no authentication required.

Overview

Status pages provide a real-time view of your monitored services. Each organization can publish multiple pages, and each page displays:

  • Current status of each service (up / down / degraded)
  • Uptime percentage over a configurable history window
  • Per-check response-time history
  • Whole check groups published as a single aggregated component, hiding your internal probe topology
  • Recent incident history
  • Locale-aware date and time formatting

Structure

A status page is organized into sections and resources:

  • Sections group related services (for example, "API", "Database", "Frontend"). Sections are ordered and can be reordered.
  • Resources are the components inside a section. A resource targets either one check or one check group — never both. Each resource can have a public display name and an explanation that override the internal name, so you control exactly what visitors see.

Group components

A single host is often probed several times over — TCP, HTTP, TLS, RDP. Publishing all four checks tells your visitors far more about your internal monitoring topology than they need to know, and it quadruples the length of your page.

Point a resource at a check group instead and it renders as one component. Nothing about the group's members reaches the public page: no member names, no member types, not even how many there are.

Status

The component's status is rolled up from the group's enabled member checks:

MembersComponent reads
All upUp
All downDown
Some — but not all — downDegraded
None down, at least one warningWarning
No members, or none reporting yetNo data

A member in the transient "validating" state still reads up publicly, exactly as a standalone check does — the component only turns red once a failure is confirmed. Disabled members are ignored entirely.

Availability

Per time bucket (a day, or an hour in the 24h view), availability is the weighted average across members:

availability = sum(successful checks across members) / sum(total checks across members) × 100

This is the same formula a single check already uses, extended across the group — not an average of per-member percentages. So a member probed every 10 seconds carries proportionally more weight in a bucket than one probed every 5 minutes, which is what you want: the number reflects how many probes actually succeeded.

A member with no data at all in a bucket contributes nothing to it — it is not counted as a zero. A bucket in which no member reported renders as "no data", exactly like a silent single check.

Group components do not publish a response-time chart. Interleaving several members' latencies into one plot would be meaningless, and it would expose per-member timing — precisely what the group component exists to hide. The availability bar is the group's public performance surface.

Maintenance

A group component shows the "Scheduled Maintenance" badge when an active maintenance window targets the group or any of its member checks. You can therefore schedule maintenance at whichever granularity is natural and the public page stays correct.

Setting one up

In the dashboard, open the status page, click + on a section, switch the dialog to the Group tab and pick the group. An existing component's target can be changed later — including switching it between a check and a group — with the pencil icon on its row.

Via the API, POST .../sections/{section}/resources accepts checkUid or checkGroupUid (a UID or a slug for either). Sending both — or neither — is a VALIDATION_ERROR naming both fields. PATCH on an existing resource accepts the same pair to move it to a different target.

From the CLI:

sp status-pages resources create public core --check-group web-frontend

Configuration

Status pages are managed per organization from the dashboard (Settings → Status Pages) or via the API. Key options per page:

OptionDescription
Name / SlugPage identity and public URL path
DescriptionOptional intro text
EnabledToggle public visibility
DefaultMark one page as the org's default
Show AvailabilityDisplay overall and per-day uptime percentages
Show Response TimeDisplay per-check response-time history
History DaysSize of the lookback window (default 90 days)
LanguageLocale used for date formatting on the public page
Custom CSSYour own stylesheet, applied to the public page (see below)
Auto-publish incidentsTurn monitoring incidents into public incidents automatically (see below)
Publication delayDebounce before an incident goes public (default 60 s)
When the incident resolvesalways / if_untouched / never

Incidents

A status page shows two different things, and it is worth keeping them apart:

  • the component grid — a dot per check or group, flipping green/amber/red on its own;
  • incidents — the narrative. A title, a state, and an append-only list of updates explaining what is going on.

Before this feature, only the grid was automatic: a check going down turned a dot red and said nothing else. If nobody was awake to write an update, visitors saw a red dot with no explanation. Incidents fix that.

Automatic publication

When Auto-publish incidents is on, an incident that opens on a check displayed by the page becomes a public incident, with a templated title built from the component's public name and a first "investigating" update.

Four things stop a publication:

SituationBehaviour
The incident resolves inside the publication delayNothing is ever published — a short blip stays private
The incident is rolled up under a parentThe parent (or group) incident publishes; members never do
The check is inside a maintenance windowPlanned work is not an incident
The page — or that one component — has auto-publish offNothing is published

The per-component override is three-state: leaving it unset means "inherit the page", which is not the same as switching it off. So you can turn a page on without silently opting in a component you deliberately excluded — and vice versa.

Existing pages are not opted in

Pages that existed before this feature shipped keep auto-publish off. Nobody's internal blips become public because they upgraded. Pages created since default to on.

What customers see — and what they never see

Public incident copy is built from exactly one incident-derived value: the public display name of the affected component (falling back to the check name, which is the same fallback the component list already uses).

Probe output, error strings, response bodies and internal hostnames are never written into a public field. This is a structural guarantee, not a convention — the publication table has no column any of it could land in, and the templates interpolate only that one name.

Resolving

When the underlying incident recovers, what happens depends on the page's When the incident resolves setting and on whether a human has touched the public incident:

SettingNobody edited itSomebody edited it
if_untouched (default)Resolved automatically, with a "resolved" updateA "component has recovered" note is posted; you close it
alwaysResolved automaticallyResolved automatically
neverNothing posted, nothing resolvedNothing posted, nothing resolved

The default exists because the moment you edit a public incident, its narrative is yours. Auto-resolving under you would overwrite a deliberate editorial decision with a machine's opinion.

If the check relapses shortly after recovering, the same incident is reopened rather than a second one being created — a flapping service reads as one incident with a history, not five.

Writing them by hand

You do not need an underlying monitoring incident at all. From Status Pages → (page) → Incidents you can publish a hand-written incident with its own title, severity badge (minor / major / critical) and updates. You can also publish an existing incident onto a page from the incident's own detail view, and take it down again later.

Updates are append-only: there is no edit and no delete. A posted update is a promise, not a draft — correcting one means posting the correction.

API

EndpointPurpose
GET /api/v1/orgs/{org}/status-pages/{page}/incidentsList publications
POST /api/v1/orgs/{org}/status-pages/{page}/incidentsPublish a hand-written incident
GET/PATCH …/incidents/{uid}Read / edit title, severity, state
POST …/incidents/{uid}/updatesAppend an update
GET/POST /api/v1/orgs/{org}/incidents/{uid}/publicationsList / publish from the incident side
DELETE …/publications/{uid}Unpublish
GET /api/v1/status-pages/{org}/{slug}/incidentsPublic incident history (?active=true for open ones)

The public page payload also gains an activeIncidents[] array, so a single request renders the banner, the components and the incidents together.

Webhooks

Publication lifecycle fires its own events on your webhook connections, separate from the internal incident events:

EventWhen
statuspage.incident.publishedAn incident became visible on a page
statuspage.incident.updatedTitle, severity, state or narrative changed
statuspage.incident.resolvedThe public incident was closed or unpublished

incident.created / incident.resolved are unchanged and still describe the internal incident. The two are deliberately distinct: they happen at different times (the publication delay sits between them), and most incidents never produce a public one at all.

Subscriber storm cap

Each public incident may trigger at most 4 subscriber email waves per hour (org parameter status_page.publication_notify_cap). Beyond that, updates are still posted to the page and the feed — only the mail stops. A flapping group incident must not fill anyone's inbox.

Custom CSS

Every status page can carry its own stylesheet, so the page matches your brand instead of SolidPing's. It is a free feature — no plan gating — and works on the default /status0/{org}/{slug} URL and on a custom domain alike.

Editing it

Open the page in the dashboard and choose Appearance (also reachable from the edit screen). The editor is a plain CSS text box beside a live preview: the preview is the real status page in an iframe, restyled as you type, so what you see is exactly what visitors get. Nothing is published until you press Save; emptying the box and saving removes the stylesheet again.

If the page has no CSS yet, Insert starter template drops in a commented template listing every supported variable.

CSS variables

The public page paints everything from CSS custom properties, so overriding a handful of them re-themes the whole page without touching a single selector:

VariableWhat it controls
--brandBrand color: logo tint and outbound links
--brand-foregroundText/icon color drawn on top of --brand
--backgroundPage background
--foregroundDefault text color
--cardSection card background
--card-foregroundText inside section cards
--borderHairlines, separators and card outlines
--muted / --muted-foregroundSecondary surfaces and secondary text
--status-ok"Operational" green: dots, badges, uptime bars
--status-warning"Degraded" amber
--status-error"Down" red
--radiusCorner radius used across the page

Colors accept any CSS color syntax (#rrggbb, rgb(), oklch(), …).

Rules placed inside a .dark { … } block apply when the page is in dark mode; rules in :root { … } apply to light mode. A visitor lands in dark mode either because they explicitly picked it with the sun/moon toggle in the page header (their choice is remembered on that browser) or, absent a stored choice, because their OS/browser requests it. Either way SolidPing adds a dark class to <html> before the page paints, so .dark { … } overrides apply consistently regardless of which of the two triggered it. You are not limited to variables — any CSS you write is applied to the live page.

Element hooks

Variables re-theme the page; the sp-* classes let you retarget individual elements — replace the logo, hide the version, white-label the footer. They are a stable, supported API: unlike the utility classes you may see in the generated markup, these will not change under you.

ClassElement
sp-logoHeader logo wrapper (the <img> sits inside it)
sp-page-nameStatus page name shown next to the logo
sp-page-titlePage heading (<h1>) at the top of the body
sp-page-descriptionPage description under the heading
sp-status-bannerOverall-status banner strip below the heading
sp-footerFooter container
sp-powered-by"Powered by SolidPing" outbound link
sp-versionVersion line (v1.2.3)

The page also carries a dark class on its <html> ancestor whenever the visitor is in dark mode (see CSS variables above) — you can target it directly, e.g. .dark .sp-logo img { content: url(...); } for a logo variant with better contrast on dark backgrounds. Most custom CSS never needs this: an override written against the --* variables (--brand, --card, --status-ok, …) already applies correctly in both modes, since the tokens themselves swap value inside .dark. Reach for .dark <selector> only when an override needs to differ structurally between modes — not just in color — such as swapping an image asset.

The logo is a plain <img> inside .sp-logo, and its size comes from CSS (not from an inline style), so both of these work without any upload:

/* Simplest — swap the image the <img> paints (Chrome, Edge, Safari). */
.sp-logo img {
content: url("https://cdn.example.com/logo.svg");
}
/* Widest browser support — hide the <img>, paint the wrapper instead. */
.sp-logo img {
display: none;
}

.sp-logo {
background: url("https://cdn.example.com/logo.svg") center / contain no-repeat;
width: 120px;
height: 32px;
}

A non-square logo also just needs its own box:

.sp-logo img {
content: url("https://cdn.example.com/wordmark.svg");
width: 140px;
height: 32px;
}

The image must be reachable over HTTPS from your own host or CDN — url() is allowed, @import is not.

Hiding the version and the credit

.sp-version {
display: none;
}

/* Fully white-label footer */
.sp-powered-by {
display: none;
}

Example

/* Light mode: warm brand, near-white page */
:root {
--brand: #ff5500;
--brand-foreground: #ffffff;
--background: #fdfaf7;
--card: #ffffff;
--border: #ece4dc;
}

/* Dark mode: deep neutral background */
.dark {
--background: #12100e;
--foreground: #f2ede8;
--card: #1c1917;
--border: #2e2a26;
}

Limits

  • Maximum size: 64 KB. A larger stylesheet is rejected with a VALIDATION_ERROR.
  • @import is not allowed, anywhere in the stylesheet and in any casing. It would let the page pull in further third-party stylesheets that were never reviewed; inline the rules you need instead.
  • External url() is allowed — web fonts, background images and other assets fetched from your own CDN work normally.

The stylesheet is stored verbatim and rendered as a text node inside a <style> element, so it cannot inject markup or scripts into the page.

Subscribers

Visitors can subscribe to a status page to be notified of incidents by email:

  • Double opt-in — a confirmation link is emailed before any updates are sent.
  • Subscribers can unsubscribe at any time via a link in every message.
  • The subscriber list is admin-only; addresses are redacted in API responses.

Feeds

Each page also publishes an Atom feed (/feed.xml) of status updates, so users can follow along in a feed reader or pipe updates into other tools.

Summary endpoint

For integrators who just want "is this service up right now?" without the full page payload, GET /api/v1/status-pages/{org}/{slug}/summary returns a lightweight JSON rollup:

{
"status": "operational",
"counts": { "operational": 12, "degraded": 1, "down": 0, "maintenance": 0, "unknown": 0 },
"page": { "name": "SolidPing", "slug": "main", "url": "https://status.example.com/" },
"generatedAt": "2026-08-08T12:00:00Z"
}

It's public (no authentication), sets Cache-Control: public, max-age=60, and computes status/counts from the exact same server-side rollup as the full page view — so the two can never disagree.

Badge

GET /api/v1/status-pages/{org}/{slug}/badge returns an SVG badge showing the page's overall status — the static, script-free counterpart to the JS embed widget, for places scripts can't run (a GitHub README, a wiki, an email footer):

![Status](https://your-solidping-instance/api/v1/status-pages/default/main/badge)

It's public, sets Cache-Control: public, max-age=60, and applies the same visibility gate and rollup as the summary endpoint above, so the badge can never disagree with the status page. Colors follow the rollup status: green (operational), yellow (degraded), red (down), blue (maintenance), gray (unknown). Customize with label, style (flat or flat-square), minWidth, and width query parameters, matching the per-check badges.

Embeddable Live Widget

GET /embed/v1/widget.js serves a small, self-contained script that renders a live status pill on your own site — the "⊙ All systems operational" badge that links back to your status page:

<script async src="https://your-solidping-instance/embed/v1/widget.js" data-page="default/main"></script>

The pill renders where the tag sits, in a shadow root, so your site's CSS can neither break it nor be affected by it. It polls the summary endpoint every 60 seconds with an uncredentialed request, and if that request fails — or the page doesn't exist, or is private — it renders nothing at all, never an error state on your site.

Customization is entirely by data-attribute:

AttributeValuesDefault
data-pageorg/slug — required
data-modeinline, floatinginline
data-positionbottom-right, bottom-left (floating only)bottom-right
data-themelight, dark, auto (follows prefers-color-scheme)auto
data-sizesm, md, lgmd
data-label-operational
data-label-degraded
data-label-down
data-label-maintenance
data-label-unknown
any textbuilt-in English labels
data-force-statusoperational, degraded, down, maintenance, unknown— (normal polling)

data-force-status skips polling entirely and renders that status statically, with no link — mainly useful for previewing the widget (the dashboard's snippet generator uses it) or for a demo/staging page that isn't backed by a real status page yet. An unrecognized value is ignored and normal polling resumes.

Everything under /embed/v1/ is a frozen contract: once you've pasted the snippet it will keep working, and any future behavior change ships under /embed/v2/ instead. The script is served with Cache-Control: public, max-age=3600.

The dashboard generates the snippet for you under Status Pages → (your page) → Appearance.

Accessing Status Pages

Status pages are served directly by SolidPing at a dedicated URL path, making them easy to embed or link to from your own website. The default page is reachable at the organization root path, and named pages at their slug.

Use Cases

  • Customer-facing status: Show your users the health of your services
  • Internal dashboards: Give teams visibility into infrastructure status
  • Incident communication: Automatically reflect incidents on the status page
  • SLA reporting: Track and display uptime metrics