Skip to content

Metrics (custom time-series)

Metrics are a lightweight way to send arbitrary numeric values (cost of an LLM request, queue length, RPS, account balance, sensor temperature) to Notifly and get time series with charts and threshold alerts.

You create a metric source — it receives a public token with the M prefix — and POST numbers with arbitrary names to POST /metric/<token>. Metrics are created automatically on first arrival: nothing needs to be declared in advance. Each point is expanded into three pre-aggregated buckets (1m, 1h, 1d), which are then returned for charts.

Useful for:

  • tracking AI costs (llm_cost_total, tokens_total) directly from code;
  • business metrics (orders, signups, revenue) without a separate BI system;
  • IoT / telemetry (temperature, humidity, battery) with cheap HTTP ingest;
  • lightweight replacement for Prometheus push-gateway for pet projects and cron scripts.
  1. Open app.notifly.ruMetrics.
  2. Click “Create source”, fill in:
    • Name — for display, e.g. “LLM-расходы” or “Prod Telemetry”.
    • Channel — where to send alerts for metrics from this source.
  3. After creating, copy the public token (M…) — you’ll need it for ingest.
Окно терминала
curl -X POST "$NOTIFLY_URL/metric-source" \
-H "Content-Type: application/json" \
-H "X-Notifly-Key: <client-token>" \
-d '{
"name": "LLM-расходы",
"channelId": 12345
}'

The response contains the source object with a public token (prefix M):

{
"id": 7,
"token": "M7c2a8f3b1e0d4a6c8e9f2b1d",
"channelId": 12345,
"name": "LLM-расходы",
"created": "2026-06-23T10:11:12Z",
"metricCount": 0,
"appName": "Marketing"
}

The full ingest URL is ${NOTIFLY_URL}/metric/M7c2a8f3b1e0d4a6c8e9f2b1d.

Public endpoint without authorization (the token in the URL is the authentication). Accepts three request body formats (application/json) — choose whichever is convenient.

{ "name": "queue_depth", "value": 42 }

A map “name → value”. Update several metrics in one request:

{
"values": {
"llm_cost_total": 0.42,
"tokens_total": 1230,
"queue_depth": 17
}
}

The values field accepts two JSON forms: you can send an object (as above) or an array [{"name":"cost","value":0.42}, {"name":"tokens","value":1230}] — elements without name are ignored, and on duplicate names the last value wins.

Format 3 — points with explicit time points

Section titled “Format 3 — points with explicit time points”

For sending historical points or backfills. Each point can carry its own ts:

{
"points": [
{ "name": "temperature", "value": 21.4, "ts": 1748174400 },
{ "name": "temperature", "value": 21.7, "ts": 1748174460 },
{ "name": "humidity", "value": 55 }
]
}

The ts field is Unix time as a number, seconds or milliseconds (auto-detected by magnitude):

  • Unix seconds — integer < 1e12, e.g. 1748174400;
  • Unix milliseconds — integer >= 1e12, e.g. 1748174400000.

If ts is omitted, the server’s current time (UTC) is used.

All three formats can be combined in one request — points from name/value, values and points are merged into a single list.

The endpoint returns 204 No Content on success. If the body contains no points (e.g. {}) or the token is unknown — it also returns 204 (silently ignored). On exceeding the daily event quota — 429. Invalid JSON, malformed metric name or non-numeric value yield 400.

LimitationValue
Maximum points in one request500
Maximum unique metrics per source100 (points beyond the limit are silently dropped)
Metric nameregex ^[a-zA-Z0-9_\-\.]{1,64}$ — Latin letters, digits, _, -, ., up to 64 chars
Valuefinite number (not NaN, not ±Inf)

A request with a malformed metric name or non-numeric value is entirely rejected with 400 and a clear error description.

