Skip to content

Public status pages

A status page is a public address of the form /status/<slug> where anyone (a customer, a colleague, a subscriber) can see whether your services are up. The data comes from monitors and heartbeats you have already configured on the selected channel: nothing extra to set up or send.

The page is served as ready-made HTML — no authentication, no JS framework and no calls to the admin UI.

  • Overall banner — “All systems operational” / “Partial degradation” / “Major outage” / “Scheduled maintenance in progress”, plus the average uptime over 90 days.
  • Service list — one row per monitor or heartbeat: name, type, a colored status mark, uptime over 90 days, average latency over 30 days and a bar of 90 daily cells (a cell’s tooltip shows the date and percentage). A service in a maintenance window is marked “Scheduled maintenance”. The 30-day uptime is available in the JSON snapshot.
  • Incident feed — the last 20 auto-incidents with start time and duration (“Incident” — still ongoing, “Resolved” — closed).
  • Subscription form — a visitor leaves an email and gets a message on every incident and recovery.
  • Footer — the update time and the “powered by Notifly” badge (a link to notifly.ru with UTM tags utm_source=statuspage&utm_medium=badge&utm_campaign=<slug>); the badge can be hidden on the Pro and Business plans.
visitor ──GET /status/<slug>────▶ server-rendered HTML (no authentication)
GET /status/<slug>/summary ──▶ the same snapshot as JSON (for auto-refresh)
channel monitors ──up↔down transition──▶ auto-incident ──▶ email to subscribers

A page is bound to one channel. It automatically shows every event source of that channel that keeps a check history:

SourceType on the page (kind)
Active monitor (ICMP / TCP / HTTP, …)the monitor’s own kind: icmp, tcp, http, …
HTTP monitorhttp
Content monitorcontent
Port monitorport
Workflow monitorworkflow
Browser workflowbrowser_workflow
Heartbeatheartbeat

Every service status is mapped to a single vocabulary:

StatusMeaning
upchecks are passing
degradedpartial degradation
downchecks are failing (for a heartbeat — a missed ping)
pendingno checks yet
pausedthe monitor is paused
maintenancethe service is in an active maintenance window — shown as “Scheduled maintenance”

The overall page status is aggregated across all services:

  • at least one down → down (banner “Major outage”);
  • otherwise a degraded → degraded;
  • otherwise a service in maintenance → maintenance (banner “Scheduled maintenance in progress”);
  • otherwise, if there are services → ok;
  • no services at all → none.

Uptime is computed from the same daily check buckets as the monitor history: the bar has 90 daily cells (up, degraded, down, maintenance, nodata), next to it are the 90-day uptime and the average latency over 30 days (the 30-day uptime is in the JSON). An up cell means all checks of the day succeeded, down — none succeeded, degraded — some failed. If a day has no data, the cell is grey and the percentage is shown as —. Failed checks inside a maintenance window are excluded from uptime; a day whose failures all fell into a window is shown as maintenance.

Incidents are created automatically; you can’t create an incident or post a message on the page manually.

  • a monitor goes down (a heartbeat goes to alerting) → an incident is opened on every status page of the channel (status: "open") with the monitor name as its title;
  • the monitor recovers (the heartbeat gets a ping) → the incident is closed (status: "resolved", with resolvedAt set).

The operation is idempotent: another failure while an incident is already open creates nothing, and a recovery without an open incident closes nothing. The feed shows the last 20 incidents. The check error text is stored with the incident but never reaches the public page or subscriber emails.

A failure inside an active maintenance window opens no incident. If the source is still down after the window, the incident opens together with the deferred alert.

The Status pages section → Create status page. Dialog fields:

FieldWhat it sets
Titlethe <title> and the page header
Slugthe address: /status/<slug>
Channelwhose monitors land on the page (your own channels only)
Descriptionsubtitle under the header
Subscriber notification languageru or en — the language of subscriber emails and of the confirmation/unsubscribe pages
Page publishedoff = draft, the public address returns 404
Hide the “powered by Notifly” badgePro and Business plans only

The page list shows the status (“Published” / “Draft”) and the reach — the number of public page views over 30 days (only HTML page opens are counted, /summary polling is not).

The slug is normalised by the server: lower case, only a-z, 0-9 and hyphens; spaces and underscores become hyphens, repeats are collapsed. For example, My Status Page → my-status-page. Slugs are globally unique: a taken one returns 409.

Окно терминала
curl -X POST "$NOTIFLY_URL/status-page" \
-H "Content-Type: application/json" \
-H "X-Notifly-Key: <api-token>" \
-d '{
"appid": 12345,
"slug": "acme-status",
"title": "ACME service status",
"description": "Current state of the API and the website",
"enabled": true,
"lang": "en",
"brandingHidden": false
}'

