Skip to content

Automation API

All automation endpoints are project-scoped. The concept docs live at Automation rules; this page is the REST surface the dashboard's Automation area uses.

Mutating endpoints require a CSRF token (see Authentication). Role enforcement is destructiveness-aware: a rule whose actions are only notify needs project operator, while a rule carrying a destructive action (retry, cancel) needs project admin plus a fresh second-factor check (the same step-up gate as the other sensitive actions; expect 403 mfa_reverify_required when the sudo window has lapsed). Actions the grammar knows but the runner does not execute (webhook, revoke, purge, pause_schedule) are rejected fail-closed with 422 at create and update time.

GET /api/v1/projects/{slug}/automation/rules

Role: viewer. Returns {"items": [Rule, ...]}.

A rule object:

{
"id": "5f0b7f2e-...",
"project_id": "6b2d25e3-...",
"name": "Retry failed invoice runs",
"is_enabled": true,
"dry_run": false,
"trigger": "task.failed",
"conditions": {"task_name": "billing.run_daily_invoices"},
"actions": [{"type": "retry", "max_retries": 2}],
"max_executions_per_window": 20,
"window_seconds": 300,
"cb_tripped": false,
"cb_execution_count": 3,
"created_by": "26573b1a-...",
"created_at": "2026-07-16T12:00:00Z",
"updated_at": "2026-07-16T12:00:00Z"
}

cb_tripped / cb_execution_count expose the rolling-window circuit breaker: when a rule exceeds max_executions_per_window inside window_seconds, the breaker trips and the rule degrades to notify-only. It clears itself: each admission attempt ages out executions older than the window, and once fewer than the limit remain the next admission both executes and resets the flag.

GET /api/v1/projects/{slug}/automation/rules/{rule_id}

Role: viewer.

POST /api/v1/projects/{slug}/automation/rules

Role: operator for notify-only rules and admin when any action is destructive. A browser-session request for a destructive rule also requires a fresh second factor; bearer-authenticated callers follow their API-key authority. Returns 201 with the created rule.

Body fields mirror the rule object: name, trigger (task.failed, task.succeeded, task.retried, task.orphaned, worker.offline, schedule.misfired), optional conditions (engine, queue, task name, exception, priority, runtime, or an issue fingerprint), actions, dry_run, is_enabled, and the breaker window (max_executions_per_window, window_seconds).

dry_run defaults to true, so a new rule audits what it would do and executes nothing until an operator flips it live. max_executions_per_window defaults to 100 (1 to 100000) and window_seconds to 3600 (1 to 604800). A name already used by another rule in the project returns 409.

conditions is either one flat object, whose keys are AND-ed, or a single one-level {"all": [...]} / {"any": [...]} group of at most 20 flat members. task_name_pattern and exception_pattern are fnmatch globs capped at 200 chars, five */? wildcards, and three character classes. actions holds 1 to 10 entries, and the serialised conditions plus actions must fit in 64 KiB; either limit fails with 422.

PATCH /api/v1/projects/{slug}/automation/rules/{rule_id}

Same role rules as create, evaluated against both the old and post-update rule shape. An edit that touches or introduces a destructive action requires admin; browser-session calls also require a fresh second factor.

DELETE /api/v1/projects/{slug}/automation/rules/{rule_id}

Role: operator for notify-only rules, admin for rules with destructive actions. Returns 204.

POST /api/v1/projects/{slug}/automation/rules/{rule_id}/reset-circuit

Clears the breaker so the rule can fire again, and writes an automation.rule.circuit_reset audit row. Re-arming a destructive rule requires admin; browser-session calls also require a fresh second factor. Returns the refreshed rule.

GET /api/v1/projects/{slug}/automation/settings
PUT /api/v1/projects/{slug}/automation/settings

GET needs viewer. PUT needs admin and a CSRF token; turning the switch on from a browser session also requires a fresh second factor, because it re-arms every destructive rule in the project at once (turning it off does not). Each change writes an automation.kill_switch.updated audit row.

The settings object carries the per-project kill switch:

{"automation_enabled": true}

When false, the rule engine loads no rules for the project, so nothing fires regardless of each rule's own is_enabled state. Every firing (and every breaker trip, skip, and auto-disable) lands on the HMAC-chained audit log.