Status pages

Embed
Preview of the card
<a href="https://forumulate.com/guides/status-pages"><img src="https://forumulate.com/guides/_api/public/orgs/046a9bc0-d530-43ae-9bae-af7945008cda/spaces/36aa4df0-1e95-4cef-8ae9-795169441345/pages/b9841b7b-6375-46bd-aae5-d851943514a9/social-card.svg?v=3e7ae33eec8b" alt="Status pages" width="600" height="315"></a>

A Status page space tells readers whether your product is up: a banner with the overall state, a row per component with its day bars, open incidents and maintenance, the history, and a way to be told next time. This guide covers the page itself: how it looks, its sub-pages, how incidents and maintenance are written and announced, who gets told and how, and how to embed it elsewhere. The monitors that feed it (probe kinds, regions, metrics, inbound alerts) are in Status monitors.

  • The page's look lives in the space theme under theme.status, set with PATCH /orgs/{orgId}/spaces/{spaceId}/theme. Setting theme.status on a space of another type is refused with a 422. A site theme may carry it, and it then reaches every status page in the site.

  • How the page behaves (state rules, reminders, channels) lives in its status settings, PUT /orgs/{orgId}/spaces/{spaceId}/status/settings.

  • Every theme key is independent, and an unset key draws the page as it drew before the key existed. A misspelt key is a 422 that names it.

Where a component's state comes from

This is the one default that changed, so it comes first.

stateSource

What decides a component's state

current (default)

Its latest checks: operational while they pass, degraded while a few fail, an outage once the failures reach its failureThreshold. This is what Statuspage, Instatus and Better Stack report. The uptime window stays on the page as the percentage and the bars.

window

The latest checks first, then a poor uptime window can degrade a passing check: under 99.5% reads degraded, under 97% partial_outage (once there are at least 20 checks over 6 hours). This was the rule before the setting existed.

The headline follows it. The banner ("All systems operational", "Degraded performance", …) is worked out from the component states, so with current a component that recovered an hour ago no longer holds the banner at "degraded" because of last week's uptime. In both modes a declared state (declaredState) always wins, and history only ever degrades a state, never improves it.

The order it is decided in: the page's own stateSource, else the organization's status.state_source, else current. The board answers with the effective one as stateSource.

# one page
PUT /orgs/{orgId}/spaces/{spaceId}/status/settings
{ "stateSource": "window" }      # null: follow the organization again

# every status page in the organization
PUT /orgs/{orgId}/settings/status.state_source
{ "value": "window" }

In the studio: Checks and alerts → How the page behaves → A component's state comes from (Its current checks, Its checks and its uptime window, or The organization's default).

Looks

theme.status.look picks a starting point. A look is a patch of ordinary keys laid under yours, so every key you set still wins, and nothing is copied into your theme. Layering is site theme → space theme → one incident's own customization.

look

Modelled on

The keys it sets

default (unset)

—

banner tint + icon + "Last updated"; bars solid, full-height no-data bars, 30 days on a phone, caption on, styled day panel; components.rowMeta: none; legend top, 5 states; incidents empty states on, outages folded into the history; page 7 days of history, refresh every 60 s, favicon: alert; header.zoneSwitch on.

classic

Statuspage

banner.style: solid (radius 6); bars thin, 34px, gap 2; components grid-2 in a card, dividers, statusStyle: word; legend bottom, 4 states; page.emptyDays: 15; layout.density: cozy; outages plain cards with durations.

band

Instatus

banner centered with a subline; bars pill, 28px, uppercase tracked caption; components card-per-group, groupSummary: count, statusStyle: icon; page.frame: band-card on a brand band (280px), nav: tabs; card radius 20, lg shadow; title centred with the logo; kpis four tiles; uppercase headings; sub-pages at the board's width with the title row.

monitor

Better Stack

banner.style: outline; bars square, 24px, 3 decimals; components rowMeta: chart, card, headline: first, statusStyle: chip; metrics area, p95, grid; sla.target: 99.9; rows.percent; compact density; sub-pages at the board's width.

calm

incident.io

banner tint without a glyph, with a subline, radius 12; bars pill, 20px; sentence-case icon-word states, no dividers; tinted incident cards with chips and relative times; no legend; flat history (emptyDays: 0), nav: links; roomy density, 48px section gap; subscribe.layout: card.

