Skip to content

REST API Documentation

Notifly provides a REST API for managing channels, clients, messages, monitoring, metrics, and users. All examples use the API base URL https://api.notifly.ru (you can substitute it using the $NOTIFLY_URL variable). https://notifly.ru is the documentation site; there is no API there. The admin UI (web interface) lives at https://app.notifly.ru/.

Notifly tokens are distinguished by their first letter. Every token is 23 characters long (prefix + 22).

TypePrefixPurpose
Channel key (app token)ASending to its own channel (POST /message, POST /ask, monitoring receivers); with read permission, also reading that channel’s messages
Channel access tokenSThe recipient’s key for delegated access: same as A…, but sending only with the send permission
API tokenMProgrammatic access to the account (read or read+write, scoped by channel), see API tokens
Device tokenCAn app or browser session: everything an API token can do, plus managing devices, API tokens, password and 2FA. Not a key for integrations
Heartbeat ping tokenHOnly in the /heartbeat/ping/{token} URL
Webhook router tokenROnly in the /router/{token}/… URL

Web-script tokens (T…) and metric-source tokens (M…, but not an API token) likewise live only in the URL of their public endpoints and grant no access to the account.

The token is passed in one of these ways (if several are sent, the first one in this list wins):

  1. the X-Notifly-Key header;
  2. the X-Gotify-Key header (legacy, for compatibility with Gotify clients);
  3. the Authorization: Bearer … header;
  4. the ?token= query parameter — for transports without headers (WebSocket, SSE, <img>);
  5. the notifly-client-token cookie — set by POST /auth/local/login for the browser.
Окно терминала
# 1. X-Notifly-Key header
curl -H "X-Notifly-Key: MtVOKLDNuD7HR9y7QkMecC5" https://api.notifly.ru/application
# 2. Query parameter
curl "https://api.notifly.ru/application?token=CK2UDmhduKshCyz-RtqxiLx"
# 3. Bearer token
curl -H "Authorization: Bearer CK2UDmhduKshCyz-RtqxiLx" https://api.notifly.ru/application

Channel tokens (A…/S… with read permission) are also accepted on some client endpoints, but see only their own channel: GET /application returns just that channel, GET /message and GET /message/search return only its messages. Account-level operations (devices, API tokens, monitors, the user) are forbidden for them — 403.

Basic Auth (username/password) is also supported — but only on the endpoints a client needs before it has a token:

EndpointPurpose
POST /auth/local/loginexchange username and password for a device token
GET /current/userverify the credentials
GET /client, GET /client/online, POST /client, POST /client/:id/reconnectcreate a device or reconnect an existing one
POST /applicationcreate a channel to get its app token
POST /web-script/:id/sourcemapsupload source maps from CI

Every other endpoint rejects username/password — a token is required. Login attempts are rate-limited per source address, and an account with an unverified email is not let in anywhere. An account with 2FA enabled cannot pass with a Basic pair (401 second factor required) — only via POST /auth/local/login and the second step.

In the examples below, $TOKEN is an API token (M…) from the API tokens page of the admin UI or a device token (C…) from the POST /auth/local/login response. Where a device token specifically is required, this is stated explicitly.


Endpoints are available without authentication.

Окно терминала
curl https://api.notifly.ru/health
{"health": "green", "database": "green"}

If the database is unavailable — 503 with "health": "red".

Окно терминала
curl https://api.notifly.ru/version
{"version": "ya-1.0.0", "commit": "...", "buildDate": "..."}
Окно терминала
curl https://api.notifly.ru/serverinfo
{"version": "ya-1.0.0", "register": false, "oidc": false}

POST /auth/local/login — login with username/password

Section titled “POST /auth/local/login — login with username/password”

The username and password are passed via Basic Auth. Creates a device, returns its token (the full value appears only in this response) and sets a cookie. Optional body fields: name (device name) and platform.

Окно терминала
curl -u user@example.com:password https://api.notifly.ru/auth/local/login \
-X POST -d "name=my-cli-client"
{
"id": 1,
"name": "my-cli-client",
"token": "CK2UDmhduKshCyz-RtqxiLx",
"user_id": 1,
"platform": "web"
}

If the plan’s device limit is reached, the oldest active device is suspended and the response includes suspended_device_id. If the account has 2FA enabled, instead of a device you get 401 with an mfa field — see Two-factor login.

Rotates the device token and clears the cookie. Responds 200 even with an invalid token.

Окно терминала
curl -H "X-Notifly-Key: CK2UDmhduKshCyz-RtqxiLx" \
https://api.notifly.ru/auth/logout -X POST

For an account with 2FA, a correct password on POST /auth/local/login returns a challenge instead of a device, with status 401:

{
"error": "Unauthorized",
"errorCode": 401,
"errorDescription": "second factor required",
"mfa": {
"challenge": "…",
"methods": ["totp", "mobile", "recovery"],
"mobileCode": "47",
"expiresIn": 300
}
}

mobile is offered only if sign-in approval from a phone is enabled in the profile. The challenge lives for 5 minutes and is single-use.

Method and pathAuthenticationDescription
POST /auth/mfa/verifychallenge in bodySecond step. Body: challenge, method (totp|recovery|mobile), code (for totp/recovery), optional name, platform. Success — the same response as login. Wrong code — 401 invalid code, dead challenge — 410
POST /auth/mfa/challengechallenge in bodyPolling for the mobile method: {"status": "pending|approved", "mobileCode", "expiresIn"}; unknown challenge — 404
GET /auth/mfa/pendingdevice tokenFor the phone: sign-ins awaiting approval (ip, kind, createdAt, expiresIn)
POST /auth/mfa/approvedevice tokenFor the phone: approve a sign-in, body {"code": "47"} (the code from the login screen). No match — 404, device is not an approver — 403

