Status pages: upstreams, maintenance from CI, SLA reports and the in-app banner
A status page says what is wrong with your product. This guide covers the parts that connect it to everything around it: the services you depend on, the pipeline that deploys you, the people who want a number for the month, and the app your users are actually looking at.
Upstream components
Your product rides on other services — a payments provider, an identity provider, a CDN, or another of your own products. Declare them once and the page shows their state beside yours, and their incidents as yours:
PUT /api/orgs/{org}/spaces/{status}/upstreams
[
{ "label": "Payments", "source": { "kind": "statuspage", "url": "https://status.stripe.com" },
"components": ["API"] },
{ "label": "Sign-in", "source": { "kind": "space", "org": "acme-id", "space": "status" } },
{ "label": "CDN", "source": { "kind": "statuspage", "url": "https://status.example-cdn.com",
"format": "instatus" } }
]Any provider.
kind: "statuspage"reads any Statuspage-compatiblesummary.json— Atlassian Statuspage, Instatus, Better Stack, a self-hosted page, anyone's.formatpicks the reader:statuspage(default),instatus,betterstack, orfeedfor an RSS/Atom status feed (Statuspage'shistory.rss, ours, AWS's — posts that share a link are one incident, each post one of its updates). Pasting the page's address is enough.Our own pages are a source too:
kind: "space"with an org and space (id or slug), in this organization or another. They are read in-process, not over the network — use this rather than the page's URL for a page hosted here, custom domain or not. Another organization's page must be public, or its owner must grant yours (see Suite status page).A platform by name.
{ "kind": "org", "org": "atlas" }means that organization's public status page here, so a product that signs in with Atlas shows "Upstream: Sign-in degraded" with nothing but{ "label": "Sign-in", "source": { "kind": "org", "org": "atlas" } }.atlas,cookiemunch,watchtowerandforumulateall publish one. It is stored as thekind: "space"form naming that page, and reads back that way. An organization with no public status page answers404saying so (usekind: "statuspage"with its address). One with several asks you to name the page.Components: the source's components by id or exact name. Omit them to follow the source page overall.
Each upstream becomes component rows on your page (grouped under its label; each row's name links the source's page). They are read at once, then every minute — a conditional GET (
If-None-Match/If-Modified-Since), so an unchanged source answers304. A source that keeps failing backs off (up to eight minutes) and the page keeps its last word rather than painting their bars red for our network. One of our pages is also re-read the moment it changes, so its incidents reach your page in seconds.displaydecides what the rows draw (defaultstatus)."Upstream: X degraded." While an upstream is not operational the page,
widget.jsonand the in-app banner print that sentence, with "via <source>" linking the source's own page.Incidents are mirrored, with every update. An open incident on the source appears on your page as "Upstream: <label> — <title>"; each update the source posts is posted here once, in order, moving the state as theirs moves; when the source resolves it, it is resolved here with the source's own closing words (from our pages, or a Statuspage's
incidents.json). Every mirrored incident carriesmirroredFrom—{ upstreamId, label, source, url }, the source's name and its page for the incident — and the reader shows "Mirrored from <source>". Your subscribers are told through their one subscription to your page — subscribe once, hear about every dependency. Turn it off per upstream withmirrorIncidents: false.GET …/upstreamslists them with their live reading, the source's name and page as last read (sourceName,sourceUrl) and why the last read failed (error;deniedwhen the source no longer lets your page read it). An upstream left out of the nextPUTis removed with its rows; its mirrored incidents are resolved.A page that already reads yours (directly or through others) cannot be made your upstream: mirroring it back would echo each page's incidents into the other (
422). Our pages never serve their own mirrored incidents to a mirror, so a chain (Cookie Munch → Referalli → the suite) does not repeat itself.
Suite status page
A suite page shows several products at once — each product's own status space, composed on one page — with every product's incidents on it as they happen. It is an ordinary status space whose upstreams are the products, with relation: "product": on a suite page a product is not upstream of anything, it is part of the page, so its notice reads "Cookie Munch degraded" and its mirrored incidents "Cookie Munch: Elevated consent errors".
PUT /api/orgs/{suite-org}/spaces/{suite-status}/upstreams
[
{ "label": "Cookie Munch", "relation": "product",
"source": { "kind": "space", "org": "cookiemunch", "space": "status" } },
{ "label": "Referalli", "relation": "product",
"source": { "kind": "space", "org": "referalli", "space": "status" } }
]A product page can list another product as a dependency in the same way — Referalli's page showing "Consent (Cookie Munch)":
PUT /api/orgs/referalli/spaces/status/upstreams
[
{ "label": "Consent (Cookie Munch)",
"source": { "kind": "space", "org": "cookiemunch", "space": "status" } }
]A Cookie Munch incident is then on the suite page and on Referalli's page within seconds (pushed), and within a few minutes at worst (the minute poll is the backstop). Each product page stays the place its incidents are written; the suite and the dependants only mirror them.
Who may compose whom. A product page that is public (and not behind a reader sign-in or a network allow-list) may be mirrored by anyone — its summary.json is public anyway. A page that is private or gated may be mirrored only by its own organization's pages and by the organizations its owner names:
PUT /api/orgs/cookiemunch/spaces/status/status/mirror-grants
{ "organizations": ["acme-suite"] }GET …/status/mirror-grants lists them, says whether the page is public, and counts how many pages in how many organizations mirror it now (counts only: which organizations read a page is theirs to say). Taking an organization off the list stops its mirror at once, and its mirrored incidents are closed with "no longer shares its status". A mirror sees what an anonymous reader would: components and incidents limited to an audience, drafts and unpublished post-mortems stay out. In the studio: Upstream services (the list, with "This page treats it as: a product of this suite") and Mirroring consent. CLI: forumulate status upstreams set --data @suite.json, forumulate status mirror-grants set --data '{"organizations":["acme-suite"]}'.
A product hosted elsewhere joins the same way with kind: "statuspage" and its address — any Statuspage-compatible page, Instatus, Better Stack, or a feed — read by polling instead of push.
Maintenance windows from CI
forumulate status maintenance --org acme --space status \
--from 2026-11-02T22:00:00Z --to 2026-11-02T23:00:00Z \
--components api,dashboard --title "Database upgrade" --key "$CI_PIPELINE_ID"POST /api/orgs/{org}/spaces/{status}/status/maintenance takes the window the way a pipeline has it: from, to, components by id, slug or name, and an optional key. The same key again updates that window instead of announcing a second one, so a retried job is harmless (201 created, 200 updated). The window starts and completes itself at its times; subscribers are told as for any maintenance (notifySubscribers: false to skip).
Incident templates
Saved incidents live at …/status/templates; forumulate status templates list and forumulate status templates use <id> open one in a step.
The SLA report
GET …/status/sla-report?days=30&target=99.9 measures every component over the period against the target: uptime, met or breached, the downtime budget and what was used, and the incidents in the period. sections is the same report laid out for a PDF (headings, columns, rows). …/sla-report.csv gives the rows as CSV. The target comes from the request, else the page's slaTarget setting, else the organization's status.sla_target, else 99.9. CLI: forumulate status sla, forumulate status sla csv --out sla.csv.
The in-app banner
<script src="https://status.acme.com/api/public/orgs/acme/spaces/status/status/banner.js" async></script>While an incident, maintenance window or upstream problem is active, a one-line bar names it and links to your page. It makes one request (to widget.json beside it), loads nothing from anyone else, and stores nothing — dismissing it lasts the page view — so it needs no cookie consent. By default it overlays the top edge (no layout shift); to place it in the flow, reserve an element and name it: data-target="#status-slot". data-position="bottom", data-show, data-accent, data-scheme and data-locale work as on the floating widget.
Private events
Machines can tell staff things the public should not see: POST …/status/events with an events key (forumulate status event-keys create "CI", status:write on that page only) or a partner link carrying status:write.
{ "id": "evt_123", "type": "webhook_endpoint.failing", "subject": "https://hooks.acme.com/fm",
"state": "failing", "title": "Webhook endpoint is failing", "detail": "HTTP 503 for 1h" }The same id twice is stored once. Staff read them at …/status/events (?open=true for unacknowledged) and acknowledge them. A page can also receive our own webhook-health events by setting eventFeeds.webhookHealth: true in its settings. Events never reach the public page or its subscribers.
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 SDKs are generated from the same spec), and an agent reaches the organization-scoped ones through the MCP agent server — its api_* family tools, or call_api. Where a CLI command or a hand-made MCP tool exists too, the last column names it. Operation names link to the API reference.
Status Depth
Operation | Request | What it does | Shortcuts |
|---|---|---|---|
| List the services this status page depends on |
| |
| Replace the services this status page depends on |
| |
| Change the services this status page depends on |
| |
| Who may mirror this status page |
| |
| Replace who may mirror this status page |
| |
| Schedule a maintenance window (from CI) |
| |
| The uptime SLA report |
| |
| The uptime SLA report as CSV |
| |
| The page’s private events feed |
| |
| Acknowledge a private event |
| |
| List the page’s events keys |
| |
| Mint an events key |
| |
| Revoke an events key |
|