Workflow monitors (multi-step API)
A regular HTTP monitor sends a request to a single URL. But a real API health check often requires a sequence: first log in, obtain a token, then call a protected endpoint with that token. A Workflow monitor performs exactly such a chain — up to 20 HTTP steps in a row, passing values from one step’s response into subsequent requests.
Steps are executed strictly in order. If any step fails (timeout,
wrong HTTP status, response body missing expected substring, JSONPath assert mismatch, or
an extractor didn’t find a value) — the chain stops, the monitor goes down and an alert is sent
indicating the failed step.
Creating in the web UI
Section titled “Creating in the web UI”app.notifly.ru → Monitors → Create → “What do you want to monitor?” → “Multi-step scenario” → “HTTP requests (API)”. Existing monitors live on the Workflow tab.
In the dialog you can fill in the steps by hand, have the AI helper build them from a description (the “Suggest step” button, see below), or start “From template…”:
| Template | Steps |
|---|---|
| REST API check with auth | GET /health → POST /login (extract token) → GET /me with Bearer |
| Login form check | GET /login (body contains <form) → POST /login with credentials |
| Chain with JSONPath extraction | list → detail endpoint by the extracted id |
A template only pre-fills the form: replace the URLs and data with your own. The “Test run” button executes the whole chain without saving it (see Test run).
How a step is structured
Section titled “How a step is structured”Each step (WorkflowStep) is one HTTP request with its own checks and
extractable variables:
| Field | Type | Purpose |
|---|---|---|
name | string | human-readable step name (included in the alert) — required |
method | string | GET / POST / PUT / PATCH / DELETE / HEAD — required |
url | string | request URL; supports {{var}} — required |
headers | string | JSON object {"Header":"Value"}; values support {{var}} |
body | string | request body; supports {{var}} |
expectedStatus | int | 0 = any 2xx; otherwise an exact status in [100, 599] |
bodyContains | string | substring that must be present in the response body |
assertJsonPath | string | JSONPath for checking a value in the body (see below) |
assertValue | string | expected value for assertJsonPath (required if assertJsonPath is set) |
extractors | array | list of variables extracted from this step’s response |
The steps themselves are stored in the monitor’s steps field as a JSON array serialized
into a string. The method is upper-cased on save.
Passing variables between steps
Section titled “Passing variables between steps”Each extractor is a {name, jsonPath} pair:
name— the variable name under which the result becomes available in subsequent steps;jsonPath— path to the value in the JSON response.
The extracted value is substituted into the url, headers, and body of subsequent steps
using the {{name}} syntax. Unknown placeholders are left as-is.
JSONPath is simple: $.token, $.user.id, array indices via a dot —
$.items.0.id. The $ root is optional. Bracket notation ($.items[0].id),
filters and * are not supported.
Example: login → token → protected endpoint
Section titled “Example: login → token → protected endpoint”[ { "name": "Login", "method": "POST", "url": "https://api.example.com/auth/login", "headers": "{\"Content-Type\":\"application/json\"}", "body": "{\"email\":\"bot@example.com\",\"password\":\"s3cret\"}", "expectedStatus": 200, "extractors": [ {"name": "token", "jsonPath": "$.access_token"}, {"name": "userId", "jsonPath": "$.user.id"} ] }, { "name": "User profile", "method": "GET", "url": "https://api.example.com/users/{{userId}}", "headers": "{\"Authorization\":\"Bearer {{token}}\"}", "expectedStatus": 200, "assertJsonPath": "$.status", "assertValue": "active" }]The first step logs in and extracts token and userId. The second substitutes both into the URL
and the Authorization header, expects 200, and checks that $.status == "active".
Monitor parameters
Section titled “Monitor parameters”| Field | Default | Constraints |
|---|---|---|
appid | — | channel the alert will go to — required |
name | — | monitor name, 1..200 characters — required |
steps | — | JSON array of steps (as a string): 1..20 items — required |
intervalSec | — | how often to check, 60..86400 sec — required |
timeoutSec | 15 | timeout per step, maximum 60 sec |
alertMessage | — | notification text, 1..2000 characters — required |
alertTitle | empty | notification title |
alertMessageMarkdown | false | treat alertMessage as Markdown |
alertPriority | 0 | notification priority, 0..10 |
notifyOnChange | false | send a notification when any extracted value has changed since the previous successful run |
The channel must be yours or shared with you with send permission.
Alerts
Section titled “Alerts”On failure you get a message titled alertTitle (if empty — “Workflow
«‹name›» failed”) with the alertMessage text followed by the failed step and
the reason:
Шаг: #2 User profileОшибка: HTTP 401 (expected 200)(The Шаг / Ошибка — “Step” / “Error” — labels are currently always in Russian.)
- An alert is sent on the transition to
down, and again if the chain starts failing at a different step. While the same step keeps failing, there are no repeat alerts: the fact that an alert was sent is stored in the monitor’salertedAtfield. - Re-arm: if you change check parameters (
steps,intervalSec,timeoutSec,notifyOnChange) viaPUT,alertedAtis cleared and the check is rescheduled to “now” — if the chain is still failing, a fresh alert arrives. Editing the name or alert texts restarts nothing. - Workflow monitors send no separate recovery notification: after a successful run
the monitor simply returns to
up. - During a maintenance window runs continue and are
recorded in history, but the failure alert is deferred:
alertedAtis not set, and if the chain is still failing after the window, the very first check sends the alert. On-change notifications are not sent during the window.
An alert is a regular channel message: its forwarding to external channels follows
quiet hours (a high alertPriority breaks through the quiet).
If the monitor is added to a status page, up↔down
transitions open and close an automatic incident there.
On-change mode
Section titled “On-change mode”With notifyOnChange: true the monitor remembers the values of all extractors from
the last successful run and sends a notification when any of them changes. The text
contains lines like token: old → new; the default title is “Workflow «‹name›»:
данные изменились” (“data changed”). The first observation of a value doesn’t notify,
and on failure the baseline values are kept. Useful for tracking not only availability
but also that some field in the API response has “drifted” (e.g. build version, price,
stock level).
AI-assisted step build (build-step)
Section titled “AI-assisted step build (build-step)”To avoid describing an HTTP request by hand, there is POST /workflow-monitor/build-step:
you write what the step should do in natural language — the LLM picks the method,
URL, headers, body, checks, and extractors, after which the candidate is actually
executed for verification.
Previously confirmed steps are passed in steps (no more than 20) — they are replayed
to accumulate variables and give the model a sample response from the previous step
(this helps it choose the correct JSONPath).
curl -X POST "$NOTIFLY_URL/workflow-monitor/build-step" \ -H "X-Notifly-Key: <api-token>" \ -H "Content-Type: application/json" \ -d '{ "description": "fetch the current user profile with the Bearer token from the previous step", "steps": [ /* already confirmed steps */ ], "timeoutSec": 15 }'The response contains:
| Field | Purpose |
|---|---|
step | proposed WorkflowStep (method, URL, headers, body, checks, extractors) |
label | short step name |
reason | explanation of why the model chose this request |
verified | true if the candidate was executed successfully |
statusCode | HTTP status of the candidate’s response |
sampleBody | a fragment of the response body (up to 400 characters) |
extractedVars | variables actually extracted by this step |
availableVars | variables available at this step (from previous ones) |
note | populated if verification did not succeed — with the error text |
Test run (test)
Section titled “Test run (test)”Before saving the monitor, run the entire chain via POST /workflow-monitor/test:
curl -X POST "$NOTIFLY_URL/workflow-monitor/test" \ -H "X-Notifly-Key: <api-token>" \ -H "Content-Type: application/json" \ -d '{"steps": [ /* array of steps */ ], "timeoutSec": 15}'Here steps is a plain JSON array (not a string), 1..20 steps; timeoutSec is per
step, 15 by default, and values above 60 are capped at 60. Response:
{"ok": true, "failedStep": 0, "vars": {"token": "ey...", "userId": "42"}}On success ok: true and vars is the final set of accumulated variables. On failure:
{"ok": false, "failedStep": 1, "error": "HTTP 401 (expected 200)"}failedStep is the index of the failed step (zero-based; meaningful only when
ok: false), error is the reason: wrong status, missing substring/JSONPath,
assert value mismatch, a network error or a timeout.
Pause, history, quota
Section titled “Pause, history, quota”POST /workflow-monitor/:id/pause sets the paused status and stops checks,
…/resume returns it to pending and queues the monitor for an immediate check; both
respond with the monitor object. Changing steps, interval, timeout or notifyOnChange
also reschedules the check to “now”.
The monitor’s History tab shows the runs and a chart of the average duration of each
step with per-step failures; via the API — GET /monitor-history/workflow_monitor/:id.
On failure the monitor itself shows lastError and lastFailedStep.
Each run of the chain is one event of the dailyEvents daily quota, regardless of the
number of steps. When the quota is exhausted, checks are deferred until 00:05 Moscow time
the next day — see Quotas and plans.
REST API
Section titled “REST API”| Method and path | API token access | Purpose |
|---|---|---|
GET /workflow-monitor | read | list monitors |
POST /workflow-monitor | write | create a monitor |
PUT /workflow-monitor/:id | write | update |
DELETE /workflow-monitor/:id | write | delete |
POST /workflow-monitor/:id/pause | write | pause checks |
POST /workflow-monitor/:id/resume | write | resume checks |
POST /workflow-monitor/build-step | write | AI selection and verification of one step |
POST /workflow-monitor/test | write | run the chain without saving |
Authentication is the X-Notifly-Key header with an API token (M…):
reading works with any token, changes require a token with write access (otherwise 403).
A token scoped to specific channels sees and changes only the monitors of those channels.
The same operations are available in the Go/Python SDK and the
MCP server.