For the mobile method: the web shows mobileCode, the owner enters it in the app (/auth/mfa/approve), the web polls /auth/mfa/challenge and, once approved, calls /auth/mfa/verify with "method": "mobile".

Окно терминала
curl https://api.notifly.ru/auth/mfa/verify \
-H "Content-Type: application/json" \
-d '{"challenge": "…", "method": "totp", "code": "123456", "name": "my-cli-client"}'

POST /user/reset-password — request password reset

Section titled “POST /user/reset-password — request password reset”

Public endpoint (no authentication). Sends an email with a password reset link to the specified address. The response is always 200, even if the user does not exist — so as not to reveal which addresses are registered.

Окно терминала
curl https://api.notifly.ru/user/reset-password \
-H "Content-Type: application/json" \
-d '{"email": "user@example.com"}'

POST /user/reset-password/confirm — confirm password reset

Section titled “POST /user/reset-password/confirm — confirm password reset”

Public endpoint. Accepts the token from the email and the new password pass (at least 8 characters, no longer than 72 bytes).

Окно терминала
curl https://api.notifly.ru/user/reset-password/confirm \
-H "Content-Type: application/json" \
-d '{"token": "<from the email>", "pass": "new-password"}'

A password reset is the recovery path after an account takeover, so along with the password the secrets issued to the account are revoked too: all devices are signed out (their tokens are rotated; access can be restored by signing in again or via POST /client/:id/reconnect) and all API tokens are deleted — they have to be issued again. Channel keys (A…) and channel access tokens (S…) keep working: they are limited to a single channel and grant no access to the account.

Public endpoint. Opened from the link in the verification email (/user/verify?token=...) and activates the user’s address.

Окно терминала
curl "https://api.notifly.ru/user/verify?token=<from the email>"

A channel (application in the API URLs — for compatibility with the Gotify protocol) is a message source. Every channel has its own app token for sending.

Your own channels and channels you have accepted delegated access to.

Окно терминала
curl -H "X-Notifly-Key: $TOKEN" https://api.notifly.ru/application
[
{
"id": 1,
"token": "ApNK7NiI0ltrJf3a8BNsOiD",
"name": "Monitoring",
"description": "Alerts from the monitoring system",
"internal": false,
"image": "https://…/channel-logos/1/1/9f2c….png",
"defaultPriority": 5,
"lastUsed": "2025-01-15T12:00:00Z",
"sortKey": "a0",
"tags": ["prod"],
"hbTotal": 2,
"hbStatus": "ok"
}
]

Channel fields:

FieldDescription
tokenThe channel’s primary key; for a shared channel — the recipient’s own S… token (empty with view)
imagePublic URL of the logo (empty if none is uploaded)
sortKeyManual sort key
parentIdParent channel (channel tree); absent for a root channel
tagsChannel tags
deliveryConfigDelivery settings; adapter secrets are masked
shared, sharePermission, canShareOnly on channels shared with you: the flag, access level (view|send) and the right to reshare
hbTotal, hbAlerting, hbAlertingInfo, hbPending, hbStatusHeartbeat summary of the channel; hbStatus — ok|degraded|alerting|pending|none
Окно терминала
curl -H "X-Notifly-Key: $TOKEN" https://api.notifly.ru/application \
-H "Content-Type: application/json" \
-d '{"name": "CI/CD", "description": "Build notifications", "defaultPriority": 5}'

Request fields (JSON; name, description, defaultPriority, parentId, sortKey can also be sent as a form):

FieldTypeRequiredDescription
namestring✓Channel name
descriptionstringDescription
defaultPriorityintegerDefault priority — applied to messages sent without priority
parentIdintegerParent channel (channel tree)
sortKeystringManual sort key
tagsstring[]Channel tags (up to 10, [a-z0-9_-]) — used by the scope of API tokens
deliveryConfigobjectRe-delivery of unread messages via email/SMS/call/Slack/Telegram/webhook/Discord/Teams/Mattermost/PagerDuty/Opsgenie — see Delivery

PUT /application/{id} — update a channel

Section titled “PUT /application/{id} — update a channel”
Окно терминала
curl -H "X-Notifly-Key: $TOKEN" https://api.notifly.ru/application/1 \
-X PUT -H "Content-Type: application/json" \
-d '{"name": "CI/CD v2", "description": "Updated description"}'

DELETE /application/{id} — delete a channel

Section titled “DELETE /application/{id} — delete a channel”
Окно терминала
curl -H "X-Notifly-Key: $TOKEN" https://api.notifly.ru/application/1 -X DELETE

GET /application/{id}/status — aggregated channel status

Section titled “GET /application/{id}/status — aggregated channel status”

Returns the channel’s aggregated status (heartbeat summary, last activity). With ?recursive=1 — for the channel and all its descendants.

Окно терминала
curl -H "X-Notifly-Key: $TOKEN" https://api.notifly.ru/application/1/status

POST /application/{id}/delivery/test — test delivery to external integrations

Section titled “POST /application/{id}/delivery/test — test delivery to external integrations”

Immediately (bypassing escalation) sends a test notification through the channel’s enabled delivery methods (email, SMS, call, Slack, Telegram, webhook, Discord, Teams, Mattermost, PagerDuty, Opsgenie) and returns the result for each. The optional body {"adapter": "slack"} limits the check to a single adapter. Details — Delivery and escalation.

Окно терминала
curl -H "X-Notifly-Key: $TOKEN" https://api.notifly.ru/application/1/delivery/test \
-H "Content-Type: application/json" -d '{"adapter": "telegram"}'
{"results": {"telegram": {"ok": true}, "slack": {"ok": false, "error": "..."}}}

If no methods are enabled — 400 no enabled adapter to test.

GET /application/{id}/event-sources — event sources of a channel

Section titled “GET /application/{id}/event-sources — event sources of a channel”

