API Flow Monitoring

Run a sequence of HTTP requests as one check — log in, carry the token forward, call the endpoint that actually matters — and get told when any step in that chain breaks.

What is an API flow monitor?

An API flow monitor runs an ordered list of HTTP requests on a schedule. Each step can send headers and a body, pull values out of the response, and assert things about what came back. Values captured in one step are available to every step after it, so you can authenticate first and then call the endpoint that needs the token.

The whole flow is one check. If every step passes, the monitor is Up. If any step fails, the monitor is Down and the run tells you exactly which step, and why.

Example: "POST to /login with my service account, take the token out of the response, GET /me with that token as a Bearer header, and require a 200. If any of that stops working, page me."

This is the check for APIs whose health can't be seen from a single request. A plain HTTP check on /me returns 401 forever — which is a perfectly healthy answer from a perfectly healthy API. Only a flow can tell you whether a real caller could have got in.


When to use one

  • Anything behind a login. Token-protected endpoints, session-cookie APIs, service accounts. The auth handshake is the thing most likely to break, and it's the thing a single-URL check can never see.
  • Multi-step business transactions. Create a cart → add an item → price it → read the total back. You're checking that the sequence works end to end, not that four URLs each return 200 in isolation.
  • Contract checks, not just availability. Assert that $.status is still "ok", that $.items isn't empty, that the response is under 200 KB, that a deploy didn't quietly change a field name. An HTTP check that only reads the status code will happily report Up while your API returns structurally wrong JSON.
  • Third-party APIs you depend on. Payment providers, mail relays, geocoders — anything whose outage becomes your outage. Point a flow at their sandbox or a read-only endpoint with your real credentials and you find out before your customers do.
  • Rotating-credential canaries. If a key expires, a certificate rolls, or a service account gets deprovisioned, the flow fails on the login step and names it.

When to use something else

You want to knowUse this instead
"Is this one URL returning 200 and loading fast?"Website monitor — one request, with keyword and JSON-path checks, security headers, SSL expiry, redirect chains and content-change detection. If your check is a single request, use it: it does far more per request than a one-step flow.
"Does the browser UI still work — can a user actually click through the signup?"Browser monitor — a real Chromium session with screenshots, video on failure and Core Web Vitals. An API flow speaks HTTP; it never renders a page or runs your JavaScript.
"Is the port/host/database reachable?"Port or Ping. A flow needs a working HTTP endpoint to talk to.
"Did my nightly job run?"Cron job monitor — the job pings Enori, not the other way around.

A useful pairing is a Website monitor on your public health endpoint plus an API flow on your authenticated path. If both go Down, the service is gone. If only the flow goes Down, the service is up and your auth broke — which is a completely different 3am.


How it works

On every check, Enori runs your steps in order, top to bottom:

  1. Your encrypted variables are decrypted into the run and any {{token}} placeholders in the step's URL, headers and body are filled in.
  2. The request is sent. Cookies set by one step are automatically sent back on later steps in the same run.
  3. The response body is read (up to a 2 MB cap).
  4. Your extractions run — values are pulled out and become variables for later steps.
  5. Your assertions run against the response.
  6. If the step passed, the flow moves to the next step. If it failed, the flow stops there by default and the remaining steps are recorded as Skipped.

What counts as Up: every step completed and every assertion on every step passed.

What counts as Down: any step failed for any reason, or the flow ran out of time. The check records the failing step's index and error code so the detail page can point straight at it.

Cookies live for the length of one run and are then thrown away. Each run starts from a clean jar, so a flow can't "stay logged in" between checks — it logs in every time, which is the point.

Response time for the monitor is the total wall-clock duration of the whole flow, not any one request. A five-step flow's response-time chart is the sum of its steps, so it will naturally sit higher than a single-request monitor's.

How often. You pick the interval when you create the monitor: 1 minute, 5 minutes, 15 minutes, 30 minutes or 1 hour — default 5 minutes.

While a monitor is Down, Enori re-checks it every 30 seconds regardless of your normal interval, so a recovery is picked up within half a minute.

Where checks run from. API flows run from Enori's own infrastructure in Europe, from one location. Unlike Website, Ping, Port and DNS monitors, they are not re-verified from a second region: a failure is recorded on the first observation, with no cross-region confirmation step behind it. There is no region choice on this monitor type — not in the wizard, not in the editor — and nothing you set elsewhere changes where a flow runs.


Setting one up

Go to Monitors, click Add Monitor, and pick API flow. That opens a dedicated four-step wizard — Identity → Steps → Variables → Review — separate from the wizard the other monitor types use.

Screenshot: the API flow card on the monitor-type picker.

Step 1 — Identity

FieldRequiredWhat to enter
Monitor nameYesSomething you'll recognise in an alert. e.g. "Production login flow". Up to 100 characters.
Base URLNoA convenience only — it pre-fills the URL of the first request built by the login template. e.g. https://api.example.com. It is not a prefix: every step still carries its own full absolute URL.
GroupNoGroups related monitors on the list page.

Step 2 — Steps

This is where the flow is defined. The counter at the top reads "N of 20 steps"20 steps is the hard maximum.

Start with the login template. The Use login template button (top right) replaces whatever is in the list with a working two-step flow:

  1. POST {base}/login with Content-Type: application/json and a body of {"username":"{{username}}","password":"{{password}}"}, extracting token from $.token, asserting status equals 200, with 1 retry.
  2. GET {base}/me with Authorization: Bearer {{token}}, asserting status equals 200.