minimal

Openstatus

banner.style: rule without glyph or "Last updated"; bars ribbon, 16px, no caption; rowMeta: percent, dividers, statusStyle: icon; no legend; flat history; favicon: dot; compact density, 14px headings; prints without colours.

paper

—

No layer at all: the page exactly as it drew before it had looks. The way back, in one key.

PATCH /orgs/{orgId}/spaces/{spaceId}/theme
{ "status": { "look": "classic" } }

Mixing looks

Because a look is only a set of keys, you can take one look and borrow another's parts. Three combinations that none of the looks is on its own:

The Statuspage layout with Better Stack's response-time chart under each row:

{ "status": { "look": "classic", "components": { "rowMeta": "chart" }, "metrics": { "percentile": "p95", "style": "line" } } }

The quiet calm page floating on a brand band like band:

{
  "status": {
    "look": "calm",
    "page": { "frame": "band-card", "band": { "tone": "brand" }, "bandHeight": 240 },
    "card": { "radius": 20, "shadow": "lg" }
  }
}

The default look with minimal's ribbons and an uptime target (the kpis band only draws where you place it on the front page):

{
  "status": {
    "bars": { "shape": "ribbon", "height": 16 },
    "sla": { "target": 99.95 },
    "kpis": { "tiles": ["uptime", "incidents30d", "mttr"] }
  }
}

The front page bands

The board is a composed front page. Until you edit it, it is generated from these bands, in this order: intro, banner, active (open incidents and maintenance under way), components, outages (what our own checks recorded), history, subscribe. Four more are offered and never generated: kpis (tiles), metrics (charts), partners (vendor maintenance from your vendor components) and board (every part in one band, kept so older pages that placed it still draw). Remove, reorder or add bands in the studio's front-page editor.

Every theme.status key group

Each group is independent. Defaults below are the unset values; the default look sets some of them, as listed above.

Group

Keys

What it shapes

colors

operational, degraded, partial, major, maintenance, noData — a palette slot or a hex

Each state's colour. Unset: your palette's success/warning/danger/info. Reaches the badge, widget, footer pill and tab icon too.

banner

style (rule | solid | tint | outline | centered), icon, align, subline, showUpdated, radius, pad

The overall banner.

bars

shape (thin | pill | ribbon | square), height, width, gap, radius, days, daysPhone (30/60/90), fill (solid | hatched), noData (full | tick), caption, percentDecimals (1–3), tooltip (native | styled), captionCase, captionTracking, drillDown

The day strip. drillDown (on) adds "Hour by hour" to the day panel. A component's own barDays overrides days.

components

layout (list | grid-2 | cards), container (none | card | card-per-group), card, dividers, statusStyle (word | icon | chip | icon-word), statusCase, rowMeta (full | percent | percent-latency | none | chart), groupCollapse, groupSummary, headline (none | first | every), headlineSize, headlineAlign, groupBy (group | region)

The component rows and groups.

rows

groupBars, translateNames (on), percent

A roll-up strip per group; names through your translated labels; the percentage even with the caption on.

legend

position (top | bottom | hidden), states (3/4/5)

The strip's legend.

incidents

card (plain | tinted | accent-left), chips, updates (all | latest | collapsed), time (absolute | relative | both), emptyStates, foldOutages, links (on), translatedNote (on)

Incident cards on the board. links: false keeps incident titles as plain text everywhere, sub-pages included.

outages

card, time, duration (on), component (on)

The recorded-outages band.

page

frame (plain | card | band-card), band, bandHeight, emptyDays (0/7/15), nav (none | links | tabs), timezone (utc | local), refresh (0/30/60/120/300 s), favicon (brand | dot | alert), language (reader | source)

The page as a whole. timezone unset is UTC, labelled, so times never change after the page loads; local re-spells them in the reader's zone.

card

radius (16), shadow, offset (64), pad (32)

The card a card or band-card frame draws.

title

size (30), align, logo, tagline, hideName

The title row.

header

reportUrl, reportLabel, zoneSwitch, languageSwitch (on), siblings (on)

Actions beside the title: "Report a problem", the Local/UTC switch, the language switch, and a switcher between your organization's status pages (only drawn with two or more).

