Перейти к содержимому

Публичные статус-страницы

Статус-страница — это публичный адрес вида /status/<slug>, на котором любой человек (клиент, коллега, подписчик) видит, работают ли ваши сервисы. Данные берутся из уже настроенных мониторов и heartbeat-ов выбранного канала: ничего дополнительно настраивать и слать не нужно.

Страница отдаётся сервером как готовый HTML — без авторизации, без JS-фреймворка и без обращения к админке.

  • Общий баннер — «Все системы работают» / «Частичная деградация» / «Сбои в работе сервисов» и средний аптайм за 90 дней.
  • Список сервисов — по одной строке на монитор: имя, тип, текущий статус, аптайм за 30 и 90 дней, средний отклик за 30 дней и полоса из 90 дневных ячеек.
  • Лента инцидентов — последние 20 авто-инцидентов с длительностью и отметкой «Восстановлено».
  • Форма подписки — посетитель оставляет email и получает письмо при каждом инциденте и восстановлении.
посетитель ──GET /status/<slug>────▶ server-rendered HTML (без авторизации)
GET /status/<slug>/summary ──▶ тот же срез в JSON (для авто-обновления)
мониторы канала ──переход up↔down──▶ авто-инцидент ──▶ письмо подписчикам

Страница привязана к одному каналу. На неё автоматически попадают все источники событий этого канала, которые ведут историю проверок:

ИсточникТип на странице (kind)
Активный монитор (ICMP / TCP / HTTP и т.п.)собственный вид монитора: icmp, tcp, http, …
HTTP-мониторhttp
Контент-мониторcontent
Порт-мониторport
Workflow-мониторworkflow
Браузерный workflowbrowser_workflow
Heartbeatheartbeat

Статус каждого сервиса приводится к единому словарю:

СтатусЗначение
upпроверки проходят
degradedчастичная деградация
downпроверки падают (у heartbeat — пропущен ping)
pendingпроверок ещё не было
pausedмонитор на паузе

Общий статус страницы собирается по всем сервисам:

  • есть хотя бы один downdown (баннер «Сбои в работе сервисов»);
  • иначе есть degradeddegraded;
  • иначе, если сервисы есть → ok;
  • сервисов нет вовсе → none.

Аптайм считается из тех же дневных бакетов проверок, что и история мониторов: полоса — 90 дневных ячеек (up, degraded, down, nodata), рядом — проценты за 30 и 90 дней и средний отклик за 30 дней. Если данных за день нет, ячейка серая, а процент показывается как .

Инциденты создаются автоматически и не требуют ручного ведения:

  • монитор перешёл в «падение» → на каждой статус-странице канала открывается инцидент (status: "open") с заголовком монитора и текстом последней ошибки;
  • монитор восстановился → инцидент закрывается (status: "resolved", проставляется resolvedAt).

Операция идемпотентна: повторное падение при уже открытом инциденте ничего не создаёт, а восстановление без открытого инцидента ничего не закрывает. В ленте показываются последние 20 инцидентов.

Раздел Статус-страницыСоздать статус-страницу. Поля диалога:

ПолеЧто задаёт
Заголовок<title> и шапка страницы
Slugадрес: /status/<slug>
Каналчьи мониторы попадут на страницу
Описаниеподзаголовок под шапкой
Язык уведомлений подписчикамru или en — язык писем подписчикам и служебных страниц подтверждения/отписки
Страница опубликованавыключено = черновик, публичный адрес отвечает 404
Скрыть бейдж «powered by Notifly»только на тарифах Pro и Business

Slug нормализуется сервером: нижний регистр, только a-z, 0-9 и дефис; пробелы и подчёркивания превращаются в дефисы, повторы схлопываются. Например, My Status Pagemy-status-page. Slug уникален глобально: занятый вернёт 409.

Окно терминала
curl -X POST "$NOTIFLY_URL/status-page" \
-H "Content-Type: application/json" \
-H "X-Notifly-Key: <client-token>" \
-d '{
"appid": 12345,
"slug": "acme-status",
"title": "Статус сервисов ACME",
"description": "Актуальное состояние API и сайта",
"enabled": true,
"lang": "ru",
"brandingHidden": false
}'

Ответ — созданный объект страницы:

