Evaluate Alert
POST/v1/alerts/:alert_id/evaluate
Evaluate this alert's condition NOW, outside its schedule, and say what it concluded.
This is a REAL evaluation, not a dry run: it runs the condition's query on
the same worker the scheduler uses, records the verdict, and — when the
verdict TRANSITIONS into the breach — queues the capture, mails every
recipient, and moves lastFiredAt. Calling it twice in a row on a
still-breaching alert answers still_firing the second time and sends
nothing; that is the hysteresis rule, not a failure.
lastEvaluatedAt DOES NOT MOVE. It reports SCHEDULED runs only —
the same rule POST /v1/subscriptions/{id}/test has always stated for
lastRun / lastStatus: writing it here would claim the schedule ran
when it did not, and a customer reading the row back a week later cannot
tell a cron firing from somebody's poke. It also feeds is_due and
registration.nextRun, so a poke that stamped it moved the schedule.
lastFiredAt is different and DOES move on a fire here, because it
answers "when did this alert last tell somebody" — and this route really
did tell them. Which runs were scheduled and which were hand-asked is
answerable from GET /v1/alerts/{alert_id}/deliveries, where every send
carries its own trigger.
Read outcome:
-
fired— the condition holds and did not hold before.deliveryIdis the capture row queued for it. -
still_firing— it holds and already held. Audited, deliberately quiet. -
not_fired— it does not hold; the alert is re-armed. Readverdict.observationwith it, becausenot_firedis two different facts:measuredmeans a number came back and was compared (proof a row was examined, and the number is on the verdict), whilenothingmeans the query returned no rows and nothing else — which is ALSO what an alert whose scan set is empty returns (a mistyped filter value, a date nobody has data for yet, a feed that stopped). The truthy shape cannot tell those apart; the sugar shape can, and reports the empty scan aserror.notesays all of this in words, derived from the verdict. -
error— it could NOT be measured (bad SQL, a table the dashboard does not bind, the question was deleted, the owner lost dataset access, or — for a SUGAR condition only — a result that is not one numeric cell).verdictis null,errornames the failure, and the alert'sstatusis nowerrorso it is visibly broken rather than quietly silent. A failed measurement is never reported asfired: false. -
warming_up— 202, not 200, and not an error. No SQL worker answered inside this request's budget, so the condition never ran. The alert is untouched: no verdict, nolastEvaluatedAt, noerrorstatus. A warm-up was kicked, so a retry in ~a minute (or the next scheduled tick) measures it. Before this outcome existed the request simply waited past thecorelambda's 30 s timeout and the caller got an empty 503 — measured on prod 2026-09-09 — which said nothing and left the row equally untouched without saying so.
400 when the alert has no condition — there is nothing to evaluate; use
POST /v1/alerts/{alert_id}/test to exercise its capture instead.
Request
Responses
- 200
- 202
- 404
- 422
Successful Response
The SQL engine was cold and the condition was not measured (outcome: "warming_up"). Nothing is broken and nothing was written to the alert; retry, or let the schedule take it.
Not found
Validation Error