It uses your Base URL from step 1 (or https://api.example.com if you left it blank). Adjust the URLs, the body field names and the extract path to match your API, and the Variables step will then prompt you for username and password. This is the fastest correct start for almost every flow.

If you already have the request working in a terminal, Import from cURL on a step is the other fast start — paste the command and the method, URL, headers and body are filled in for you.

Each step is a card you can expand, collapse, drag to reorder, and delete.

Screenshot: the Steps screen with the login template applied — two cards, the extract row on step 1, the assertion row on both.

Step fields

FieldNotes
Step typehttp_request (the default and the one you want), sleep, or run_subtest. See Step types.
Step nameOptional label, up to 80 characters. Worth filling in — it's what the results page shows instead of a bare step number.
Import from cURLPaste a curl command and the request half of the step is filled in from it. See Import from cURL.
MethodGET POST PUT PATCH DELETE HEAD OPTIONS.
URLRequired for http_request. A full absolute http:// or https:// URL, up to 2048 characters. May contain {{variables}}.
HeadersName/value rows. Values may contain {{variables}}. See Headers.
AuthenticationNone (default), Basic, or Bearer — referencing an encrypted variable by name. See Basic and Bearer authentication.
Body + content typeFree text, with a selector for JSON / form / XML / plain text / GraphQL. May contain {{variables}}. See Body and content type.
Timeout (ms)1000–60000. Leave empty for the default 10 s. See Execution controls.
Follow redirectsOn by default for a step you add; off means a 3xx is the response this step asserts against.
Ignore the flow's cookiesOff by default. On, this step neither sends nor stores the shared cookies.
Retries0–3, default 0. See Retries.
Backoff (ms)0–30000, default 500.
ExtractValues to capture from the response — see Extracting values.
AssertionsWhat must be true about the response — see Assertions.
Continue flow on assertion failureOff by default. Read what this actually does before turning it on — it is not "ignore this failure".

The Available variables disclosure at the bottom of the screen lists the {{tokens}} your steps currently reference.

Everything in that table is equally available later in the monitor's editor — the Steps tab is the same editor as this screen. See Changing a monitor later.

Step 3 — Variables

Every {{token}} you used on the Steps screen is listed here automatically, waiting for a value. See Variables and secrets for the full detail. If your flow uses no placeholders, this screen says "No variables needed" and you can move on.

Step 4 — Review & create

Two settings live here, plus a summary:

  • Check interval — 1 minute, 5 minutes, 15 minutes, 30 minutes or 1 hour. Default 5 minutes. Pick carefully: the interval cannot be changed from the UI after creation (see Changing a monitor later).
  • Flow timeout — a slider from 5 s to 120 s in 5-second steps, default 60 s. This is the hard ceiling for the entire flow, including every step's own timeout and all retry backoff. If the flow is still running when it expires, the run fails with FLOW_TIMEOUT and any steps that hadn't started are marked Skipped.

Below them sits Test flow — run the flow once, right now, before you commit to it. Use it. See Test flow.

Click Create monitor. Enori queues a first check immediately, so you normally see a result within about 30 seconds.

Straight after you create it: attach an alert channel

The API flow wizard does not ask which alert channels to use, and a monitor with no channel attached records its failures but delivers no alert at all. The detail page shows an amber banner saying so.

To fix it, go to Monitors, tick the checkbox next to your new flow, open Bulk edit, enable Alert channels, pick your channels, and save. The same modal also sets Failure threshold, Escalation policy, and the alert on/off toggles. (This works on a selection of one.) Alert channels themselves are created under Settings → Alerts.


Test flow: trying a draft before you save it

The wizard's Review screen has a Test flow button. It runs the steps you've just written against their real targets, once, and shows you what happened — before the monitor exists.

Without it, the only way to find out whether a flow works is to create it and wait out the interval, which defaults to five minutes. Getting the answer in a few seconds instead is the difference between iterating on a flow and guessing at one.

Nothing is saved. No monitor, no check result, no alert, no incident — and it does not touch anyone's uptime. It reads the form as it stands, so it always tests exactly what Create monitor would save.

What you get back is the same thing the results page shows for a real run: an overall pass/fail, the total duration, an error code if there was one, and a row per step with its status, HTTP status and duration. A failing test is the useful case — that's what you came to find out — so it renders inline rather than as an error.

Four things to know:

  • Real requests go out. The test hits your actual endpoints with your actual credentials. If a step creates something, it creates it.
  • It's capped like Check Now. Your plan's manual-check allowance (Base 20 / Pro 100 / Business 500) governs it, plus 5 a minute. Over budget, you're told to save the monitor and use Check Now instead.
  • The test budget tops out at 60 seconds, while the flow-timeout slider goes to 120. If you set a longer flow timeout, the test runs on the shorter budget and says so under the result — so a FLOW_TIMEOUT here doesn't necessarily mean the saved monitor will time out.
  • It can't test a flow containing a run_subtest step, because a subtest is resolved from a saved monitor and a draft isn't one. The button is disabled and says why.

There is no Test flow inside the editor of an existing monitor — once it's saved, Check Now is the equivalent.


Step types

http_request — the real work. Everything above applies.

sleep — pause between steps. One field, Sleep (ms), 0–30000. Useful when the thing you're about to check is created asynchronously by the previous step. Remember that the sleep is spent from your flow timeout budget.

run_subtest — one flow calls another, so a shared "log in" flow can be written once and reused by several monitors. Pick the step type and fill in the Subtest monitor ID box with the id of the flow you want to call.

At check time the referenced flow's steps are spliced into this one at that position — they run as steps of your flow, appear as its steps on the results page, and its variables become available to the steps that follow.

Before it will save, the target has to be marked as reusable, and that mark is API-only on an API flow today. Set it once with PATCH /api/monitors/{id} and "isSubtestTarget": true on the flow you want to reuse; there is no toggle for it in the API flow editor (the Browser monitor editor has one — API flow doesn't yet). After that first mark, Enori looks after the flag itself: it's set automatically on any flow a monitor starts referencing, and cleared again when the last reference to it goes away. Until the mark is there, saving the calling flow is refused with SUBTEST_NOT_TARGET.

Four more rules, all enforced when you save rather than at check time:

  • The target must be one of your own API flow monitors. Another type is SUBTEST_WRONG_TYPE; an id that isn't yours, or doesn't exist, is SUBTEST_NOT_FOUND.
  • One level deep. A flow you call may not itself contain a run_subtest step — SUBTEST_DEPTH_EXCEEDED.
  • 20 steps after expansion. Your steps plus every step pulled in from the flows you call must total 20 or fewer, otherwise STEP_LIMIT_EXCEEDED. A 5-step flow calling a 6-step login flow costs 10 steps, not 5.
  • Names still have to be unique. The target's variables and extracts share the one namespace with yours, so a token on both sides is VARIABLE_NAME_COLLISION — see Name rules and collisions.

And one thing you lose: a flow containing a run_subtest step can't be tried with Test flow. Save it, then use Check Now.


Headers

Every http_request step has a Headers editor on its card — a list of name/value rows with an Add link — in the create wizard and in the edit modal alike. Type whatever the request needs: an X-API-Key, a tenant header, a non-JSON Content-Type.

Values may contain {{variables}}. Authorization: Bearer {{token}} is the usual case, and the value is resolved at check time from your variables and from anything an earlier step extracted. Names are not — a name is sent exactly as you typed it.

Three things fill headers in for you, so you often don't start from an empty list:

  1. The login template sets Content-Type: application/json on the login request and Authorization: Bearer {{token}} on the follow-up.
  2. Automatic Bearer pre-fill. When you click Add step and the previous step extracts a variable named token, access_token, jwt, bearer or auth_token, the new step is created with Authorization: Bearer {{that_name}} already on it. Naming your extract token is therefore worth doing.
  3. Import from cURL brings across every -H in the command you paste.

Rules the editor holds you to when you save:

  • No two rows may share a name. The comparison ignores case, because HTTP does: Accept and accept are the same header, and the last one would silently win.
  • A value needs a name. A row with a value and no name blocks the save. A row that is blank on both sides is simply dropped — that's the "clicked Add, changed my mind" row.
  • Some names are refused outright: Connection, Content-Length, Host, Keep-Alive, Proxy-Authorization, Proxy-Connection, TE, Trailer, Transfer-Encoding, Upgrade. Those describe the connection rather than your request, and setting them by hand breaks it. Authorization and Cookie are allowed.
  • No control characters in a name or a value — a stray newline is how header injection works, so it's rejected rather than stripped. (Tab is fine; it's legal in a header value.) The same check runs again at check time on the resolved value, so a variable holding a newline fails the check with INVALID_HEADER_VALUE rather than putting two headers on the wire.

A few behaviours worth knowing once headers are set:

  • A Cookie header you set yourself is dropped. The flow's own cookie jar is the single source of truth for cookies, so that later steps automatically replay what earlier steps were given. (This is also why -b/--cookie isn't brought across by the cURL import — it would be dropped.)
  • A body is sent as application/json unless the step carries an explicit Content-Type — which the content-type selector sets for you.
  • On a redirect to a different origin, all of your headers are dropped except Content-Type. A header you scoped to api.example.com is not replayed to whatever host that server redirects you to. This is deliberate — it's what stops a redirect from leaking your credentials.
  • A step that sets an Authorization header and uses the authentication block is refused. Pick one.