hero

layers, layersDark, placement, height (200), overlap, bleed, radius

A drawn header behind the top of the page, in the title-band vocabulary.

layout

density (compact | cozy | roomy), sectionGap (32), headingSize (18), headingCase

Spacing and section headings.

kpis

tiles (operational, degraded, down, uptime, incidents30d, mttr), card, size, tinted

The KPI band.

metrics

range (24h | 7d | 30d), rangeSwitch, percentile (p50 … p99, both), style, height, tone, grid, rows (auto | latency | metrics)

Response-time and custom metric charts.

sla

target (90–100), label, tone

An uptime target: a figure below it is drawn in the outage colour.

subscribe

layout (inline | stacked | card), components, kinds

The subscribe band; a component picker and an incidents/maintenance choice.

partners

card, links, component

The vendor-maintenance band.

widget

position, show (incidents | incidents-maintenance | always), tone, radius, scheme

The floating widget on your own site (below).

dates

display (the shared date vocabulary), zone (an IANA zone such as Europe/Berlin)

Date words, and a named zone instead of UTC. A reader's own pick with the zone switch still wins.

phone

components (list | stack), compactBanner, titleSize, hideLegend, hideActions

Changes under 640px, on top of theme.mobile.

print

colors (on), hide (subscribe, actions, legend, history, metrics, hero)

A printed page. Unset hide: subscribe and actions.

history, incident, uptime, maintenance, subpages

see Sub-pages

The sub-pages.

words

board.*, pages.*, subscribe.*

Every built-in word (below).

Numbers and colours reach the page as CSS variables named after the key (bars.height → --status-bars-height), so your own CSS can read them too.

Your own words

theme.status.words rewords any of the page's ~180 built-in strings. Each word is its own field, grouped as board (the board), pages (the sub-pages) and subscribe (the subscribe band), with dotted catalogue names in camelCase (subline.up → sublineUp):

{
  "status": {
    "words": {
      "board": { "allSystemsOperational": "We're fully operational", "pastIncidents": "History" },
      "subscribe": { "notifyMe": "Keep me posted" }
    }
  }
}

Write each word once, in the page's language. A reader in another language gets your translation of it from theme.i18n.labels where you gave one, and otherwise the word as you wrote it. An unknown field is a 422. The studio's key tree lists every word with its default beside it.

Sub-pages

A status page has more than its board:

Address

What it is

…/history

The incident archive, by month, with a component filter and text search.

…/uptime

Each component's uptime as month calendars.

…/maintenance

Upcoming (and past) maintenance, with a calendar file (maintenance.ics).

…/components/{slug}

One component: its state, regions, description, response time, uptime and incidents.

…/incidents/{id}

One incident or maintenance window, with its updates and post-mortem. …/incidents/{id}.md is its Markdown twin.

…/subscription?token=…

A subscriber's own page to change what they hear about (below).

…/uptime.csv

One row per component per day: component, group, date, uptime_percent, state.

subpages shapes all of them at once, and each page has its own composition:

Key

Default

What it does

subpages.width

list