The response is the created page object:

{
"id": 987,
"appid": 12345,
"slug": "acme-status",
"title": "ACME service status",
"description": "Current state of the API and the website",
"enabled": true,
"brandingHidden": false,
"lang": "en",
"created": "2026-06-23T10:00:00Z",
"updated": "2026-06-23T10:00:00Z"
}

Required fields are appid, slug and title. The page is immediately available at $NOTIFLY_URL/status/acme-status (for example, https://api.notifly.ru/status/acme-status).

At the bottom of the page there is an email subscription form. It works as double opt-in:

  1. the visitor enters an email → POST /status/<slug>/subscribe;
  2. a confirmation email is sent (GET /status-subscribe/verify?token=…);
  3. after confirming, the subscriber receives an email when each incident is opened and when it is closed;
  4. every email contains an unsubscribe link (GET /status-subscribe/unsub?token=…).

Subscribing again with the same address does not create a duplicate: a confirmed subscriber gets nothing, an unconfirmed one gets the confirmation email again. The mailing is done by the same per-minute delivery worker as notification escalation, so the email arrives within a minute of the incident.

Subscriber emails and the confirmation/unsubscribe pages use the page language (the lang field). The public page UI itself is localised by the visitor’s Accept-Language header (ru/en).

  • The page snapshot is cached for 30 seconds — a traffic spike does not turn into a storm of database queries.
  • An open page polls /status/<slug>/summary every 45 seconds. If the overall status changed, the page reloads; otherwise only the “Updated” timestamp is refreshed.
  • Without JavaScript the page reloads itself every 2 minutes.

GET /status/<slug>/summary returns the same snapshot as JSON — handy for a widget on your own site, a dashboard or a bot:

Окно терминала
curl "$NOTIFLY_URL/status/acme-status/summary"
{
"title": "ACME service status",
"description": "Current state of the API and the website",
"overall": "ok",
"overallUptime90d": 99.94,
"services": [
{
"kind": "http",
"name": "API",
"status": "up",
"uptime30d": 99.98,
"uptime90d": 99.94,
"avgLatencyMs": 148,
"days": [{"date": "2026-03-26", "state": "up", "uptimePct": 100}],
"lastCheckAt": "2026-06-23T09:59:12Z"
}
],
"incidents": [
{
"id": 4242,
"title": "API",
"status": "resolved",
"startedAt": "2026-06-21T14:02:00Z",
"resolvedAt": "2026-06-21T14:19:00Z"
}
],
"brandingHidden": false,
"generatedAt": "2026-06-23T10:00:00Z"
}

days holds 90 cells, oldest first. Uptime percentages are 0..100; -1 means “no data” (for avgLatencyMs too). A service in a maintenance window has status: "maintenance" and maintenance: true; overall takes the values ok, degraded, down, maintenance, none. The endpoint is public: no authentication is needed, while a disabled page (enabled: false) and an unknown slug both return 404.

The snapshot is deliberately narrow: internal identifiers (channel, monitor, the page itself) and the check error text are never exposed publicly — they are visible only to the owner in the admin UI.

CapabilityFreeProBusiness
Number of status pages15Unlimited
Hide the “powered by Notifly” badge—✅✅

Exceeding the page limit returns 403 on creation; enabling a premium field on an unsuitable plan also returns 403 with an explanation in errorDescription. Current limit values are on the Quotas & pricing page.

Managing pages requires an API token (X-Notifly-Key) and channel ownership; modifying requests need a token with write access. The public endpoints work without authentication.

MethodPathAuthenticationDescription
GET/status-pageAPI tokenList your status pages (with the views30d reach)
POST/status-pageAPI token (write)Create a page. Body: appid, slug, title, description, enabled, lang, brandingHidden
PUT/status-page/{id}API token (write)Update a page (same payload)
DELETE/status-page/{id}API token (write)Delete a page
GET/status/{slug}—Public HTML page
GET/status/{slug}/summary—The same snapshot as JSON
POST/status/{slug}/subscribe—Email subscription: { "email": "user@example.com" }
GET/status-subscribe/verify?token=…—Confirm the subscription (link from the email)
GET/status-subscribe/unsub?token=…—Unsubscribe (link from the email)

CRUD response codes: 400 — a missing required field, invalid slug/language or someone else’s channel, 403 — plan limit or a premium field not available on the plan, 404 — on update, the page does not exist or is not yours, 409 — the slug is taken. Deletion responds 204.