Skip to content

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.

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…”:

TemplateSteps
REST API check with authGET /health → POST /login (extract token) → GET /me with Bearer
Login form checkGET /login (body contains <form) → POST /login with credentials
Chain with JSONPath extractionlist → 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).

Each step (WorkflowStep) is one HTTP request with its own checks and extractable variables:

FieldTypePurpose
namestringhuman-readable step name (included in the alert) — required
methodstringGET / POST / PUT / PATCH / DELETE / HEAD — required
urlstringrequest URL; supports {{var}} — required
headersstringJSON object {"Header":"Value"}; values support {{var}}
bodystringrequest body; supports {{var}}
expectedStatusint0 = any 2xx; otherwise an exact status in [100, 599]
bodyContainsstringsubstring that must be present in the response body
assertJsonPathstringJSONPath for checking a value in the body (see below)
assertValuestringexpected value for assertJsonPath (required if assertJsonPath is set)
extractorsarraylist 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.

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".

FieldDefaultConstraints
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
timeoutSec15timeout per step, maximum 60 sec
alertMessage—notification text, 1..2000 characters — required
alertTitleemptynotification title
alertMessageMarkdownfalsetreat alertMessage as Markdown
alertPriority0notification priority, 0..10
notifyOnChangefalsesend 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.

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’s alertedAt field.
  • Re-arm: if you change check parameters (steps, intervalSec, timeoutSec, notifyOnChange) via PUT, alertedAt is 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: alertedAt is 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.

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).

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:

FieldPurpose
stepproposed WorkflowStep (method, URL, headers, body, checks, extractors)
labelshort step name
reasonexplanation of why the model chose this request
verifiedtrue if the candidate was executed successfully
statusCodeHTTP status of the candidate’s response
sampleBodya fragment of the response body (up to 400 characters)
extractedVarsvariables actually extracted by this step
availableVarsvariables available at this step (from previous ones)
notepopulated if verification did not succeed — with the error text

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.

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.

Method and pathAPI token accessPurpose
GET /workflow-monitorreadlist monitors
POST /workflow-monitorwritecreate a monitor
PUT /workflow-monitor/:idwriteupdate
DELETE /workflow-monitor/:idwritedelete
POST /workflow-monitor/:id/pausewritepause checks
POST /workflow-monitor/:id/resumewriteresume checks
POST /workflow-monitor/build-stepwriteAI selection and verification of one step
POST /workflow-monitor/testwriterun 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.