Everything that writes to the channel: monitors of every type, heartbeats, port scans, metrics, web scripts, email-inbox and webhook-router rules — in a single list, with the current status and the time of the last check. Source secrets (tokens, headers, steps) are not included in the output. With ?recursive=1 — including the channel’s descendants.

Method and pathDescription
POST /application/{id}/imageUpload a channel logo (multipart/form-data, field file)
DELETE /application/{id}/imageDelete the logo

The body must be JSON (Content-Type: application/json): this endpoint currently does not accept form-data or urlencoded bodies (400).

Окно терминала
curl "https://api.notifly.ru/message?token=ApNK7NiI0ltrJf3a8BNsOiD" \
-H "Content-Type: application/json" \
-d '{"message": "Build #42 finished", "title": "CI/CD", "priority": 5}'

Request fields:

FieldTypeRequiredDescription
messagestring✓Message text
titlestringTitle
priorityintegerPriority (0–10); if omitted — the channel’s defaultPriority
extrasobjectAdditional fields for clients (see msgextras)

Example with extras (markdown content):

Окно терминала
curl "https://api.notifly.ru/message?token=ApNK7NiI0ltrJf3a8BNsOiD" \
-H "Content-Type: application/json" \
-d '{
"message": "**Done!** Details: [link](https://example.com)",
"title": "Build",
"priority": 5,
"extras": {
"client::display": {"contentType": "text/markdown"}
}
}'

Response:

{
"id": 123,
"appid": 1,
"message": "Build #42 finished",
"title": "CI/CD",
"priority": 5,
"date": "2025-06-01T10:30:00Z",
"senderName": "user@example.com"
}

Every message consumes one event of the daily quota; once it is exhausted — 429 daily event quota exceeded. Writing to the system channel is forbidden — 403.

POST /integrations/alertmanager, POST /integrations/zabbix — receiving from monitoring systems

Section titled “POST /integrations/alertmanager, POST /integrations/zabbix — receiving from monitoring systems”

Native alert receivers authorized with the channel’s app token (the X-Notifly-Key header, Authorization: Bearer or ?token=), just like POST /message.

  • /integrations/alertmanager — the Prometheus Alertmanager webhook body (version 4) or a Grafana webhook contact point; creates a message for firing alerts and a separate one for resolved alerts.
  • /integrations/zabbix — the parameters of the Zabbix “Webhook” media type (subject, message, severity, status, …); one message.

The priority is derived from the source’s severity; each message consumes one quota event. Response — {"created": N, "ids": [...]}. Setup and mapping — Alertmanager, Grafana and Zabbix.

GET /message — all messages (with pagination)

Section titled “GET /message — all messages (with pagination)”

Messages from all accessible channels (your own and those shared with you), newest first. An API token sees only the channels in its scope, a channel token — only its own channel.

Окно терминала
curl -H "X-Notifly-Key: $TOKEN" "https://api.notifly.ru/message?limit=20"

Parameters:

ParameterTypeDefaultDescription
limitinteger100Number of messages (1–200)
sinceintegerPagination cursor: return messages with an ID lower than this. Taken from paging.since / paging.next of the previous page

Response:

{
"paging": {
"size": 20,
"limit": 20,
"since": 4012,
"next": "/message?limit=20&since=4012"
},
"messages": [
{
"id": 4031,
"appid": 1,
"message": "Message text",
"title": "Title",
"priority": 5,
"date": "2025-06-01T10:30:00Z",
"read": true,
"senderName": "user@example.com"
}
]
}

paging.since and paging.next are present only when a next page exists; they are absent on the last page. In a message, read: true means it has been read by the current user (unread messages have no such field), senderName is the sender, extras — if they were provided.

GET /application/{id}/message — channel messages

Section titled “GET /application/{id}/message — channel messages”

Same pagination and response format as GET /message. With ?recursive=1 — messages of the channel and all its descendants (for a share recipient — only those shared with them).

Окно терминала
curl -H "X-Notifly-Key: $TOKEN" "https://api.notifly.ru/application/1/message?limit=50"
curl -H "X-Notifly-Key: $TOKEN" "https://api.notifly.ru/application/1/message?recursive=1"

Searches the user’s messages. The q parameter is required (at least 2 characters). The optional appId limits the search to a single channel. Together with appId, the recursive=1 flag extends the search to the channel and all its descendants (for a share recipient — only to those shared with them). Supports pagination (limit, since). A channel token searches only its own channel.

Окно терминала
curl -H "X-Notifly-Key: $TOKEN" "https://api.notifly.ru/message/search?q=error&limit=20"
curl -H "X-Notifly-Key: $TOKEN" "https://api.notifly.ru/message/search?q=deploy&appId=1"
curl -H "X-Notifly-Key: $TOKEN" "https://api.notifly.ru/message/search?q=deploy&appId=1&recursive=1"

POST /message/read — mark messages as read

Section titled “POST /message/read — mark messages as read”

Body: {"ids": [1, 2, 3]} — a list of message IDs. The owner’s read mark stops re-delivery; a read-only API token is not allowed here.

Окно терминала
curl -H "X-Notifly-Key: $TOKEN" https://api.notifly.ru/message/read \
-H "Content-Type: application/json" -d '{"ids": [1, 2, 3]}'

POST /application/{id}/message/read — mark entire channel as read

Section titled “POST /application/{id}/message/read — mark entire channel as read”

With ?recursive=1 — together with its descendants.

Окно терминала
curl -H "X-Notifly-Key: $TOKEN" https://api.notifly.ru/application/1/message/read -X POST

Not available to an API token with a restricted scope.

Окно терминала
curl -H "X-Notifly-Key: $TOKEN" https://api.notifly.ru/message -X DELETE
Окно терминала
curl -H "X-Notifly-Key: $TOKEN" https://api.notifly.ru/message/123 -X DELETE