Import from cURL

Every http_request step card has an Import from cURL link. Paste the command you already run in a terminal, click Import, and the step's method, URL, headers, body and follow-redirects setting are filled in from it. --max-time also becomes the step's timeout.

The import replaces the request half of the step — that's the point, since a curl command describes a whole request and merging would leave a header behind from the previous one. What you built on top of the request is untouched: the step name, assertions, extracts and the retry policy all survive.

Anything it couldn't take literally is listed back to you, in an amber "Imported, with changes" box under the card. Nothing is dropped silently. The notes you're most likely to see:

You pastedWhat happens
-d '…' with no Content-Typecurl would send application/x-www-form-urlencoded, so that header is written out explicitly — because a bodied step with no content type is sent as JSON here. Change it in the content-type selector if your API wants JSON.
No -LRedirects are not followed on the imported step — curl doesn't follow them without -L, and the import is faithful to the command you tested rather than to Enori's own default. Tick Follow redirects afterwards if you want them.
--compressedIgnored, and it is not a redirect flag: Enori already negotiates and decodes gzip/deflate on every request, so there is nothing to set per step. If you meant "follow redirects", that's -L.
-b / --cookie / --cookie-jarIgnored on purpose. A flow keeps one cookie jar for the whole run and drops any Cookie header set on a step, so an imported cookie could never be sent. Log in in an earlier step instead.
A second URLIgnored — a step requests one URL. Add another step for it.
A method a step can't sendLeft as the implied one (HEAD for -I, POST when there's a body, otherwise GET), and the note says so.
-u user:passBecomes an Authorization: Basic … header — with the password in it, in plain text. See the warning below.
No scheme (curl api.example.com/x)Imported as https://.