list (the reading column), board (the board's own width, so nothing jumps), wide, full, or a px number 480–1600.

subpages.hero

none

The board's hero on sub-pages too: same or compact (half height).

subpages.titleRow

off

The board's title row and actions above each sub-page instead of the plain way back.

subpages.<page>.intro

none

Markdown above the page (a note, a support link).

subpages.<page>.heading

built-in

The page's heading in your words.

subpages.<page>.parts

every part

Which parts it draws, in your order. An empty list draws only the heading.

The parts per page: history — intro, filter, search, archive; uptime — intro, filter, calendars; maintenance — intro, calendarLink, upcoming, past; component — intro, state, regions, description, latency, uptime, incidents; incident — components, updates, pager, related.

And each page's own keys:

Key

Default

What it does

history.view

list

list or calendar (a month grid, each day in its worst colour).

history.group.by

month

none, day, month or year, with the shared group vocabulary (heading style, counts, folding).

history.monthsPerPage

3

1, 3, 6 or 12.

history.perMonth

5

Incidents per month before "Show all"; 0 lists all.

history.filter / history.archiveLink

on / on

The component filter; the board's link to the archive.

incident.timeline

rail

The updates as a rail with a dot each, or a plain list.

incident.order

desc

Newest first, or asc (oldest first, read as a story).

incident.components / incident.author

on / off

Affected components as chips; who posted each update.

incident.actions

all allowed

The shared item actions (copy link, share, Markdown).

uptime.months / cell / monthPercent / cellSize / cellGap

3 / square / on / 14 / 3

The calendars.

maintenance.view / showPast / card

list / on / plain

The maintenance page.

Incidents and maintenance

Incidents are posted with POST …/status/incidents and walk investigating → identified → monitoring → resolved. Maintenance (severity maintenance) has a window (scheduledFor, scheduledUntil) and says the same steps in its own words: scheduled, in progress, verifying, completed. Severity is notice, minor, major, critical or maintenance; a notice moves no component unless you link components with an impact.

Drafts, delayed notices and publishing

Field on create

Default

What it does

published

true

false is a draft: on the console only, told to nobody. Publish it with POST …/incidents/{id}/publish (subscribers are told then, unless notifySubscribers: false).

notifyAt

none

Post now, tell subscribers later: the notice goes out at this time instead.

notifySubscribers

true

Untick for a typo fix or a note nobody needs mailed. On PATCH, only a state change is announced.

startedAt / resolvedAt

now

Backfill: when it really began and ended. Posting one already resolved marks it Retroactive.

autoStart / autoComplete

true

Maintenance moves to in progress at scheduledFor and completes at scheduledUntil on its own. Off: you move it.

audience

everyone

Who sees it on the public page (everyone, signedIn, groups).

A posted update can be corrected (PATCH …/incidents/{id}/updates/{updateId}) or deleted, and a whole incident deleted (DELETE …/incidents/{id}).

Post-mortems

A post-mortem is its own document on the incident, written in Markdown or blocks, and public only once published:

PUT /orgs/{orgId}/spaces/{spaceId}/status/incidents/{incidentId}/postmortem
{ "markdown": "## What happened\n…", "publish": true }

The first publish tells subscribers once (notifySubscribers: false skips it); publish: false takes it back down. An unpublished post-mortem is never served, not on the page, the Markdown twin or the feeds.

Drafting an update with AI

POST …/incidents/{id}/draft-update drafts the next update from the incident so far, for a person to edit and post; nothing is posted. It uses your organization's own AI provider (BYOK); without one it answers 409 ai.unavailable and no draft appears.

Templates

Save an incident you post often (POST …/status/templates) with its title, write-up, severity, opening state, components, and the words for each later step (updates.identified, updates.monitoring, updates.resolved). Open one with POST …/status/templates/{id}/incidents, passing variables: {{name}} in the title and write-up is filled from them, {{date}} is today (UTC), and an unknown name is left as written.

Per-incident overrides

Each incident has its own say over how the page shows it (create or PATCH; null clears):

Field

What it does

pinned

Above everything else: in the active band while open, first in the history once resolved.

hideFromHistory

Out of the public history, archive and feeds once resolved (a test, a mistaken post). It still colours its days.

bannerText

The banner's headline while it is open, in your words (≤160).

tone

Its card accent, a palette slot (success, warning, danger, info, neutral, brand). Null: its state's colour.

recurrence

Maintenance only: { "every": "week" | "month", "until": "YYYY-MM-DD" } schedules the next window when this one completes.

customization

The incident page's own piece: theme, customCss, share image, Open Graph text and JSON-LD (which replaces the automatic graph).

customization.theme takes the shared token keys (colours, type, widths, components) and the status namespace only; another type's namespace (say theme.forum) is refused with a 422 that names it.

PATCH /orgs/{orgId}/spaces/{spaceId}/status/incidents/{incidentId}
{ "customization": { "theme": { "status": { "incident": { "timeline": "list", "order": "asc" } } } } }

Outlets

Theme components can render into these slots on a status page (see Theme components):

Outlet

Pages

Context

Where

status-above-board

board

—

Above the board's bands, once (not inside a band).

status-below-board

board

—

Below the board and the history.

status-component-row-after

board, component page (repeats)

component.name, component.slug, component.status

Under every component row.

status-subpage-top

every sub-page

subpage

Under the back link.

status-subpage-bottom

every sub-page

subpage

At the end of its content.

status-incident-above-body

incident page

incident.title, incident.id, incident.kind, incident.status

Under the title and impact, above the updates.

status-incident-after-body

incident page

as above

After the last update.

Subscribers and reader alerts

The subscribe band takes an email address (double opt-in, captcha where your organization has one). With theme.status.subscribe.components a reader picks components; with subscribe.kinds incidents, maintenance or both. The notices are sent in the language the reader subscribed in, and an incident's translation is used where it has one.

Every notice carries a link to the reader's own subscription page (…/subscription?token=…), where they change components, kinds and language (GET/PUT /public/orgs/{orgId}/spaces/{spaceId}/status/subscription).

Readers can also point their own channels at the page, each confirmed with a six-digit code before anything is delivered: slack, discord, teams, googlechat and webhook (a hook URL), sms (a phone number) and telegram (a chat id). A reader's webhook is signed: X-Forumulate-Signature: sha256=<HMAC-SHA256 of the raw body, keyed by the subscription's token>.

You manage the list in Checks and alerts → Email subscribers: add one (confirmed: true for someone who already agreed elsewhere, with no mail), import a CSV (email[,components]), resend a confirmation, remove, or export (GET …/status/subscribers/export.csv). Readers' other channels are listed under Alerts and integrations → Readers' own alerts.

Notification channels and integrations

Your own channels post every notice to your team: webhook, slack, discord, teams (a Workflows webhook) and google_chat (POST …/status/channels), plus channels that post with a credential (POST …/status/channels/credential): Mastodon, Bluesky, X, Telegram, Jira (an issue per notice) and Zendesk (a ticket per notice).

The Slack app. Your own Slack app's /status command opens, updates and resolves incidents from a channel: /status open <title>, /status update <words>, /status resolve [words], /status list. Point the command at https://forumulate.com/api/public/status/slack/{orgId}.

On call. A schedule (POST …/status/oncall) names members in rotation order, a shift length (rotation.startsAt, rotation.lengthHours) and an escalation (afterMinutes, steps). When a check fails, the member on call is paged by email; the page's acknowledge link stops the escalation, and an unacknowledged page moves to the next member. In the studio: Checks and alerts → On call.

What you need to set up

Every integration here uses your organization's own accounts; nothing on Forumulate's side is needed. Until each is set, its feature is simply not offered.

Feature

What you enter

Where

Until then

SMS to readers

A Twilio account: accountSid, authToken, fromNumber

Alerts and integrations → Text messages, or PUT /orgs/{orgId}/sms (details)

The page does not offer SMS.

Telegram for readers

Your bot: token (secret), username

Your own integration accounts, PUT …/status/integrations/telegram-bot

The page does not offer Telegram.

Slack /status command

Your Slack app: signingSecret and botToken (secrets), appId, teamId

Your own integration accounts, PUT …/status/integrations/slack-app

The command answers "This command is not set up."

Mastodon / X / Telegram channel

token; Mastodon also instance, Telegram chatId

Post to Mastodon, Bluesky, X, Telegram, Jira or Zendesk

No such channel.

Bluesky channel

handle, appPassword

as above

No such channel.

Jira / Zendesk channel

Jira site, project, issueType, email, apiToken; Zendesk subdomain, email, apiToken

as above

No such channel.

AI-drafted updates

Your organization's AI provider

Organization settings

No draft is made; the route answers 409.

Pulled metrics

The provider's keys

Status monitors → Metrics

The metric shows only what you push.

Secrets are write-only and stored encrypted; reads name which ones are set.

How the page behaves

PUT …/status/settings (studio: Checks and alerts → How the page behaves and Telling people). Each key is the page's own; unset reads as below.

Key

Default

What it does

stateSource

org's, else current

See the top of this guide.

dayThresholds

org's status.day_thresholds, else 99.9 / 99

{ "operational": 99.5, "partial": 98 }: the uptime a day needs to read operational / partial. null follows the org.

notifySubscribers

on

Email subscribers when a check opens an incident.

autoIncident

off

Open an incident automatically when a check fails (they close themselves). Off by default, so a flaky check does not open and close incidents in front of readers.

maintenanceReminders

[24]

Hours before a window to remind subscribers, up to three of 1, 6, 24, 48, 72. []: none.

maintenanceNotifyStart / maintenanceNotifyEnd

on / on

Tell subscribers when a window starts and completes.

suppressAlertsInMaintenance

on

No operator alert or automatic incident for a component under active maintenance.

uptimeExcludesMaintenance

on

Maintenance is not downtime. Off: a window costs a manual component's days like an outage.

smsTemplate

{page}: {title} — {state}. {url}

The text message, with {page}, {title}, {state}, {url}.

replyTo

the org's mail default

Where a reply to a status email goes.

alertChannels

all on

email, slack, webhook, sms, discord, teams, googlechat, telegram, each on or off. Off: not offered, refused, and existing subscriptions stop.

autoTranslate

none

Languages every incident is machine-translated into as it is posted (your provider).

Languages

An incident can be translated by hand or by your organization's translation provider (…/incidents/{id}/translations/{locale}, or autoTranslate above); see Translations everywhere. Each language then has its own address under the page (…/status/es), listed in the sitemap with hreflang. page.language decides which language incidents open in (reader: the reader's own when there is a translation; source: the original), header.languageSwitch draws the switch, and incidents.translatedNote says "Translated from English · Show original" above a translation. The page's own words are catalogued in English, German, Spanish, French, Portuguese, Arabic, Hebrew, Persian and Urdu.

