Метрики (custom time-series)
Метрики — это лёгкий способ слать в Notifly произвольные числовые значения (стоимость запроса к LLM, длину очереди, RPS, остаток на балансе, температуру датчика) и получать из них временные ряды с графиками и пороговыми алёртами.
Вы создаёте источник метрик (metric source) — он получает публичный токен
с префиксом M — и шлёте на POST /metric/<token> числа с произвольными
именами. Метрики создаются автоматически при первом поступлении: заранее
ничего объявлять не нужно. Каждая точка раскладывается в три pre-агрегированных
бакета (1m, 1h, 1d), которые потом отдаются для графиков.
Удобно для:
- учёта расходов на AI (
llm_cost_total,tokens_total) прямо из кода; - бизнес-метрик (заказы, регистрации, выручка) без отдельной BI-системы;
- IoT / телеметрии (температура, влажность, заряд) с дешёвым HTTP-ingest;
- лёгкой замены Prometheus push-gateway для pet-проектов и cron-скриптов.
Создание источника метрик
Заголовок раздела «Создание источника метрик»Через админку
Заголовок раздела «Через админку»- Откройте app.notifly.ru → Метрики.
- Нажмите «Создать источник», заполните:
- Название — для отображения, например «LLM-расходы» или «Прод-телеметрия».
- Канал — куда слать алёрты по метрикам этого источника.
- После создания скопируйте публичный токен (
M…) — он понадобится для ingest.
Через REST API
Заголовок раздела «Через REST API»curl -X POST "$NOTIFLY_URL/metric-source" \ -H "Content-Type: application/json" \ -H "X-Notifly-Key: <client-token>" \ -d '{ "name": "LLM-расходы", "channelId": 12345 }'В ответе придёт объект источника с публичным token (префикс M):
{ "id": 7, "token": "M7c2a8f3b1e0d4a6c8e9f2b1d", "channelId": 12345, "name": "LLM-расходы", "created": "2026-06-23T10:11:12Z", "metricCount": 0, "appName": "Marketing"}Полный URL для ingest — ${NOTIFLY_URL}/metric/M7c2a8f3b1e0d4a6c8e9f2b1d.
Отправка точек: POST /metric/:token
Заголовок раздела «Отправка точек: POST /metric/:token»Публичный эндпоинт без авторизации (аутентификация — сам токен в URL).
Принимает три формата тела (application/json) — выбирайте удобный.
Формат 1 — одно значение
Заголовок раздела «Формат 1 — одно значение»{ "name": "queue_depth", "value": 42 }Формат 2 — батч values
Заголовок раздела «Формат 2 — батч values»Карта «имя → значение». За один запрос обновляете сразу несколько метрик:
{ "values": { "llm_cost_total": 0.42, "tokens_total": 1230, "queue_depth": 17 }}Поле values толерантно к двум формам JSON: можно прислать объект (как выше)
или массив [{"name":"cost","value":0.42}, {"name":"tokens","value":1230}] —
элементы без name игнорируются, при повторе имени побеждает последнее значение.
Формат 3 — точки с явным временем points
Заголовок раздела «Формат 3 — точки с явным временем points»Для отправки исторических точек или дозаписи задним числом. Каждая точка может
нести свой ts:
{ "points": [ { "name": "temperature", "value": 21.4, "ts": 1748174400 }, { "name": "temperature", "value": 21.7, "ts": 1748174460 }, { "name": "humidity", "value": 55 } ]}Поле ts — Unix-время числом, секунды или миллисекунды (определяется
автоматически по величине):
- Unix-секунды — целое
< 1e12, например1748174400; - Unix-миллисекунды — целое
>= 1e12, например1748174400000.
Если ts не указан, берётся текущее время сервера (UTC).
Все три формата можно комбинировать в одном запросе — точки из name/value,
values и points объединяются в общий список.
Ответ и квота
Заголовок раздела «Ответ и квота»Эндпоинт возвращает 204 No Content при успехе. Если в теле нет точек
(например {}) или токен неизвестен — тоже 204 (тихо игнорируется). При
превышении дневной квоты событий — 429. Невалидный JSON, битое имя метрики
или нечисловое значение дают 400.
Лимиты ingest
Заголовок раздела «Лимиты ingest»| Ограничение | Значение |
|---|---|
| Максимум точек в одном запросе | 500 |
| Максимум уникальных метрик в источнике | 100 (точки сверх лимита тихо пропускаются) |
| Имя метрики | regex ^[a-zA-Z0-9_\-\.]{1,64}$ — латиница, цифры, _, -, ., до 64 символов |
| Значение | конечное число (не NaN, не ±Inf) |
Запрос с битым именем метрики или нечисловым значением целиком отклоняется
с 400 и понятным описанием ошибки.
Примеры по языкам
Заголовок раздела «Примеры по языкам»# curl — одно значениеcurl -X POST "$NOTIFLY_URL/metric/M7c2a8f3b1e0d4a6c8e9f2b1d" \ -H "Content-Type: application/json" \ -d '{"name":"llm_cost_total","value":0.0042}'
# curl — батчcurl -X POST "$NOTIFLY_URL/metric/M7c2a8f3b1e0d4a6c8e9f2b1d" \ -H "Content-Type: application/json" \ -d '{"values":{"orders":12,"revenue":3490.5}}'# Pythonimport requestsrequests.post( "https://notifly.ru/metric/M7c2a8f3b1e0d4a6c8e9f2b1d", json={"values": {"llm_cost_total": 0.0042, "tokens_total": 1230}}, timeout=5,)// JavaScript / Nodeawait fetch("https://notifly.ru/metric/M7c2a8f3b1e0d4a6c8e9f2b1d", { method: "POST", headers: { "Content-Type": "application/json" }, body: JSON.stringify({ name: "queue_depth", value: 42 }),});// Gobody := strings.NewReader(`{"name":"queue_depth","value":42}`)http.Post("https://notifly.ru/metric/M7c2a8f3b1e0d4a6c8e9f2b1d", "application/json", body)Тип метрики: gauge vs counter
Заголовок раздела «Тип метрики: gauge vs counter»У каждой метрики есть тип, влияющий на визуализацию по умолчанию:
| Тип | Смысл | Как рисуется |
|---|---|---|
gauge | моментальное значение, которое колеблется вверх-вниз (температура, длина очереди, баланс) | по last / avg бакета |
counter | монотонно растущий счётчик (всего запросов, суммарная стоимость) | акцент на sum за период |
Тип определяется автоматически по имени при создании метрики (конвенция в
духе Prometheus): если имя оканчивается на _total, _count, _sum или
_counter — это counter, иначе gauge. Например llm_cost_total →
counter, queue_depth → gauge.
Если авто-определение ошиблось, тип можно переопределить вручную —
PATCH /metric-source/:id/metrics/:mid:
curl -X PATCH "$NOTIFLY_URL/metric-source/7/metrics/31" \ -H "Content-Type: application/json" \ -H "X-Notifly-Key: <client-token>" \ -d '{"type":"gauge"}'Допустимы только значения gauge и counter — иначе 400.
Хранение и временные ряды
Заголовок раздела «Хранение и временные ряды»Каждая точка при поступлении раскладывается сразу в три бакета — 1m, 1h,
1d. В каждом бакете накапливаются sum, count, min, max и last
(последнее по времени значение). У бакетов разный срок хранения (TTL):
| Бакет | Гранулярность | Хранится |
|---|---|---|
1m | поминутно | 24 часа |
1h | почасово | 30 дней |
1d | посуточно | 365 дней |
Чтение ряда — GET .../series
Заголовок раздела «Чтение ряда — GET .../series»curl "$NOTIFLY_URL/metric-source/7/metrics/31/series?bucket=1h&from=2026-06-22T00:00:00Z&to=2026-06-23T00:00:00Z" \ -H "X-Notifly-Key: <client-token>"Параметры:
| Параметр | Значение | По умолчанию |
|---|---|---|
bucket | 1m, 1h или 1d | 1h |
from, to | границы интервала, RFC3339 | последние 24 часа |
Ответ — массив бакетов; фронт сам решает, что рисовать (last для gauge,
sum для counter):
[ { "ts": "2026-06-22T10:00:00Z", "sum": 4.2, "count": 60, "min": 0.01, "max": 0.21, "last": 0.07 }, { "ts": "2026-06-22T11:00:00Z", "sum": 3.9, "count": 58, "min": 0.01, "max": 0.18, "last": 0.05 }]Если запрошенный бакет пуст (например, 1d ещё не успел накопиться), но
поминутные данные есть, Notifly агрегирует на лету из 1m-бакетов.
Алёрты по порогу
Заголовок раздела «Алёрты по порогу»Алёрт — это правило «если значение метрики удовлетворяет условию — отправить уведомление в канал источника». Проверяется при каждом поступлении точки (по финальному значению метрики в запросе, чтобы батч из сотен точек не плодил дубли уведомлений).
Поля алёрта:
| Поле | Описание |
|---|---|
metricName | имя метрики, к которой применяется правило (обязательное) |
operator | оператор сравнения: >, >=, <, <=, == |
threshold | пороговое значение (число) |
title | заголовок уведомления; если пусто — генерируется автоматически (⚠️ <metric> <op> <threshold>) |
cooldownMinutes | минимальный интервал между уведомлениями этого правила; 0 → 60 минут |
Канал не задаётся в алёрте — он всегда берётся из источника метрик (поле
channelId источника), даже если прислать channelId в теле, оно игнорируется.
# Алёрт: расходы на LLM за сутки превысили $50curl -X POST "$NOTIFLY_URL/metric-source/7/alerts" \ -H "Content-Type: application/json" \ -H "X-Notifly-Key: <client-token>" \ -d '{ "metricName": "llm_cost_total", "operator": ">", "threshold": 50, "title": "LLM-расходы превысили $50", "cooldownMinutes": 360 }'При срабатывании в канал источника прилетит обычное push-уведомление с текстом вида:
llm_cost_total = 51.3
Источник: LLM-расходы
Порог: > 50REST API
Заголовок раздела «REST API»Публичный ingest-эндпоинт не требует авторизации (токен M… в URL).
Все остальные требуют клиентский (или MCP) токен; операции записи —
с правом write.
| Метод и путь | Авторизация | Назначение |
|---|---|---|
POST /metric/:token | публичный (токен M) | отправить точки (три формата) |
GET /metric-source | client-token | список источников |
POST /metric-source | client-token (write) | создать источник |
PUT /metric-source/:id | client-token (write) | обновить (название, канал) |
DELETE /metric-source/:id | client-token (write) | удалить источник |
GET /metric-source/:id/metrics | client-token | список метрик источника |
PATCH /metric-source/:id/metrics/:mid | client-token | сменить тип (gauge/counter) |
DELETE /metric-source/:id/metrics/:mid | client-token (write) | удалить метрику |
GET /metric-source/:id/metrics/:mid/series | client-token | временной ряд (бакеты) |
GET /metric-source/:id/alerts | client-token | список алёртов |
POST /metric-source/:id/alerts | client-token (write) | создать алёрт |
PUT /metric-source/:id/alerts/:aid | client-token (write) | обновить алёрт |
DELETE /metric-source/:id/alerts/:aid | client-token (write) | удалить алёрт |
Смежные возможности: Активные мониторы, Heartbeat, MCP для управления из AI-ассистента.