Two things it refuses outright rather than guessing: an unterminated quote (half a body imported invisibly is worse than an error), and a non-HTTP URL (ftp://, file:// — Enori only monitors http and https).

Don't leave a password in a header. -u user:pass, and any Authorization or X-API-Key you pasted, land in the step as literal text. Move the secret to a variable, which is encrypted, and reference it — or better, delete the header and use the authentication block, which can only ever hold a variable's name. If the command carried an Authorization header and the step already had an authentication block, the import switches the block off and tells you, because a step can't have both.


Body and content type

Next to the Body label is a content-type selector. It writes the step's Content-Type header for you, so you don't have to know that's what a "body type" is.

OptionSent as
No content typeNo Content-Type header at all. The engine then sends the body as application/json.
JSONapplication/json
Form (URL-encoded)application/x-www-form-urlencoded
XMLapplication/xml — an existing text/xml is recognised and left alone rather than rewritten
Plain texttext/plain
GraphQLapplication/json. That is correct, not a bug: GraphQL over HTTP is JSON. What the option gives you is the right envelope — put your query and variables in {"query": "…", "variables": {}}.
Custom (set in Headers)Whatever Content-Type row is already on the step, untouched.

The selector never invents a value. A step carrying something it doesn't know — application/vnd.api+json, say, set through the API — reads back as Custom, is left exactly as it arrived, and saves unchanged.

The body itself is free text and may contain {{variables}}; the placeholder changes to match the type you picked.


Basic and Bearer authentication

A step can carry HTTP authentication directly, instead of you hand-building a header. Pick Basic or Bearer from the Authentication selector on the step card.

There is no password field, and that's deliberate. What you enter is the name of an encrypted variableapi_password, service_token — and Enori looks the value up at check time. The credential lives only in your encrypted variables, never in the step's configuration — so reading the monitor back, through the UI or the API, returns the variable's name and nothing else. A text box holding the secret would have thrown that property away.

TypeYou provideWhat gets sent
BasicA Username (plain — a username isn't a secret) and a Password variableAuthorization: Basic
Bearer tokenA Token variableAuthorization: Bearer

The variable box suggests the variables you've declared, but it isn't limited to them: a name captured by an earlier step's extract works too, which is how you carry a freshly-issued token into the next step without writing the header yourself.

Things that will stop you:

  • A username with a colon in it is rejected — Basic encodes username:password, so a colon makes it ambiguous.
  • An Authorization header on the same step is refused, at save and again at check time as AUTH_HEADER_CONFLICT. One source per header.
  • A variable that doesn't exist fails the check with UNRESOLVED_VARIABLE, naming the variable — never a value.
  • A variable that's empty fails with AUTH_SECRET_EMPTY. An empty credential would otherwise be sent as a valid-looking header and get a puzzling 401.

The built header is treated as a secret for the rest of the run: it's masked out of every artifact, error message and assertion value, and it is dropped on a redirect to a different origin like any other header.


Execution controls

Three per-step settings under Execution on the step card.

Timeout (ms) — how long this one step may take, 1–60 seconds. Leave the box empty for the default of 10 s. This is the range the check engine actually enforces; a step stored outside it (through the API, which accepts a wider range) is brought inside the range when you open the step, so the number on screen is the number in force. The whole flow is still bounded by its own flow timeout, which is the smaller budget in most flows.

Follow redirects — on by default. Turn it off when the redirect is what you're checking: with it off, a 301 is the response this step asserts against, so status equals 301 and a header Location contains … become meaningful. With it on, a step follows at most 5 hops before failing with TOO_MANY_REDIRECTS, and your headers are dropped on any hop to a different origin.

Ignore the flow's cookies — off by default. Turn it on for a step that must not inherit the session the earlier steps established: it neither sends the flow's cookies nor stores the ones it's given. The canonical use is proving that a protected endpoint really is protected — call it without the session and assert status equals 401.


Retries and backoff

Retries (0–3, default 0) apply in exactly two situations — both of them "the target hasn't given us an answer yet", as opposed to "the target gave us an answer we don't like":

  1. Transport failures — connection errors, TLS failures and timeouts. Nothing came back.
  2. HTTP 429 with a Retry-After header — the server is explicitly saying not now, ask again in N.

Everything else is a verdict, not a flake. A failed assertion is never retried: if your API answered 500, that is a real answer, and retrying it would just delay the alert by three attempts. A 429 without a usable Retry-After is treated the same way — a plain answer, not a request to come back.

Backoff (ms) is linear: the wait before attempt N is backoff × N. With the default 500 ms, retries wait 500 ms, then 1 s, then 1.5 s. On a throttled step the server's Retry-After wins instead — capped at 30 seconds, so a hostile Retry-After: 86400 can't park your check for a day. Both the delay-seconds form (Retry-After: 3) and the HTTP-date form are understood.

All retry waiting comes out of your flow timeout.


Continue flow on assertion failure

The checkbox at the bottom of each step card does exactly one thing: it stops the failure from short-circuiting the rest of the flow. Later steps still run.

It does not make the failure harmless. The run is still recorded as failed, the monitor still goes Down, the failing step is still named as the cause, uptime still takes the hit, and your alert still fires. There is no way to mark a step as optional or informational.

So use it when you want diagnostic breadth — "step 2 is broken but I still want to know whether steps 3 and 4 would have worked" — and leave it off (the default) for the normal case where later steps depend on earlier ones anyway.


Assertions

Each step can carry any number of assertions. Every one of them must pass or the step fails with ASSERT_FAILED. A step with no assertions passes as long as the request completed — even on an HTTP 500 — so in practice you want at least status equals 200 on every request.

Add one with the Add link in the step card's Assertions row. Each assertion is four fields: type, path, operator, expected.

The six types

TypeWhat it looks atPath fieldWorked example
statusThe HTTP status codenot usedstatus · equals · 200
json_pathA value inside a JSON response bodythe JSONPathjson_path · $.user.email · exists
headerA response headerthe header nameheader · Content-Type · contains · application/json
response_textThe raw response body as textnot usedresponse_text · contains · "status":"ok"
response_timeHow long this step took, in msnot usedresponse_time · lt · 2000
response_sizeThe response body size in bytesnot usedresponse_size · gt · 100

The nine operators

OperatorMeans
equals / not_equalsExact match.
contains / not_containsSubstring.
exists / not_existsThe value or header is present / absent.
gt / ltNumeric greater-than / less-than.
matches_regexThe value matches a regular expression.

Not every operator is meaningful for every type. The combinations that work:

TypeSupported operators
status, response_time, response_sizeequals, not_equals, gt, lt. (exists / not_exists are accepted but degenerate — the value always exists, so exists always passes and not_exists always fails.)
json_pathall nine
headerall nine — including gt / lt, which compare the header's value as a number
response_textequals, not_equals, contains, not_contains, matches_regex

An unsupported combination fails — and tells you it was the configuration, not the API. status contains 20 is not a loose match on 200 and 204; it's a permanently failing assertion, and its actual value reads UNSUPPORTED_ASSERTION: assertion type 'Status' does not support operator 'Contains' — the value is numeric; use Gt or Lt for a range. Every unsupported pair is unsupported because a better-typed one exists, so the message names the one you wanted.

Numeric header assertions

header · x-ratelimit-remaining · gt · 100 compares the header's value as a number. Signed and fractional values work (clock skew, load averages). Three outcomes, kept distinct on purpose:

SituationVerdictWhat actual shows
Header present and numericThe comparisonthe raw value
Header absentFails — an absent header doesn't satisfy "> 100"(missing)
Header present but not a numberFailsapplication/json (not numeric) — so a numeric operator aimed at a textual header reads as your mistake, not a broken comparison

A header that appears more than once has its values joined with , , which correctly does not parse as a number.

Case sensitivity, exactly

This trips people up, so it's worth stating precisely:

  • json_path and response_textequals and contains are case-sensitive.
  • headerequals is case-sensitive, but contains is case-insensitive. The header name you put in the path field is always matched case-insensitively.
  • matches_regexcase-insensitive, for every type.

JSONPath assertions

Paths use standard JSONPath: $.token, $.data.items[0].id, $.user.roles[2].

A path with a [*] wildcard fans out across every match. Positive operators pass when any element satisfies them; not_equals and not_contains pass only when no element does.

Example: json_path · $.services[].status · equals · up passes if at least one service is up. To require that none are down, use json_path · $.services[].status · not_equals · down.

If the response body isn't valid JSON, every json_path assertion fails — except not_exists, which passes.

matches_regex is guarded against runaway patterns: a single evaluation is cut off after 100 ms, and a [*] fan-out after 500 ms across all its elements. A timed-out regex is reported as a failed assertion (never a silent pass), with regex timed out (>100 ms — ReDoS guard) in the actual value.

More worked examples

GoalTypePathOpExpected
The call succeededstatusequals200
Not a server error (any 2xx/3xx/4xx)statuslt500
A token came backjson_path$.tokenexists
The account is the right onejson_path$.user.emailequalssvc@example.com
The list isn't emptyjson_path$.items[0]exists
No item is in a failed statejson_path$.items[*].statenot_equalsfailed
The API is still returning JSONheaderContent-Typecontainsjson
A required header is present at allheaderX-Request-Idexists
We're not about to be rate-limitedheaderX-RateLimit-Remaininggt100
This endpoint stays fastresponse_timelt1500
The response isn't suspiciously emptyresponse_sizegt50
An error string never appearsresponse_textnot_containsInternal Server Error

Extracting values and variables

An extract captures something from a step's response and makes it available to every later step as {{name}}.

Add one with the Add link in the step card's Extract row. Each extract is name, source, path — plus a pattern box when the source is regex.

Sources

SourcePath fieldCaptures
json_pathrequired — the JSONPathThe value at that path. e.g. $.token, $.data.session.id
headerrequired — the header nameThe response header's value (matched case-insensitively; multiple values joined with , ).
cookierequired — the cookie nameA cookie from the flow's jar after this response. Cookie names are matched case-sensitively.
status_codenot usedThe HTTP status code as text.
response_textnot usedThe whole response body.
regexoptional — a header name; leave it blank for the bodyWhat your pattern matched. See Capturing with a regular expression.

If an extract can't find its value, the step fails with EXTRACT_FAILED:{name} — it is an assertion in disguise. Extraction runs before assertions, so a missing token fails the step as an extract failure, not an assertion failure.

For session cookies, prefer the cookie source over reading the Set-Cookie header: a response that sets several cookies produces one joined header value that's awkward to use, whereas the cookie source gives you the one you named.

Capturing with a regular expression

For the responses JSONPath can't reach — a plain-text body, an id buried in a Location header, a token in an HTML page — set the source to regex and fill in the pattern box.

What the pattern runs against depends on the path box:

  • Path empty → the response body.
  • Path set → that header's value, and only that header. If the header isn't in the response, the extract fails — it does not quietly fall back to the body. You asked for a header; matching something else would put a value from somewhere nobody looked at into a later step.

Which part you get:

  • If the pattern defines at least one capturing group, you get group 1. /orders/([A-Za-z0-9_]+)$ gives you just the id.
  • If it defines none, you get the whole match. \d+ gives you the digits it matched.
  • A named group works through the same slot — a lone (?…) is group 1.
  • Don't mix named and unnamed groups in one pattern. Group numbering puts the unnamed ones first, so "group 1" would silently mean the wrong one. Use exactly one capturing group when you care which you get.
  • A group that exists but didn't participate in the match — one side of an alternation, an optional group that matched nothing — is a failure, not an empty string. An empty value would let a later step build a URL with a silently missing segment.

Matching is case-insensitive, the same as a matches_regex assertion — so the same pattern gives the same answer in both places.

Two guards, and a timeout is never read as a no-match. A single evaluation is cut off after 100 ms, and all the regex extracts on one step share a 500 ms budget. When either fires, the extract fails with REGEX_TIMEOUT in its reason and says the result is unknown. That wording is the point: "matched nothing" means your pattern or the response is wrong, while "we stopped looking" means Enori never finished and can't tell you either way. If you see it, the pattern is doing catastrophic backtracking — look for nested quantifiers like (a+)+ — or you have too many regex extracts on one step.

Using the value: {{name}} syntax

Write {{name}} anywhere in a later step's URL, body, or header value.

text
URL   https://api.example.com/orders/{{order_id}}
Body  {"session":"{{session_id}}"}

Rules that actually bite:

  • The token is {{name}} with no spaces. {{ token }} is not recognised and will be sent literally.
  • Only letters, digits and underscores are recognised inside the braces.
  • Names are matched case-sensitively at check time. Variable names must be lowercase, so {{token}} works and {{Token}} does not — and the wizard will not warn you, because its own check is case-insensitive. A mismatch shows up as an UNRESOLVED_VARIABLE failure on the first check.
  • A step can only use values captured by an earlier step. Referencing something extracted later in the flow leaves the token unresolved and fails the step.
  • If any token in a step's URL, body or header can't be resolved, the step fails with UNRESOLVED_VARIABLE rather than sending a request with a literal {{token}} in it.

Name rules and collisions

Extract names and variable names share one flat namespace and one rule:

Lowercase letters, digits and underscores; must start with a letter; maximum 50 characters. ^[a-z][a-z0-9_]{0,49}$

Every name must be unique across the whole monitor. If two different sources would introduce the same name — two steps extracting to token, or a step extracting token while a stored variable is also called token — the monitor is rejected on save with VARIABLE_NAME_COLLISION, and the error names both definitions. This is deliberate: silently overwriting would make the flow behave differently depending on step order.


Variables and secrets

Variables hold the values you don't want to type into a step — passwords, API keys, TOTP seeds. They are encrypted before storage and never returned by the API afterwards. Once saved, you can rename or replace a variable but you cannot read its value back, in the UI or through the API.

Variables are per monitor. There is no account-wide variable store; each flow carries its own set.

On the wizard's Variables screen, every {{token}} referenced in your steps already has a row waiting. Each row is:

  • Name — the token name, subject to the rule above.
  • Typetext or totp.
  • Value — masked as you type, with an eye icon to reveal it while you're checking it.

Maximum 20 variables per monitor.

TOTP

Set a variable's type to totp and put your Base32 secret in the value field — the same string an authenticator app scans from a QR code. Enori then generates a fresh 6-digit code at the moment the request is sent, so {{totp}} in a body or header is a currently-valid code on every check.

The secret is validated as Base32 when you save; an invalid one is rejected immediately rather than failing silently on every check. A variable named exactly totp, 2fa, otp, mfa or authenticator defaults to the TOTP type automatically — any other name defaults to text, and you can change it either way.

How secrets appear in results

Two different things are masked, and you need both.

Values you declared. Every variable value, and every value extracted from a response, is treated as sensitive for the rest of the run. Before anything is stored, those values are replaced with •••••• in:

  • captured request and response bodies,
  • captured request and response headers,
  • error messages and error detail,
  • assertion expected and actual values.

URL-encoded and HTML-encoded forms of the same value are masked too, so a password that shows up percent-encoded in a URL doesn't survive. Values shorter than three characters are not masked — a two-character string appearing everywhere would shred the artifact and isn't a real credential.

Headers known to carry credentials, by name. Value-matching alone can't protect a secret nobody declared — the Set-Cookie your target sends back, or an Authorization you typed straight into a header row. So in a captured header table, these headers have their value replaced outright, whatever it looks like:

Authorization · Proxy-Authorization · Cookie · Set-Cookie · WWW-Authenticate · Proxy-Authenticate · X-API-Key · Api-Key · X-Auth-Token · X-Amz-Security-Token

Matching ignores case, so set-cookie is covered too. The header name always survives — only the value is replaced. That's deliberate: this is a debugging record, and "no Set-Cookie came back" and "a Set-Cookie came back and we hid it" are different diagnoses, so you can still see that the header was there.

On top of that, captured request/response bodies and headers on failed steps are erased entirely after 7 days, independently of your plan's history retention. The step's metadata — status, duration, which assertion failed — stays for the full retention window; the bodies do not.


Reading the results

Open the monitor from Monitors. The API flow detail page has three parts.

The header strip

Six cells across the top:

CellShows
StatusHealthy / Failing / Paused / Maintenance, with the latest error code underneath.
Last runHow long ago the most recent run was, and whether it failed.
StepsHow many steps the flow has — and, on a failure, "failed at #N".
Last durationTotal wall-clock time of the last run, green if it passed, red if it didn't.
RegionA location label. It is not a setting and not a choice you made — API flows are not region-selectable and always run from Enori's default location, so read past it.
Check scheduleYour interval.

Run timeline

The main panel. It shows the latest run by default; the Pick a run dropdown switches to any of the last 20.

The header line gives the verdict (Success / Failed), the total duration, a N passed · N failed · N steps count, and — on a failure — the run's error code as a red chip.

Under it, one row per step: the step number, a status icon and label, an HTTP method chip, your step name, the HTTP status code (colour-coded by 2xx/3xx/4xx/5xx), a retry badge if the step was retried, and the step's duration.

Step statuses:

StatusMeaning
SuccessThe step completed and all its assertions passed.
Assert failedThe server answered, but at least one assertion was false.
HTTP errorThe request never got a usable response — connection refused, DNS failure, TLS error, blocked target, unresolved variable.
TimeoutThe step, or the whole flow, ran out of time.
Extract failedThe response arrived but a value you asked for wasn't in it.
Response too largeThe body exceeded the 2 MB cap, so extractions and assertions were not run.
SkippedThe step never ran, because an earlier step failed or the flow timed out.

Step detail drawer

Click any step row. A drawer opens on the right with:

  • Summary — step type, duration, HTTP status, response size, retry count.
  • Error — the error code and its detail, when the step failed.
  • Extracted variables — the names captured by this step (names only; values are never shown).
  • Assertions — one row per assertion with a pass/fail icon, the type, path and operator, and the expected vs actual values side by side. This is normally the fastest way to see what changed.
  • Failure artifacts — see below.
  • A truncation note if the response was cut at the 2 MB cap.

Failure artifacts

On a failed step, Enori keeps the actual request and response — headers and body — so you can see what really went over the wire without reproducing it by hand. Two collapsible sections, Request and Response, each showing a header table and the body.

Bodies are capped at 16 KB each; an amber Truncated chip appears when the cap was hit. Everything is masked and scrubbed before storage — both your own values and any credential-carrying header, by name — and the whole artifact is erased after 7 days.

Recent runs

The right rail lists the last 10 runs — pass/fail, how long ago, the failing step number, and the duration. It's a read-only summary; use the Pick a run dropdown in the timeline panel to actually open an older run.

Screenshot: an API flow detail page with a failed run — the timeline showing step 2 red, the drawer open on its assertion table.

What the error codes mean

The code you see on the header strip, on the timeline chip and in your alert:

CodeIn plain language
ASSERT_FAILEDThe server answered, but something you asserted wasn't true. Open the step drawer — the assertion table shows expected vs actual.
EXTRACT_FAILED:{name}The named value wasn't in the response. Usually the response shape changed, or an error body came back where a success body was expected.
HTTP_ERRORThe request never completed — connection refused, DNS failure, TLS problem. Nothing to do with your assertions.
TIMEOUTOne step took longer than its own timeout.
FLOW_TIMEOUTThe whole flow ran past its flow-timeout budget. Steps that hadn't started are Skipped.
RESPONSE_TOO_LARGEThe response body exceeded 2 MB. Extractions and assertions were skipped, because judging a truncated body would give a wrong verdict.
SSRF_BLOCKEDA step URL resolved to a private, internal or reserved address. Enori only checks publicly reachable endpoints.
TOO_MANY_REDIRECTSThe step followed more than 5 redirects.
UNRESOLVED_VARIABLEA {{token}} in the URL, body or a header had no value. Check spelling and case, and that the value is captured before the step that uses it.
INVALID_URLAfter variables were filled in, the step's URL wasn't a valid absolute URL.
NO_STEPSThe monitor has no steps to run.
SUBTEST_NOT_FOUND · SUBTEST_WRONG_TYPE · SUBTEST_NOT_TARGET · SUBTEST_DEPTH_EXCEEDED · STEP_LIMIT_EXCEEDEDProblems with a run_subtest step — the target is missing, is the wrong monitor type, isn't marked reusable, nests another subtest, or the expanded flow exceeds 20 steps. All five are normally caught when you save; see Step types.
AUTH_HEADER_CONFLICT · AUTH_SECRET_MISSING · AUTH_SECRET_EMPTYThe step's authentication block clashes with an Authorization header you also set, names no variable, or names one that is empty.
INVALID_HEADER_VALUEA header value — typed, or built from an auth variable — contains a control character. Usually a stray newline pasted into a variable.
VARIABLE_NAME_COLLISIONTwo sources define the same variable name. Rename one.
INVALID_STEP_URLRejected at save: a step URL used a scheme other than http/https, or pointed at a literal private IP.

In an alert message the code is followed by the step it happened on. That number is zero-basedASSERT_FAILED at step 1 means the second step, the one shown as 2 on the detail page. The detail page and the drawer both count from 1.


Running a flow on demand

The monitor's action row — Snooze · Check Now · Edit — has a Check Now button. It queues one run of the flow straight away, outside the schedule; the page updates itself when that run finishes, normally within a few seconds. The result counts like any other check: it goes into your uptime, and it can take the monitor Down or bring it back Up.

Two limits apply, and both come from your plan:

PlanManual checks per dayCooldown between manual checks on one monitor
Base202 minutes
Pro1001 minute
Business50030 seconds

The daily allowance is per account, across every monitor, and it resets at midnight UTC. The cooldown is per monitor. On top of both, an account may trigger at most 5 manual checks a minute.

The button tells you where you stand: hovering shows how many checks you have left today, it reads Wait Ns while a cooldown is running, Limit reached at the daily cap, and Paused on a paused monitor (unpause it first — a paused monitor cannot be checked on demand).

Doing the same thing programmatically is POST /api/monitors/{id}/check — see the API reference.

If you want to try a flow before it becomes a monitor, use Test flow on the wizard's Review screen instead — see Test flow.


Alerts

API flow monitors alert through the same machinery as every other monitor type — channels, escalation policies, on-call schedules, maintenance suppression. See the Alerts guide for the full picture.

Three things are specific to API flows:

You must attach a channel yourself. The creation wizard never asks. A monitor with no channels records every failure, opens incidents, and counts against uptime — but sends nothing. The amber banner on the detail page is telling you exactly this. Fix it via Bulk edit on the Monitors list (see above).

Failure threshold gates the alert, not the status. The monitor flips to Down on the first failed check, and that check counts against your uptime immediately. The alert waits until the configured number of consecutive failures. Flows created through the wizard are set to 1, i.e. page on the first failure; change it in Bulk edit (0–5). Recovery fires only if a Down alert was actually sent.

Maintenance windows suppress everything. Inside a maintenance window the flow keeps running, but the checks are excluded from uptime, no alerts or incidents are raised, and the auto-pause circuit breaker is switched off so a window-long outage can't pause your monitor.

Down and recovery also produce an in-app notification and, if the monitor is on a status page, a component status change for your subscribers.


The auto-pause circuit breaker

If the same step fails on 10 consecutive checks, Enori stops running the flow. The monitor is auto-paused, you get a MonitorAutoPaused alert, and an amber Circuit breaker tripped banner appears on the detail page with a Resume button.

This surprises people, so here is exactly when it fires and when it doesn't.

It fires when: 10 checks in a row have failed on the same step index. Both conditions matter — the counter must reach 10, and the last 10 recorded runs must all have failed on that same step.

It does not fire when: the failing step moves around between checks (step 1 fails, then step 3, then step 1 again), a run succeeds at any point — which resets the counter to zero — or the monitor is inside a maintenance window.

Why it exists. A flow that is thoroughly broken — a decommissioned endpoint, a revoked credential, a login that will never succeed again — otherwise generates a check and its side effects forever. Pausing after ten identical failures stops the noise and makes it obvious that something needs a human.

What it also tidies up. A parked flow runs no checks, so nothing would ever arrive to close what the outage opened. On the check that trips the breaker, Enori therefore resolves the monitor's open incident and closes its alert episode as part of pausing. Without that, the incident would sit at Investigating forever — inflating your active-incident count and showing on any status page the monitor is on — and the still-open episode would swallow the next real outage instead of paging you for it.

How to get it running again. Fix the underlying problem, then Resume — from the banner, from the monitor's overflow menu, or from its row on the Monitors list; all three do the same thing. That clears the pause and the failure counters, and the next scheduled check runs the flow again. If step 1 still fails, the breaker will simply trip again after another 10 checks — resuming without fixing anything buys you ten checks, not a solution.

There is no way to disable the breaker or change the threshold of 10.


Changing a monitor later

Open the monitor and click the pencil icon. The editor has four tabs.

TabWhat you can change
IdentityMonitor name, group, tags.
StepsThe full step editor, byte for byte the one in the wizard — add, remove, reorder and edit every step, including headers, authentication, the body content type, the execution controls and cURL import.
VariablesAdd, replace or delete variables. Values are write-only; you'll see names and types, never the stored values.
AlertsAlert on failure, notify on recovery, failure threshold (0–5), repeat alerts every N failed checks (1–20).

Two things you should know before you commit to them at creation time:

  • The check interval cannot be changed here, and it is not offered by Bulk edit either. Changing it needs the API (PATCH /api/monitors/{id} with intervalSeconds) — or delete the monitor and create it again.
  • The flow timeout cannot be changed here either; it is preserved as-is when you save. Same workaround.

There is no region picker and no multi-region verification toggle in this editor, because neither applies to an API flow — see Where checks run from.

Alert channels and escalation policies are not in this editor. Use Bulk edit on the Monitors list.


FAQ

Does an API flow monitor need every step to pass?

Yes. One failed step fails the whole check. The Continue flow on assertion failure checkbox only controls whether the remaining steps still run — it does not make the run pass. There is no "optional step".

How do I check an endpoint that needs a login?

Two steps: a POST to your auth endpoint that extracts the token, then the real request carrying it. The Use login template button builds exactly that. Put the credentials in Variables so they're encrypted, and reference them as {{username}} / {{password}}.

Can I monitor something on my private network?

No. Step URLs that resolve to private, internal or reserved addresses are refused — at save time for a literal IP, and at check time for anything else (reported as SSRF_BLOCKED). Enori checks from the public internet, so it can only reach what the public internet can reach.

Why is my response time so high compared to my other monitors?

Because it's the total for the whole flow. A five-step flow's response time is the sum of five requests plus any sleeps and retry backoff. That's the number to alert on with a response_time assertion if you care about per-step latency instead.

Do failed checks count against my uptime?

Yes, from the first failed check — including checks that failed before your alert threshold was reached. They feed SLOs and uptime reports like any other downtime. Use a maintenance window around planned work.

Can I see the values my flow extracted?

You see the names, not the values. Extracted values are treated as secrets for the rest of the run and masked out of everything that gets stored. That's deliberate — a session token in a stored artifact is a session token in a database.

Can I run a flow on demand?

Yes — Check Now on the monitor's detail page. It runs the flow immediately and the result counts towards uptime like a scheduled one. Your plan sets a daily allowance and a cooldown; see Running a flow on demand.

To try a flow you haven't saved yet, use Test flow on the wizard's Review screen — that one runs the draft and stores nothing.

How many steps can a flow have?

  1. The counter above the step list tracks it and the Add step button disables at the cap.

Can one flow call another?

Yes — a run_subtest step, one level deep. The flow you're calling has to be marked as reusable first, and that mark is currently only settable through the API. Full detail in Step types.

Can I manage API flows through the API?

Yes. Three things are still API-only: the check interval after creation, flow timeouts above 120 s, and marking a flow as a subtest target. Everything else the engine supports — headers, per-step timeouts, redirect following, the cookie setting, authentication, every extract source — is on the step card. See the API reference and the MCP server.


Troubleshooting

UNRESOLVED_VARIABLE on the first check, though the wizard let me save

Almost always case. Variable names are lowercase, and at check time the match is exact — {{Token}} will never find a variable named token, and the wizard's own validation is case-insensitive so it doesn't catch it. Check the token spelling in the URL, body and every header value.

The other cause is order: a step can only use values that an earlier step extracted. Check that the step doing the extracting comes first in the list.

EXTRACT_FAILED:token — but the token is definitely in the response

  1. Open the step drawer and read the failure artifacts. The captured response body shows what actually came back, which is often an error object rather than the success shape you designed the path for.
  2. Check the path. $.token and $.data.token are different. Enori's JSONPath is standard — test yours against a real response body.
  3. Check the earlier assertion. If your login step has no status equals 200 assertion, a 401 response still counts as "the step completed" and the extractor then correctly reports that $.token wasn't there. Adding the status assertion converts a confusing extract failure into an obvious auth failure.
  4. For cookies, check the case. Cookie names are matched exactly — SESSIONID and sessionid are different cookies.

ASSERT_FAILED and I can't see why

Open the step drawer. The Assertions table shows expected and actual side by side for every assertion, with the failing one in red. Three common surprises:

  • contains on json_path and response_text is case-sensitive (on header it isn't).
  • An operator that isn't supported for that assertion type always fails — look for UNSUPPORTED_ASSERTION in the actual value, which names the operator you should have used. See the support table.
  • A numeric header comparison against a non-numeric header shows … (not numeric) rather than a comparison result.

FLOW_TIMEOUT even though each step is fast

The flow timeout covers everything: every step, every sleep, and every retry backoff. A flow with four steps each allowed 10 s, plus 3 retries at 500 ms linear backoff, can exceed a 60 s budget on a bad day even though the happy path takes 2 s. Either raise the flow timeout on the Review screen (max 120 s in the UI) or reduce retries.

Note that the flow timeout is separate from each step's timeout. A step defaults to 10 seconds and can be set anywhere from 1 to 60 seconds under Execution on its card — but raising a step's timeout doesn't raise the flow's, and the flow's is what stops the run.

RESPONSE_TOO_LARGE

The endpoint returned more than 2 MB. Extractions and assertions are deliberately not run on a truncated body, because a response_size or json_path check against half a document would produce a confidently wrong verdict. Point the step at a paginated or filtered variant of the endpoint — a monitor doesn't need the whole dataset to know the API is healthy.

HTTP_ERROR on a URL that works in my browser

Your browser has cookies, a session, and your IP. Enori has none of those and comes from a different network. Check that the endpoint is reachable from the public internet, that every header your API needs is actually on the step, and that a firewall or bot-protection rule isn't refusing an unfamiliar client. The request Enori actually sent is in the failure artifacts — compare it against the one that works, or paste your working curl command into Import from cURL and let it build the step for you.

The monitor got auto-paused and I don't know why

The banner names it: the same step failed 10 checks in a row. Open the last run in the timeline, click the failing step, and read the error. Fix that, then click Resume. See the circuit breaker.

It says Down but I never got an alert

Check the amber banner at the top of the detail page. No alert channel attached is by far the most common cause on this monitor type, because the creation wizard never asks for one. Attach one via Bulk edit on the Monitors list.

Otherwise: the failure threshold may not have been reached yet, the monitor may be inside a maintenance window, or it may be snoozed.

The Headers editor won't let me save a header

Three rules block a save, and each names the offending row:

  • Duplicate header name. Two rows with the same name, compared without case. Merge them.
  • Header name is required. A row with a value but no name.
  • Header '…' is not allowed. Connection, Content-Length, Host, Keep-Alive, Proxy-Authorization, Proxy-Connection, TE, Trailer, Transfer-Encoding and Upgrade are set by the connection itself and can't be overridden. There's no workaround, including through the API — the same rule runs there.

A fourth is only visible at check time: a header value that resolves to something containing a newline or another control character fails the check with INVALID_HEADER_VALUE. That's almost always a trailing newline pasted into a variable's value.

A step must send both an Authorization header and Basic/Bearer auth

It can't — that combination is refused, at save and again at check time as AUTH_HEADER_CONFLICT. Two sources for one header is a configuration error rather than a precedence puzzle, so Enori won't pick a winner for you. Delete the header row, or set the authentication selector back to None. Importing a cURL command that carries its own Authorization switches the block off for you and says so in the import notes.


Reference: limits and defaults

SettingValue
Steps per flow1–20, counted after any run_subtest expansion
Step typeshttp_request, sleep, run_subtest (depth 1; target must be marked reusable via the API)
HTTP methodsGET, POST, PUT, PATCH, DELETE, HEAD, OPTIONS
Step URLRequired, absolute http/https, up to 2048 chars. Private/reserved addresses refused
Step nameOptional, up to 80 chars
Request headersName/value rows on the step card. Names unique per step (case-insensitive); Connection, Content-Length, Host, Keep-Alive, Proxy-Authorization, Proxy-Connection, TE, Trailer, Transfer-Encoding, Upgrade refused; no control characters
Body content typesJSON · form-urlencoded · XML · plain text · GraphQL (sent as application/json) · custom. No Content-Type header ⇒ sent as application/json
Step authenticationNone · Basic · Bearer. The credential is referenced by the name of an encrypted variable, never stored as a value. Not combinable with an Authorization header
Per-step timeout1–60 s, default 10 s. Editable on the step card
Follow redirectsPer step, on by default for steps added in the editor. Maximum 5 hops; your headers are dropped on a cross-origin hop
Ignore flow cookiesPer step, off by default
Retries per step0–3, default 0. Transport failures, timeouts, and a 429 carrying Retry-After — never assertion failures
Retry backoff0–30000 ms, default 500 ms, linear. A 429's Retry-After wins, capped at 30 s
Response body cap2 MB — beyond it the step fails as RESPONSE_TOO_LARGE
Sleep step0–30000 ms
Flow timeout5–120 s in the UI (default 60 s); up to 240 s via the API. Test flow caps at 60 s
Assertions per stepUnlimited. Six types × nine operators, 41 of the 54 pairs supported — see the support table
Extracts per stepUnlimited. Sources: json_path, header, cookie, status_code, response_text, regex
Regex (extract and assertion)Case-insensitive. 100 ms per evaluation; 500 ms across one assertion's [*] fan-out, and 500 ms across all of a step's regex extracts. A timeout is a reported failure, never a silent no-match
Variable / extract names^[a-z][a-z0-9_]{0,49}$, unique across the whole monitor
Variables per monitor20. Types text and totp. Encrypted at rest, never readable back
Check interval1m, 5m, 15m, 30m, 1h — default 5 minutes. Not changeable from the UI after creation
While DownRe-checked every 30 seconds until it recovers
Manual checks (Check Now)Per day, per account: Base 20 · Pro 100 · Business 500, resetting at midnight UTC. Cooldown per monitor: 120 s · 60 s · 30 s. 5 per minute overall
Test flow (dry run)Same daily allowance and per-minute cap as Check Now. Writes nothing, affects no uptime. Max 20 steps, no run_subtest
Failures before alert0–5. Wizard-created flows use 1 (alert on first failure)
Auto-pauseAfter 10 consecutive failures on the same step. Manual Resume only
Failure artifactsRequest + response headers and bodies, 16 KB per body, erased after 7 days. Credential-carrying headers masked by name
Multi-region verificationNot available for this type
Monitors per planBase 10 · Pro 50 · Business 200
Check-history retentionBase 30 days · Pro 60 days · Business 90 days
Plan availabilityIncluded on every plan, no add-on


Feedback or corrections: support@enori.io