Skip to main content

Evaluation API

Two endpoints, both accepting an integration key (dakkio_i_…).

The integration key

Organization-scoped and deliberately non-destructive. It reaches every bucket in the organization, because an evaluator has to, but it cannot damage anything.

PermissionPurpose
buckets:readList buckets and data sources
data:readQuery series
notifications:readFetch due rules and their parameters
evaluations:writeSubmit verdicts

Notably absent: data:write, buckets:write, buckets:delete, notifications:write, apikeys:manage.

The worst a faulty evaluator can do is submit a wrong verdict. It cannot create rules, delete them, alter thresholds, or write data — so a bad expression in an automation workflow cannot destroy your notification configuration.

Create one in the dashboard under API Keys → integration.

Fetch due evaluations

GET /api/evaluations/due
X-API-Key: dakkio_i_...
Query paramDefaultNotes
limit100Max 500
bucketIdOptional filter

Returns rules whose last evaluation is older than their interval. Organization-wide: one call regardless of bucket count.

{
"count": 1,
"evaluations": [
{
"ruleId": "6a6c835f1ada3a6c5f032520",
"name": "Weight below minimum - Scale 3",
"kind": "threshold",
"state": "ARMED",
"bucket": { "id": "69e75875049cfa22d51464ae", "name": "Acme Monitoring" },
"dataSource": { "id": "6a04b41c049cfa22d51464d1", "name": "Scale-03" },
"parameters": { "operator": "<", "threshold": 20 },
"window": {
"seconds": 900,
"startTime": "2026-07-31T10:58:40.087Z",
"endTime": "2026-07-31T11:13:40.087Z"
},
"inputs": [
{
"key": "v",
"field": "weight",
"dataSource": { "id": "6a04b41c049cfa22d51464d1", "name": "Scale-03" },
"query": {
"bucketId": "69e75875049cfa22d51464ae",
"filters": {
"dataSourceIds": ["6a04b41c049cfa22d51464d1"],
"fields": ["weight"],
"startTime": "2026-07-31T10:58:40.087Z",
"endTime": "2026-07-31T11:13:40.087Z"
}
}
}
]
}
]
}
inputs[].query is the request body

That object is literally what you POST to /api/data/query. Time bounds are absolute and resolved server-side, so your evaluator performs no clock arithmetic and cannot drift.

An empty array is success

Most polls return { "count": 0, "evaluations": [] }. That is the steady state, not an error.

Rules in dwell are still returned. The evaluator must keep reporting so the dwell timer stays accurate. Paused rules are not returned.

Submit a verdict

POST /api/evaluations/{ruleId}/verdict
X-API-Key: dakkio_i_...
{
"outcome": "met",
"evidence": { "latest": 18.6, "threshold": 20, "operator": "<", "samples": 12 },
"windowStart": "2026-07-31T10:58:40.087Z",
"windowEnd": "2026-07-31T11:13:40.087Z"
}

The three outcomes

OutcomeMeaning
metThe condition holds
not_metThe condition does not hold
inconclusiveThe window carried no usable information
inconclusive is not a failure — and not a substitute for not_met

It is the gate case. If the band rule's external temperature barely moved, the window says nothing about whether the pattern is present.

Reporting not_met there would start the clear-dwell timer and eventually re-arm the rule on no evidence at all. inconclusive marks the rule evaluated — so it is not immediately due again — while holding its state exactly as it is.

Use it whenever your computation cannot reach a conclusion: a gate not satisfied, too few samples, a series that came back empty.

Evidence

The computed values behind the verdict, passed through verbatim and persisted on the alert event. This is what lets a notification say why a rule fired rather than merely that it did.

Response

{
"ruleId": "6a6c835f1ada3a6c5f032520",
"previousState": "ARMED",
"state": "FIRED",
"reason": "fired",
"dispatched": true,
"alertEventId": "6a6c835f1ada3a6c5f032599",
"evaluatedAt": "2026-07-31T11:45:26.890Z"
}

dispatched is true only on the ARMED → FIRED edge.

StatusMeaning
200Verdict applied
400Rule is not externally evaluated
404Rule not found in your organization
409Rule is paused, or state changed concurrently — the verdict was stale and discarded
A 409 is benign

It means another verdict for the same rule landed first. Your workflow should tolerate it rather than treating it as an outage — it is the guard that stops a rule firing twice for one edge.

The state machine

Edge-triggered. A webhook fires on ARMED → FIRED only. A rule true for six months dispatches once.

Asymmetric hysteresis. Instant to fire, slow to clear. The condition must be absent continuously for dwellSeconds before the rule re-arms; any met verdict in between resets the timer.

This is strictly better than a cooldown. A cooldown re-arms after N minutes whether or not the condition cleared, so a persistently-true condition re-fires forever. Clear-dwell re-arms only after the condition has genuinely been absent, which makes flapping at a threshold boundary impossible.

The seven reasons

reasonFrom → ToState changed?Webhook?
firedARMED → FIRED
rearmedFIRED → ARMED
still_firingFIRED → FIRED
dwell_startedFIRED → FIRED
dwell_in_progressFIRED → FIRED
still_armedARMED → ARMED
held_inconclusiveunchanged
Why "not_met" can return "state: FIRED"

Seeing outcome: not_met alongside state: FIRED and reason: dwell_in_progress is correct. The condition has gone away, the dwell timer is running, and the rule stays FIRED until it fully elapses.

Cold start. A new rule begins ARMED and fires on its first met verdict, so you learn straight away which data sources match. Enabling a rule across many sources produces a burst on the first run — group it in your notification layer.

No "recovered" notification in v1. FIRED → ARMED is silent.

Transition history

Every state change is recorded in alertStateTransitions — and only state changes. Of the seven reasons above, exactly two move the rule, so a rule evaluated every five minutes for a year produces a handful of rows, not a hundred thousand.

{
alertRuleId, organizationId, bucketId, dataSourceId,
ruleName: "Weight below minimum - Scale 3",
kind: "threshold",
from: "ARMED", to: "FIRED",
reason: "fired",
outcome: "met",
evidence: { latest: 18.6, threshold: 20 },
window: { start, end },
notificationRequired: true,
alertEventId,
occurredAt
}

notificationRequired records what was owed, not what happened — whether the webhook actually went out lives on the alert event's delivery record, so a re-delivery never rewrites history.

Idempotency

Verdicts are advisory. dakkio re-checks state on receipt and discards duplicates or stale submissions, so an evaluator can be restarted, redeployed or accidentally run twice without consequence. It holds no state of its own.