DELETE /application/{id}/message — delete all messages in a channel

Section titled “DELETE /application/{id}/message — delete all messages in a channel”

With ?recursive=1 — together with its descendants.

Окно терминала
curl -H "X-Notifly-Key: $TOKEN" https://api.notifly.ru/application/1/message -X DELETE

A client is a device or application that receives messages and manages resources.

Окно терминала
curl -H "X-Notifly-Key: $TOKEN" https://api.notifly.ru/client
[
{
"id": 1,
"name": "firefox",
"token": "C…xiLx",
"lastUsed": "2025-06-01T10:00:00Z",
"status": "active",
"createdAt": "2025-05-20T08:00:00Z",
"platform": "web",
"lastIp": "203.0.113.7",
"lastUserAgent": "Mozilla/5.0 …",
"current": true
}
]

The token in the list is only a hint, not a secret. The full token value is returned once: in the responses of POST /auth/local/login, POST /client and POST /client/{id}/reconnect. status is active|suspended|revoked; current: true marks the device the request was made from.

Body: name (required), platform. Response — the created client with the full token. When the plan’s device limit is reached — 402 with errorCode: "device_limit_exceeded".

Окно терминала
curl -H "X-Notifly-Key: $TOKEN" https://api.notifly.ru/client \
-H "Content-Type: application/json" \
-d '{"name": "my-script"}'
Окно терминала
curl -H "X-Notifly-Key: $TOKEN" https://api.notifly.ru/client/1 \
-X PUT -H "Content-Type: application/json" \
-d '{"name": "renamed-client"}'
Окно терминала
curl -H "X-Notifly-Key: $TOKEN" https://api.notifly.ru/client/1 -X DELETE

DELETE /client — delete all devices except the current one

Section titled “DELETE /client — delete all devices except the current one”

Response — {"deleted": N}.

Окно терминала
curl -H "X-Notifly-Key: $TOKEN" https://api.notifly.ru/client -X DELETE

By default a device receives messages from all channels. The “subscriptions” in the paths below are historical names: they actually hold the list of channels muted on the device.

Method and pathDescription
GET /client/onlineList of device IDs with an active WS connection
POST /client/{id}/reconnectRotate the device token; the new token is returned once
PUT /client/{id}/statusChange the status, body {"status": "active|suspended|revoked"}; revoked is irreversible, reactivating over the limit — 402
GET /client/{id}/activityDevice activity log (?from=&to=&type=&limit=, dates in RFC3339)
GET /client/{id}/subscriptionsChannels muted on the device
PUT /client/{id}/subscriptionsReplace the list of muted channels, body {"channelIds": [1, 2]} (an empty array unmutes all)

Device self-service (authenticated with the device’s own token):

Method and pathDescription
GET /device/me/subscriptionsChannels muted on this device
POST /device/me/subscriptionsMute a channel, body {"channelId": 1}
DELETE /device/me/subscriptions/{channel_id}Unmute a channel

GET /current/user — current user information

Section titled “GET /current/user — current user information”
Окно терминала
curl -H "X-Notifly-Key: $TOKEN" https://api.notifly.ru/current/user
{
"id": 1,
"email": "user@example.com",
"admin": false,
"verified": true,
"plan": "free",
"eventCounter": 120,
"messageCounter": 80,
"heartbeatCounter": 30,
"monitorCounter": 10,
"webScriptCounter": 0,
"balanceKopecks": 0,
"storageBytes": 0,
"image": "",
"mfaEnabled": false
}

POST /current/user/sudo — confirm your identity (sudo)

Section titled “POST /current/user/sudo — confirm your identity (sudo)”

Sensitive actions (changing the password, 2FA settings, admin endpoints) require a fresh confirmation. Without it the response is 403 with "reason": "sudo_required" and the list of available methods. A confirmation opens a 15-minute window for this device.

Device token only: no window is issued to an API token or a channel token (403).

FieldDescription
methodpassword — if 2FA is off; totp or recovery — if it is on (the password is not accepted)
passPassword (for password)
codeAuthenticator code or recovery code
Окно терминала
curl -H "X-Notifly-Key: $TOKEN" https://api.notifly.ru/current/user/sudo \
-H "Content-Type: application/json" \
-d '{"method": "password", "pass": "current-password"}'
{"ok": true, "method": "password", "expiresIn": 900}

Wrong password — 400 wrong password, wrong code — 401 invalid code.

POST /current/user/password — change password

Section titled “POST /current/user/password — change password”

Requires a device token and a sudo window; an API token gets 403. The current password oldPass is required, the new pass must be at least 8 characters and no longer than 72 bytes.

Окно терминала
curl -H "X-Notifly-Key: $TOKEN" https://api.notifly.ru/current/user/password \
-H "Content-Type: application/json" \
-d '{"pass": "new-secure-password", "oldPass": "current-password"}'

A wrong oldPass returns 400 with errorDescription: "wrong password". After a successful change all other devices are signed out (their tokens are rotated), while the session the request came from keeps working; open sudo windows are closed. Changing the password does not touch API tokens — unlike a reset by email.

All endpoints require a device token; those marked “sudo” require a sudo window.