Окно терминала
# curl — single value
curl -X POST "$NOTIFLY_URL/metric/M7c2a8f3b1e0d4a6c8e9f2b1d" \
-H "Content-Type: application/json" \
-d '{"name":"llm_cost_total","value":0.0042}'
# curl — batch
curl -X POST "$NOTIFLY_URL/metric/M7c2a8f3b1e0d4a6c8e9f2b1d" \
-H "Content-Type: application/json" \
-d '{"values":{"orders":12,"revenue":3490.5}}'
# Python
import requests
requests.post(
"https://notifly.ru/metric/M7c2a8f3b1e0d4a6c8e9f2b1d",
json={"values": {"llm_cost_total": 0.0042, "tokens_total": 1230}},
timeout=5,
)
// JavaScript / Node
await fetch("https://notifly.ru/metric/M7c2a8f3b1e0d4a6c8e9f2b1d", {
method: "POST",
headers: { "Content-Type": "application/json" },
body: JSON.stringify({ name: "queue_depth", value: 42 }),
});
// Go
body := strings.NewReader(`{"name":"queue_depth","value":42}`)
http.Post("https://notifly.ru/metric/M7c2a8f3b1e0d4a6c8e9f2b1d",
"application/json", body)

Each metric has a type that affects default visualization:

TypeMeaningHow it’s drawn
gaugean instantaneous value that fluctuates up and down (temperature, queue length, balance)uses last / avg of the bucket
countera monotonically increasing counter (total requests, cumulative cost)emphasizes sum over the period

Type is auto-detected by name when the metric is created (convention similar to Prometheus): if the name ends with _total, _count, _sum or _counter — it’s a counter, otherwise gauge. For example llm_cost_totalcounter, queue_depthgauge.

If auto-detection is wrong, you can override the type manuallyPATCH /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"}'

Only gauge and counter are accepted — otherwise 400.

Each point upon arrival is immediately expanded into three buckets — 1m, 1h, 1d. Each bucket accumulates sum, count, min, max and last (the most recent value by time). Buckets have different retention TTLs:

BucketGranularityRetention
1mper-minute24 hours
1hper-hour30 days
1dper-day365 days
Окно терминала
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>"

Parameters:

ParameterValueDefault
bucket1m, 1h or 1d1h
from, tointerval bounds, RFC3339last 24 hours

The response is an array of buckets; the frontend decides what to plot (last for gauge, sum for 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 }
]

If the requested bucket is empty (for example, 1d hasn’t accumulated yet), but per-minute data exists, Notifly aggregates on the fly from 1m buckets.

An alert is a rule “if the metric value satisfies the condition — send a notification to the source channel”. It is evaluated on each incoming point (based on the final metric value in the request, so a batch of hundreds of points doesn’t create duplicate notifications).

Alert fields:

FieldDescription
metricNamethe metric name the rule applies to (required)
operatorcomparison operator: >, >=, <, <=, ==
thresholdthreshold value (number)
titlenotification title; if empty — generated automatically (⚠️ <metric> <op> <threshold>)
cooldownMinutesminimum interval between notifications for this rule; 060 minutes

The channel is not set on the alert — it is always taken from the metric source (the source’s channelId), even if you send channelId in the body, it will be ignored.

Окно терминала
# Alert: LLM costs exceeded $50 in a day
curl -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
}'

When triggered, the source channel will receive a standard push notification with text like:

llm_cost_total = 51.3
Source: LLM-расходы
Threshold: > 50

The public ingest endpoint does not require authorization (token M… in the URL). All other endpoints require a client (or MCP) token; write operations require write permission.

Method & pathAuthorizationPurpose
POST /metric/:tokenpublic (token M)send points (three formats)
GET /metric-sourceclient-tokenlist sources
POST /metric-sourceclient-token (write)create source
PUT /metric-source/:idclient-token (write)update (name, channel)
DELETE /metric-source/:idclient-token (write)delete source
GET /metric-source/:id/metricsclient-tokenlist source metrics
PATCH /metric-source/:id/metrics/:midclient-tokenchange type (gauge/counter)
DELETE /metric-source/:id/metrics/:midclient-token (write)delete metric
GET /metric-source/:id/metrics/:mid/seriesclient-tokentime series (buckets)
GET /metric-source/:id/alertsclient-tokenlist alerts
POST /metric-source/:id/alertsclient-token (write)create alert
PUT /metric-source/:id/alerts/:aidclient-token (write)update alert
DELETE /metric-source/:id/alerts/:aidclient-token (write)delete alert

Related features: Active monitors, Heartbeat, MCP for management via an AI assistant.