Embeds, widget and badge

What

Address

Notes

Badge

…/badge.svg

style=uptime (default) or status (a dot and the sentence); theme=light|dark; size=s|m|l; component=<slug or id>. In your state colours. Cached 5 minutes.

Floating widget

<script src="…/widget.js" async></script>

Shows only while something is wrong or under way. data-position, data-show, data-accent, data-scheme, data-locale override theme.status.widget.

Iframe card

…/card?embed=1

The headline in its state's colour and what is happening now, for a site that cannot run a script. Links open the full page.

Machine-readable

summary.json, api/v2/*.json (Statuspage-compatible), widget.json, day.json, rss.xml, atom.xml, feed.json, maintenance.ics, uptime.csv

Always answer as an anonymous reader.

The console's Metrics and embeds card prints each snippet, including the React components below.

Importers and integrations

Moving from another status product. POST …/status/imports with source statuspage, instatus or betterstack brings groups, components (as manual components), the incident history and, from Statuspage with an API key, confirmed email subscribers. It runs in the background and notifies nobody. See Importers.

Terraform, Zapier and React. A Terraform provider manages components, groups, incident templates and metrics; a Zapier app triggers on incidents and creates incidents, updates and component states; @forumulate/react has useStatus, ForumulateStatusBadge and ForumulateStatusBanner. See Zapier, Terraform, React and WordPress.

Studio controls

  • The studio's theme settings have a section Status page: every setting that holds every theme.status key, including words, with "Find a setting". Clicking a part of the page offers the keys that draw it.

  • Checks and alerts (the space's sheet) holds the rest, top to bottom: Checks (each check's kind, probe, Probe from, advanced HTTP rules, order), Groups, Telling people, notification channels and credential channels, How the page behaves, Email subscribers, Import a status page, Your own integration accounts, On call, Alerts and integrations, Who can open this page, Metrics and embeds, Incidents and Incidents in other languages.

  • On an incident in Incidents: Pin, hide, banner words, corrections, Repeats (maintenance), Card accent, Post-mortem, Publish and tell subscribers on a draft, Draft the next update with AI, and the incident's own page style card.

In the API

Every operation below is also reachable from the other surfaces without a dedicated command. forumulate api <Operation> calls it from the CLI, the JavaScript SDK has a typed method for each one (the last column names it; the Python and Go clients are generated from the same spec), and an agent reaches the organization-scoped ones through the MCP call_api tool. Where a CLI command or MCP tool exists too, the last column names it. Operation names link to the API reference.

Public Status Subscription

Operation

Request

What it does

Shortcuts

PublicStatusSubscription_view

GET /public/orgs/{orgId}/spaces/{spaceId}/status/subscription

Read my status subscription

client.status.publicSubscription.view()

PublicStatusSubscription_update

PUT /public/orgs/{orgId}/spaces/{spaceId}/status/subscription

Change my status subscription

client.status.publicSubscription.update()

The Forumulate teamUpdated