Method and pathDescription
GET /current/user/mfaState: enabled, pendingSetup, recoveryCodesRemaining, mobileConnected, mobileEnabled, approverDevices
POST /current/user/mfa/setupsudo. A new authenticator secret: {"secret", "uri"} (otpauth://…); 2FA already enabled — 409
POST /current/user/mfa/confirmsudo. Enable 2FA with the first code, body {"code": "123456"}; response — {"recoveryCodes": [...]}
POST /current/user/mfa/disablesudo. Disable 2FA
POST /current/user/mfa/recovery-codessudo. Regenerate recovery codes
POST /current/user/mfa/mobile/startsudo. Start linking a phone for sign-in approval: {"challenge", "code", "expiresIn"}
POST /current/user/mfa/mobile/confirmFinish linking after the phone has entered code via /auth/mfa/approve, body {"challenge": "…"}
POST /current/user/mfa/mobile/disablesudo. Turn off sign-in approval from a phone
DELETE /current/user/mfa/mobile/devices/{id}sudo. Remove a device from the approvers

Details on plans and limits are on the Quotas and plans page.

Method and pathDescription
GET /user/quota-breakdownDetailed breakdown of event usage for a day (?day=YYYY-MM-DD), per hour and per minute, plus a snapshot of used storage (storage)
POST /current/user/planChange plan, body {"plan": "free|pro|business"}. On your own you can only downgrade or keep the current plan; an upgrade — 402 plan upgrade requires payment
POST /current/user/topupTop up the balance, body {"amountRubles": N}. For a non-admin — 402 with payment details (paymentMethod: "bank_transfer")
POST /current/user/imageUpload an avatar (multipart/form-data, field file)
DELETE /current/user/imageDelete the avatar

There is no payment gateway: plan upgrades and top-ups are paid by invoice (bank transfer), after which an operator moves the account to the new plan (POST /user/{id}/plan).

Окно терминала
curl -H "X-Notifly-Key: $TOKEN" "https://api.notifly.ru/user/quota-breakdown?day=2026-06-23"
# Downgrade to free
curl -H "X-Notifly-Key: $TOKEN" https://api.notifly.ru/current/user/plan \
-H "Content-Type: application/json" -d '{"plan": "free"}'

POST /current/user/topup response for a non-admin (message is a human-readable instruction):

{
"message": "…",
"amountRubles": 500,
"paymentMethod": "bank_transfer"
}

Details — Quiet hours and digest.

Method and pathDescription
GET /current/user/quiet-hoursCurrent settings (for a new user: enabled: false, 23:00–08:00)
PUT /current/user/quiet-hoursSave; fields not sent keep their values; the response is the saved object

Body: enabled, timezone (IANA), start/end (HH:MM; an end earlier than the start spans midnight), days (ISO 1=Mon…7=Sun, by the start day), breakthroughPriority (0..10), digest.

Окно терминала
curl -X PUT -H "X-Notifly-Key: $TOKEN" -H "Content-Type: application/json" \
https://api.notifly.ru/current/user/quiet-hours \
-d '{"enabled":true,"timezone":"Europe/Moscow","start":"23:00","end":"08:00","days":[1,2,3,4,5,6,7],"breakthroughPriority":8,"digest":true}'

Paginated, keyset by ascending id: limit (1–200, default 100) and since — the id of the last user on the previous page.

Окно терминала
curl -H "X-Notifly-Key: $TOKEN" "https://api.notifly.ru/user?limit=100"
{
"paging": {"size": 2, "limit": 100},
"users": [
{
"id": 1,
"email": "admin@example.com",
"admin": true,
"verified": true,
"plan": "business",
"mfaEnabled": true
},
{
"id": 2,
"email": "user1@example.com",
"admin": false,
"verified": true,
"plan": "free",
"mfaEnabled": false
}
]
}

A user has the same format as in GET /current/user (fields are abbreviated in the example).

A user created by an administrator is verified immediately; on self-registration a verification email is sent to the address.

Окно терминала
curl -H "X-Notifly-Key: $TOKEN" https://api.notifly.ru/user \
-H "Content-Type: application/json" \
-d '{"email": "newuser@example.com", "pass": "password123", "admin": false}'

Body: email (required, the current address — it cannot be changed), admin (send the current value: a missing field means false), pass (optional, a new password), resetMfa (reset the user’s 2FA). A password change by an administrator works like a reset: all of the user’s devices are signed out and all of their API tokens are deleted.

Окно терминала
curl -H "X-Notifly-Key: $TOKEN" https://api.notifly.ru/user/2 \
-H "Content-Type: application/json" \
-d '{"email": "user1@example.com", "admin": false, "pass": "new-password-123"}'

POST /user/{id}/plan — change a user’s plan

Section titled “POST /user/{id}/plan — change a user’s plan”

An operator plan change, including an upgrade after payment. Body {"plan": "free|pro|business"}.

Окно терминала
curl -H "X-Notifly-Key: $TOKEN" https://api.notifly.ru/user/2/plan \
-H "Content-Type: application/json" -d '{"plan": "pro"}'

You cannot delete yourself (400).

Окно терминала
curl -H "X-Notifly-Key: $TOKEN" https://api.notifly.ru/user/2 -X DELETE

Dead-man switch: an external job (cron, script) periodically sends a “ping”; if the ping doesn’t arrive in time, Notifly sends an alert. Details — Heartbeat.

Public ping (authentication by the H… ping token in the URL, no API token):

Окно терминала
curl https://api.notifly.ru/heartbeat/ping/HrvF2iYjEnz5z8ZngHvvkX3 # GET — handy from cron
curl -X POST https://api.notifly.ru/heartbeat/ping/HrvF2iYjEnz5z8ZngHvvkX3 \
-H "Content-Type: application/json" -d '{"fail_reason": "backup failed"}'

Management (API token):

Method and pathDescription
GET /heartbeatList of heartbeats
POST /heartbeatCreate
PUT /heartbeat/{id}Update
DELETE /heartbeat/{id}Delete
POST /heartbeat/{id}/pause · /resumePause / resume
POST /heartbeat/{id}/test-alert · /test-recoveryTest alert / recovery notification

Active availability and content checks. Each type has its own page with details.

Method and pathDescription
GET /monitor · POST /monitorList / create
POST /monitor/testTest settings without saving
PUT /monitor/{id} · DELETE /monitor/{id}Update / delete
POST /monitor/{id}/pause · /resumePause / resume
Method and pathDescription
GET /http-monitor · POST /http-monitorList / create
POST /http-monitor/testTest a request
PUT /http-monitor/{id} · DELETE /http-monitor/{id}Update / delete
POST /http-monitor/{id}/pause · /resumePause / resume
Method and pathDescription
GET /content-monitor · POST /content-monitorList / create
POST /content-monitor/suggest-selectorAI-suggested CSS selector
POST /content-monitor/test-ruleTest a rule
PUT /content-monitor/{id} · DELETE /content-monitor/{id}Update / delete
POST /content-monitor/{id}/pause · /resumePause / resume
Method and pathDescription
GET /port-monitor · POST /port-monitorList / create
POST /port-monitor/testTest a port
PUT /port-monitor/{id} · DELETE /port-monitor/{id}Update / delete
POST /port-monitor/{id}/pause · /resumePause / resume
Method and pathDescription
GET /port-scan · GET /port-scan/{id}List / a single scan
POST /port-scanStart a scan
PATCH /port-scan/{id} · DELETE /port-scan/{id}Update / delete
POST /port-scan/{id}/cancel · /restartCancel / restart
POST /port-scan/{id}/to-monitorTurn a discovered port into a monitor
Method and pathDescription
GET /workflow-monitor · POST /workflow-monitorList / create
POST /workflow-monitor/build-stepAI step builder
POST /workflow-monitor/testRun the scenario
PUT /workflow-monitor/{id} · DELETE /workflow-monitor/{id}Update / delete
POST /workflow-monitor/{id}/pause · /resumePause / resume
Method and pathDescription
GET /browser-workflow · POST /browser-workflowList / create
POST /browser-workflow/build-stepAI-suggested action
POST /browser-workflow/loginCapture auth cookies
POST /browser-workflow/test-stepTest a step
PUT /browser-workflow/{id} · DELETE /browser-workflow/{id}Update / delete
POST /browser-workflow/{id}/pause · /resumePause / resume

GET /browser-shot/{key} returns a JPEG screenshot of a failed browser workflow or browser content-monitor check. The key comes from the notifly::screenshot field in the alert message’s extras. Only the monitor’s owner has access; for <img> the token can be passed in ?token=. Old screenshots are removed by retention — then 404.

Окно терминала
curl -H "X-Notifly-Key: $TOKEN" -o shot.jpg \
"https://api.notifly.ru/browser-shot/<value of notifly::screenshot>"

Proof of host ownership before scanning/monitoring.

Method and pathDescription
GET /verified-hostList
POST /verified-host/start · /checkStart / check the verification
DELETE /verified-host/{id}Delete

{kind} — monitor type (monitor, http-monitor, content-monitor, port-monitor, etc.).

Method and pathDescription
GET /monitor-history/{kind}/{id}Uptime %, response time, aggregates
GET /monitor-history/{kind}/{id}/logDetailed log of individual checks

An incoming email to the inbox address becomes a notification. Details — Email Inbox.

Method and pathDescription
GET /email-inbox · POST /email-inboxList / create an inbox
PUT /email-inbox/{id} · DELETE /email-inbox/{id}Update / delete
GET /email-inbox/{id}/rules · POST /email-inbox/{id}/rulesEmail processing rules
PUT /email-inbox/{id}/rules/{rid} · DELETE /email-inbox/{id}/rules/{rid}Update / delete a rule
GET /email-inbox/eventHistory of incoming emails
GET /email-inbox/event/{eid} · DELETE /email-inbox/event/{eid}An email / delete
POST /email-inbox/rule/from-event/{eid}AI-suggested rule from an email

JS snippets for your site: error tracking (console_errors), events, etc. Details — Web script.

Public ingest (authentication by the token in the URL, sent from the browser). GET and a bodiless POST work too — the script fires with its default settings:

Окно терминала
curl -X POST https://api.notifly.ru/script/<token> \
-H "Content-Type: application/json" -d '{ ... }'
curl https://api.notifly.ru/script/<token>

Management (API token):

Method and pathDescription
GET /web-script · POST /web-scriptList / create
PUT /web-script/{id} · DELETE /web-script/{id}Update / delete
GET /web-script/{id}/sourcemaps · POST /web-script/{id}/sourcemapsSource maps
DELETE /web-script/{id}/sourcemaps/{smid}Delete a source map
GET /web-script/{id}/issuesGrouped errors (issues)
GET /web-script/{id}/issue-countNumber of unresolved issues ({"unresolved": N})
POST /web-script/{id}/issues/{iid}/resolve · /ignoreMark an issue resolved / ignored
DELETE /web-script/{id}/issues/{iid}Delete an issue
GET /web-script/{id}/issues/{iid}/breadcrumbsUser actions before the error
POST /web-script/{id}/issues/{iid}/analyzeAI root-cause analysis (body: lang, force)
GET /web-script/{id}/issues/{iid}/analysisSaved analysis (204 if there is none)
GET /web-script/{id}/series · /error-seriesTime series of triggers and errors (?bucket=1m|1h|1d&from=&to=)
GET /web-script/{id}/flush-statsDistribution of batch flush reasons

Receives incoming webhooks and routes them to channels by rules. It is a per-user singleton with path-based routing. Details — Webhook router.

Public ingest (authentication by the R… token in the URL, any path after the token; a bodiless GET is accepted too):

Окно терминала
curl -X POST https://api.notifly.ru/router/<token>/any/path \
-H "Content-Type: application/json" -d '{ ... }'

Management (API token):

Method and pathDescription
GET /webhook-router · PUT /webhook-routerGet / update the router
POST /webhook-router/rotate-tokenRotate the public token
GET /webhook-router/rule · POST /webhook-router/ruleRouting rules
PUT /webhook-router/rule/{rid} · DELETE /webhook-router/rule/{rid}Update / delete a rule
GET /webhook-router/event · GET /webhook-router/event/{eid}Event history
DELETE /webhook-router/event/{eid}Delete an event
GET /webhook-router/message · DELETE /webhook-router/message/{mid}Routed messages
POST /webhook-router/rule/from-event/{eid}AI-suggested rule from an event

Numeric time series with alerts. Details — Metrics.

Public metric ingest (authentication by the source’s M… token in the URL — not an API token):

Окно терминала
curl -X POST https://api.notifly.ru/metric/<token> \
-H "Content-Type: application/json" -d '{ ... }'

Management (API token):

Method and pathDescription
GET /metric-source · POST /metric-sourceMetric sources
PUT /metric-source/{id} · DELETE /metric-source/{id}Update / delete a source
GET /metric-source/{id}/metricsMetrics of a source
PATCH /metric-source/{id}/metrics/{mid} · DELETE /metric-source/{id}/metrics/{mid}Type / delete a metric
GET /metric-source/{id}/metrics/{mid}/seriesTime series of values
GET /metric-source/{id}/alerts · POST /metric-source/{id}/alertsMetric alerts
PUT /metric-source/{id}/alerts/{aid} · DELETE /metric-source/{id}/alerts/{aid}Update / delete an alert

Granting other users access to a channel by email. Details — Channel sharing.

Method and pathDescription
GET /application/{id}/share · POST /application/{id}/shareList / create a share for a channel
PUT /share/{id} · DELETE /share/{id}Update / revoke a share
GET /share/incomingShares granted to me
GET /share/recipientsAddresses I have already shared with (suggestions)
POST /share/{id}/accept · /declineAccept / decline a share
POST /share/joinJoin by token
POST /share/leaveGive up access to a channel, body {"appid": N}
POST /share/rotateRotate the value of your Share token, body {"appid": N}

Multiple keys per channel with separate permissions, soft revocation and rotation. Available only to the channel owner.

Method and pathDescription
GET /application/{id}/token · POST /application/{id}/tokenList / issue a key. Body: name, canRead, canWrite, note, expiresAt (unix ms, 0 — no expiry)
PUT /token/{id}Change permissions, label and expiry
DELETE /token/{id}Revoke a key (a repeated call deletes the record; the channel’s primary key → 409)
POST /token/{id}/rotateChange the value, keeping the id and permissions

A public page with the status and uptime of a channel’s monitors. Details — Status pages.

Management (API token, channel owner only):

Method and pathDescription
GET /status-pageList of your status pages
POST /status-pageCreate. Body: appid, slug, title, description, enabled, lang (ru|en), brandingHidden
PUT /status-page/{id}Update (same payload)
DELETE /status-page/{id}Delete

Public endpoints (no authentication):

Method and pathDescription
GET /status/{slug}HTML status page
GET /status/{slug}/summaryThe same snapshot as JSON: overall, overallUptime90d, services[], incidents[]
POST /status/{slug}/subscribeEmail subscription (double opt-in), body {"email": "..."}
GET /status-subscribe/verify?token=…Confirm the subscription (link from the email)
GET /status-subscribe/unsub?token=…Unsubscribe (link from the email)
Окно терминала
curl -H "X-Notifly-Key: $TOKEN" https://api.notifly.ru/status-page \
-H "Content-Type: application/json" \
-d '{"appid": 1, "slug": "acme-status", "title": "ACME Status", "enabled": true, "lang": "en"}'
curl https://api.notifly.ru/status/acme-status/summary

Codes: 400 — invalid slug/language or someone else’s channel, 403 — plan limit or a premium field not included in the plan, 404 — page not found or not published, 409 — slug taken.


Planned work: covered monitors don’t alert, messages aren’t escalated and arrive silently, the status page shows “Planned maintenance”. Details — Maintenance windows.

Method and pathDescription
GET /maintenance-windowList of your windows
POST /maintenance-windowCreate
GET /maintenance-window/{id}A single window
PUT /maintenance-window/{id}Replace the fields
DELETE /maintenance-window/{id}Delete (204)

Body: name, channelIds[], includeSubtree, monitors[] ({"kind","id"}), schedule (once — startsAt/endsAt; weekly — timezone, days, start, end), enabled. The response additionally contains active, nextStart, nextEnd, coveredChannelIds.

Окно терминала
curl -X POST -H "X-Notifly-Key: $TOKEN" -H "Content-Type: application/json" \
https://api.notifly.ru/maintenance-window \
-d '{"name":"Release","channelIds":[12],"includeSubtree":true,"monitors":[],"schedule":"once","startsAt":"2026-10-02T23:00:00Z","endsAt":"2026-10-03T01:00:00Z","enabled":true}'

Codes: 400 — validation error or someone else’s channel/monitor, 403 — limit or an unsuitable API token, 404 — the window doesn’t exist or belongs to someone else.


Programmatic account keys (M…) for the SDK, CLI and MCP. Issued from the admin UI; details are on the API tokens page. They can be managed only with a device token: an API token itself is not allowed on these endpoints (403).

Method and pathDescription
GET /api-tokenList; the token field is a M…ab3d hint, not a secret
POST /api-tokenIssue; the only response with the full value
PUT /api-token/{id}Change name, access, scope, expiry
POST /api-token/{id}/rotateReissue: a new value, the same settings
DELETE /api-token/{id}Delete

POST/PUT body:

FieldTypeDescription
namestringName (required)
accessstringread or write (required)
appIdsint[]Allowed channels by id
appTagsstring[]Allowed channels by tag; empty appIds and appTags — all channels. In PUT a missing field means no change
expiresAtstringExpiry, RFC3339, in the future; in PUT a missing field means no change
Окно терминала
curl -H "X-Notifly-Key: $DEVICE_TOKEN" https://api.notifly.ru/api-token \
-H "Content-Type: application/json" \
-d '{"name": "ci", "access": "write", "appTags": ["prod"], "expiresAt": "2027-01-01T00:00:00Z"}'

Interactive questions: the channel asks — the user answers from the interface. Details — Ask.

Asking a question uses the app token:

Method and pathAuthenticationDescription
POST /askapp tokenAsk a question on behalf of the channel
GET /ask/pending-answersapp tokenFetch ready answers
POST /ask/ackapp tokenAcknowledge receipt of answers
GET /ask-question/{id}app tokenGet the answer to a specific question (expired or cancelled — 410)
POST /ask-question/{id}/answerAPI tokenAnswer a question from the UI
DELETE /ask-question/{id}API tokenCancel a question
POST /message/{id}/answerAPI tokenAnswer a question from a message
POST /ask-question/{id}/extenddevice, API or channel tokenExtend the answer deadline, body {"timeout_sec": 1…86400}; a channel token needs write permission
GET /a/answer?token=…signed linkAnswer via a button from an email or Telegram
GET /m/read?token=…signed linkMark a message as read (stops re-delivery)

A chat copilot for onboarding and help. Details — Assistant.

Method and pathDescription
GET /assistant/threadsList of conversations
GET /assistant/threads/{id}/messagesMessage history of a conversation
DELETE /assistant/threads/{id}Delete a conversation
POST /assistant/chatSend a message to the assistant
GET /assistant/insightsProactive account recommendations (?polish=1, ?refresh=1, ?lang=)

There are two independent live-update channels. Details are on the WebSocket page.

The primary channel for real-time push delivery. Connect with a token in the token query parameter (usually a device token; other options are on the WebSocket page):

Окно терминала
# Using wscat
wscat -c "wss://api.notifly.ru/ws?token=CK2UDmhduKshCyz-RtqxiLx"
const ws = new WebSocket('wss://api.notifly.ru/ws?token=CK2UDmhduKshCyz-RtqxiLx');
ws.onmessage = (event) => {
const msg = JSON.parse(event.data);
console.log(`[${msg.title}] ${msg.message} (priority: ${msg.priority})`);
};

Every incoming event is a Message JSON object:

{
"id": 124,
"appid": 1,
"message": "New message",
"title": "Title",
"priority": 5,
"date": "2025-06-01T10:31:00Z"
}

A separate Server-Sent Events endpoint (GET /stream, Content-Type: text/event-stream) for live UI updates: it signals that something has changed, while the messages themselves arrive over WebSocket. The server sends only named events — connected, channel:status_changed, share:changed (data — {"appid": N}) — plus a keepalive comment every 25 seconds. An onmessage handler does not receive them: subscribe with addEventListener.

Authentication — a token in ?token= or the session cookie on a page of the same site. For cross-origin requests the cookie is not sent (withCredentials works only for origins listed in the server’s CORS_ALLOWED_ORIGINS setting), so from elsewhere use ?token=.

const es = new EventSource('https://api.notifly.ru/stream?token=CK2UDmhduKshCyz-RtqxiLx');
es.addEventListener('connected', () => console.log('connected'));
es.addEventListener('channel:status_changed', (e) => {
console.log('channel status changed', JSON.parse(e.data).appid);
});
es.addEventListener('share:changed', (e) => {
console.log('channel access changed', JSON.parse(e.data).appid);
});

On AWS deployments /stream is unavailable and responds 503 — use WebSocket.


Messages are sent only as a JSON body.

import requests
API = "https://api.notifly.ru"
# Send a message (channel token)
requests.post(f"{API}/message", params={"token": "ApNK7NiI0ltrJf3a8BNsOiD"}, json={
"title": "Backup",
"message": "Backup completed",
"priority": 2,
})
# Fetch messages (API token)
resp = requests.get(f"{API}/message", headers={"X-Notifly-Key": "MtVOKLDNuD7HR9y7QkMecC5"})
for msg in resp.json()["messages"]:
print(f"[{msg['title']}] {msg['message']}")
package main
import (
"bytes"
"encoding/json"
"net/http"
)
func main() {
body, _ := json.Marshal(map[string]any{
"title": "Deploy",
"message": "Version 2.0 deployed",
})
http.Post("https://api.notifly.ru/message?token=ApNK7NiI0ltrJf3a8BNsOiD",
"application/json", bytes.NewReader(body))
}
// Send a message
const resp = await fetch('https://api.notifly.ru/message?token=ApNK7NiI0ltrJf3a8BNsOiD', {
method: 'POST',
headers: {'Content-Type': 'application/json'},
body: JSON.stringify({
title: 'CI',
message: 'Tests passed',
priority: 3,
}),
});
console.log(await resp.json());
Окно терминала
# Send a message
$body = @{
title = "Report"
message = "Daily report generated"
priority = 1
} | ConvertTo-Json
Invoke-RestMethod -Uri "https://api.notifly.ru/message?token=ApNK7NiI0ltrJf3a8BNsOiD" `
-Method POST -ContentType "application/json; charset=utf-8" -Body $body

CodeMeaning
200Success
204Success, no body (e.g. deleting a maintenance window)
400Bad request (invalid parameters, wrong password on re-confirmation)
401Unauthorized (missing or invalid token, second factor required, wrong 2FA code)
402Payment required: plan upgrade, balance top-up, plan device limit
403Forbidden: insufficient rights, unsuitable token type, sudo required ("reason": "sudo_required"), plan limit
404Resource not found (including someone else’s)
409Conflict: slug taken, question no longer pending, 2FA already enabled, revoking a channel’s primary key
410No longer valid: question expired or cancelled, MFA challenge expired or used
429Too many requests: daily event quota exhausted (daily event quota exceeded) or a rate limit hit (Retry-After header)
503Temporarily unavailable: /health when the database is down, /stream on AWS