Status pages
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 withPATCH /orgs/{orgId}/spaces/{spaceId}/theme. Settingtheme.statuson a space of another type is refused with a422. 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
422that names it.
Where a component's state comes from
This is the one default that changed, so it comes first.
| What decides a component's state |
|---|---|
| Its latest checks: operational while they pass, degraded while a few fail, an outage once the failures reach its |
| The latest checks first, then a poor uptime window can degrade a passing check: under 99.5% reads |
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.
| Modelled on | The keys it sets |
|---|---|---|
| — |
|
| Statuspage |
|
| Instatus |
|
| Better Stack |
|
| incident.io |
|
| Openstatus |
|
| — | 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 |
|---|---|---|
|
| Each state's colour. Unset: your palette's success/warning/danger/info. Reaches the badge, widget, footer pill and tab icon too. |
|
| The overall banner. |
|
| The day strip. |
|
| The component rows and groups. |
|
| A roll-up strip per group; names through your translated labels; the percentage even with the caption on. |
|
| The strip's legend. |
|
| Incident cards on the board. |
|
| The recorded-outages band. |
|
| The page as a whole. |
|
| The card a |
|
| The title row. |
|
| 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). |
|
| A drawn header behind the top of the page, in the title-band vocabulary. |
|
| Spacing and section headings. |
|
| The KPI band. |
|
| Response-time and custom metric charts. |
|
| An uptime target: a figure below it is drawn in the outage colour. |
|
| The subscribe band; a component picker and an incidents/maintenance choice. |
|
| The vendor-maintenance band. |
|
| The floating widget on your own site (below). |
|
| Date words, and a named zone instead of UTC. A reader's own pick with the zone switch still wins. |
|
| Changes under 640px, on top of |
|
| A printed page. Unset |
| see Sub-pages | The sub-pages. |
|
| 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 |
|---|---|
| The incident archive, by month, with a component filter and text search. |
| Each component's uptime as month calendars. |
| Upcoming (and past) maintenance, with a calendar file ( |
| One component: its state, regions, description, response time, uptime and incidents. |
| One incident or maintenance window, with its updates and post-mortem. |
| A subscriber's own page to change what they hear about (below). |
| One row per component per day: |
subpages shapes all of them at once, and each page has its own composition:
Key | Default | What it does |
|---|---|---|
|
|
|
|
| The board's hero on sub-pages too: |
| off | The board's title row and actions above each sub-page instead of the plain way back. |
| none | Markdown above the page (a note, a support link). |
| built-in | The page's heading in your words. |
| 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 |
|---|---|---|
|
|
|
|
|
|
| 3 | 1, 3, 6 or 12. |
| 5 | Incidents per month before "Show all"; 0 lists all. |
| on / on | The component filter; the board's link to the archive. |
|
| The updates as a rail with a dot each, or a plain |
|
| Newest first, or |
| on / off | Affected components as chips; who posted each update. |
| all allowed | The shared item actions (copy link, share, Markdown). |
| 3 / | The calendars. |
|
| 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 |
|---|---|---|
|
|
|
| none | Post now, tell subscribers later: the notice goes out at this time instead. |
|
| Untick for a typo fix or a note nobody needs mailed. On |
| now | Backfill: when it really began and ended. Posting one already resolved marks it Retroactive. |
|
| Maintenance moves to in progress at |
| everyone | Who sees it on the public page ( |
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 |
|---|---|
| Above everything else: in the active band while open, first in the history once resolved. |
| Out of the public history, archive and feeds once resolved (a test, a mistaken post). It still colours its days. |
| The banner's headline while it is open, in your words (≤160). |
| Its card accent, a palette slot ( |
| Maintenance only: |
| The incident page's own piece: |
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 |
|---|---|---|---|
| board | — | Above the board's bands, once (not inside a band). |
| board | — | Below the board and the history. |
| board, component page (repeats) |
| Under every component row. |
| every sub-page |
| Under the back link. |
| every sub-page |
| At the end of its content. |
| incident page |
| Under the title and impact, above the updates. |
| 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: | Alerts and integrations → Text messages, or | The page does not offer SMS. |
Telegram for readers | Your bot: | Your own integration accounts, | The page does not offer Telegram. |
Slack | Your Slack app: | Your own integration accounts, | The command answers "This command is not set up." |
Mastodon / X / Telegram channel |
| Post to Mastodon, Bluesky, X, Telegram, Jira or Zendesk | No such channel. |
Bluesky channel |
| as above | No such channel. |
Jira / Zendesk channel | Jira | as above | No such channel. |
AI-drafted updates | Your organization's AI provider | Organization settings | No draft is made; the route answers |
Pulled metrics | The provider's keys | 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 |
|---|---|---|
| org's, else | |
| org's |
|
| on | Email subscribers when a check opens an incident. |
| 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. |
|
| Hours before a window to remind subscribers, up to three of 1, 6, 24, 48, 72. |
| on / on | Tell subscribers when a window starts and completes. |
| on | No operator alert or automatic incident for a component under active maintenance. |
| on | Maintenance is not downtime. Off: a window costs a manual component's days like an outage. |
|
| The text message, with |
| the org's mail default | Where a reply to a status email goes. |
| all on |
|
| 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 |
|
|
Floating widget |
| Shows only while something is wrong or under way. |
Iframe card |
| 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 |
| 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.statuskey, includingwords, 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 |
|---|---|---|---|
| Read my status subscription |
| |
| Change my status subscription |
|