{
"id": 987,
"appid": 12345,
"slug": "acme-status",
"title": "Статус сервисов ACME",
"description": "Актуальное состояние API и сайта",
"enabled": true,
"brandingHidden": false,
"lang": "ru",
"created": "2026-06-23T10:00:00Z",
"updated": "2026-06-23T10:00:00Z"
}

Страница сразу доступна по адресу $NOTIFLY_URL/status/acme-status (например, https://api.notifly.ru/status/acme-status).

Внизу страницы — форма подписки по email. Работает по схеме double opt-in:

  1. посетитель вводит email → POST /status/<slug>/subscribe;
  2. на адрес уходит письмо со ссылкой подтверждения (GET /status-subscribe/verify?token=…);
  3. после подтверждения подписчик получает письмо при открытии и при закрытии каждого инцидента;
  4. в каждом письме есть ссылка отписки (GET /status-subscribe/unsub?token=…).

Повторная подписка тем же адресом не создаёт дубль: подтверждённому подписчику ничего не шлётся, неподтверждённому письмо отправляется заново. Рассылку ведёт тот же минутный воркер доставки, что и эскалацию уведомлений, поэтому письмо приходит в пределах минуты после инцидента.

Письма подписчикам и страницы подтверждения/отписки идут на языке страницы (поле lang). Интерфейс самой публичной страницы локализуется по заголовку Accept-Language посетителя (ru/en).

  • Агрегат страницы кэшируется на 30 секунд — всплеск трафика не превращается в шторм запросов к базе.
  • Открытая страница раз в 45 секунд опрашивает /status/<slug>/summary. Если общий статус изменился — страница перезагружается, иначе просто обновляется отметка «Обновлено».

GET /status/<slug>/summary отдаёт тот же срез в JSON — удобно для виджета на своём сайте, дашборда или бота:

Окно терминала
curl "$NOTIFLY_URL/status/acme-status/summary"
{
"title": "Статус сервисов ACME",
"description": "Актуальное состояние API и сайта",
"overall": "ok",
"overallUptime90d": 99.94,
"services": [
{
"kind": "http",
"id": 555,
"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,
"statusPageId": 987,
"appid": 12345,
"entityKind": "http_monitor",
"entityId": 555,
"title": "API",
"status": "resolved",
"startedAt": "2026-06-21T14:02:00Z",
"resolvedAt": "2026-06-21T14:19:00Z",
"lastEventAt": "2026-06-21T14:19:00Z",
"lastError": "HTTP 502 from https://api.example.com/health"
}
],
"brandingHidden": false,
"generatedAt": "2026-06-23T10:00:00Z"
}

Проценты аптайма — 0..100; значение -1 означает «нет данных». Endpoint публичный: авторизация не нужна, отключённая страница (enabled: false) и неизвестный slug отвечают 404.

ВозможностьFreeProBusiness
Количество статус-страниц15Безлимит
Скрыть бейдж «powered by Notifly»
Свой домен (customDomain)

Превышение лимита страниц вернёт 403 при создании; попытка включить премиум-поле на неподходящем тарифе — тоже 403 с пояснением в errorDescription. Актуальные значения лимитов — на странице Квоты и тарифы.

Управление страницами требует клиентский (или MCP) токен и права владельца канала; публичные endpoints работают без авторизации.

МетодПутьАутентификацияОписание
GET/status-pageclient-tokenСписок своих статус-страниц
POST/status-pageclient-token (write)Создать страницу. Body: appid, slug, title, description, enabled, lang, brandingHidden, customDomain
PUT/status-page/{id}client-token (write)Обновить страницу (тот же payload)
DELETE/status-page/{id}client-token (write)Удалить страницу
GET/status/{slug}Публичная HTML-страница
GET/status/{slug}/summaryТот же срез в JSON
POST/status/{slug}/subscribeПодписка по email: { "email": "user@example.com" }
GET/status-subscribe/verify?token=…Подтверждение подписки (ссылка из письма)
GET/status-subscribe/unsub?token=…Отписка (ссылка из письма)

Коды ответов CRUD: 400 — некорректный slug/язык или чужой канал, 403 — лимит тарифа или премиум-поле не по плану, 404 — чужая/несуществующая страница, 409 — slug занят.