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.ru → Monitoring → Metrics.
  2. Click “Create metric source”, fill in:
    • Name — for display, e.g. “LLM costs” or “Prod Telemetry”.
    • Notification channel — where to send alerts for metrics from this source (your own channel or a shared one with send permission). You can leave it as ”—”: points will be accepted, but alerts will have nowhere to go.
  3. After creating, the token (M…) and ready-made integration examples are shown in the source’s row — you’ll need the token for ingest.
Окно терминала
curl -X POST "$NOTIFLY_URL/metric-source" \
-H "Content-Type: application/json" \
-H "X-Notifly-Key: <api-token>" \
-d '{
"name": "LLM costs",
"channelId": 12345
}'

The response contains the source object with a public token (the M prefix and 40 hex characters):

{
"id": 7,
"token": "M7c2a8f3b1e0d4a6c8e9f2b1d4c7e0a9b2f5d8c1e",
"channelId": 12345,
"name": "LLM costs",
"created": "2026-06-23T10:11:12Z",
"metricCount": 0,
"appName": "Marketing"
}

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

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 (points are not written, the owner receives a quota notification); a 429 with a Retry-After header is also possible when requests are too frequent. Invalid JSON, a malformed metric name, a non-numeric value or more than 500 points yield 400.

LimitationValue
Maximum points in one request500
Request body sizeup to 64 KiB
Maximum unique metrics per source100 (points of new metrics 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/M7c2a8f3b1e0d4a6c8e9f2b1d4c7e0a9b2f5d8c1e" \
-H "Content-Type: application/json" \
-d '{"name":"llm_cost_total","value":0.0042}'
# curl — batch
curl -X POST "$NOTIFLY_URL/metric/M7c2a8f3b1e0d4a6c8e9f2b1d4c7e0a9b2f5d8c1e" \
-H "Content-Type: application/json" \
-d '{"values":{"orders":12,"revenue":3490.5}}'
# Python
import requests
requests.post(
"https://api.notifly.ru/metric/M7c2a8f3b1e0d4a6c8e9f2b1d4c7e0a9b2f5d8c1e",
json={"values": {"llm_cost_total": 0.0042, "tokens_total": 1230}},
timeout=5,
)
// JavaScript / Node
await fetch('https://api.notifly.ru/metric/M7c2a8f3b1e0d4a6c8e9f2b1d4c7e0a9b2f5d8c1e', {
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://api.notifly.ru/metric/M7c2a8f3b1e0d4a6c8e9f2b1d4c7e0a9b2f5d8c1e",
"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_total → counter, queue_depth → gauge.

If auto-detection is wrong, you can override the type via the API — 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: <api-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

A source’s page shows its metrics (last value, time, number of points) and a chart of the selected metric for a period from 10 minutes to a year (10m … 1y; 10m and 1h have a Live mode). Tick two or more metrics to get a shared chart for comparison.

Окно терминала
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: <api-token>"

Parameters:

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

An unknown bucket yields 400; unparseable from/to are silently replaced with the defaults.

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”. Thresholds are static only: there is no anomaly detection, no comparison with a previous period and no rate-of-change rules. The rule is evaluated whenever points arrive — against the final (latest by ts) value of the metric 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, otherwise 400)
operatorcomparison operator: >, >=, <, <=, == (required)
thresholdthreshold value (number)
titlenotification title; if empty — generated automatically (⚠️ <metric> <op> <threshold>)
cooldownMinutesminimum interval between notifications for this rule; 0 → 60 minutes

The channel is not set on the alert — it is taken from the metric source (the source’s channelId); a channelId sent in the body is ignored. The channel is fixed in the rule when it is created and saved: if you later change the source’s channel, re-save the alerts. Alerts of a source without a channel are not delivered.

In the dashboard an alert is added on the source’s page (“Add alert”), and the metric name is picked from the metrics that have already arrived. Via the API a rule can be created in advance, for a metric that doesn’t exist yet.

Окно терминала
# 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: <api-token>" \
-d '{
"metricName": "llm_cost_total",
"operator": ">",
"threshold": 50,
"title": "LLM costs exceeded $50",
"cooldownMinutes": 360
}'

When triggered, the source channel receives a regular message with text like the following (the text is currently generated in Russian: “Источник” — source, “Порог” — threshold):

llm_cost_total = 51.3
Источник: LLM costs
Порог: > 50

The message priority is the channel’s default priority. Like any channel message, it is escalated via the configured delivery methods, obeys quiet hours and arrives silently during an active maintenance window of the channel.

The public ingest endpoint does not require authorization (the source token in the URL). All other endpoints require an API token (X-Notifly-Key); write operations require write permission.

Method & pathAuthorizationPurpose
POST /metric/:tokenpublic (source token)send points (three formats)
GET /metric-sourceAPI tokenlist sources
POST /metric-sourceAPI token (write)create source
PUT /metric-source/:idAPI token (write)update (name, channel)
DELETE /metric-source/:idAPI token (write)delete source
GET /metric-source/:id/metricsAPI tokenlist source metrics
PATCH /metric-source/:id/metrics/:midAPI tokenchange type (gauge/counter)
DELETE /metric-source/:id/metrics/:midAPI token (write)delete metric
GET /metric-source/:id/metrics/:mid/seriesAPI tokentime series (buckets)
GET /metric-source/:id/alertsAPI tokenlist alerts
POST /metric-source/:id/alertsAPI token (write)create alert
PUT /metric-source/:id/alerts/:aidAPI token (write)update alert
DELETE /metric-source/:id/alerts/:aidAPI token (write)delete alert

Sending points and managing sources, metrics and alerts are also available in the Go and Python SDKs (PushMetric/PushMetrics, push_metric/push_metrics).

Related features: Active monitors, Heartbeat.