API
All routes are under https://dash.openwaf.dev/v1/orgs/{org} and take an OpenWAF API token (owf_…) or the dashboard session. Reads need readonly; writes need an admin of the whole organization, take X-Change-Reason (recorded in the audit log) and accept ?dry_run=true to validate without saving.
| Route | What it does |
|---|---|
GET /tasks | Overview: enabled, requirements, quota, usage, kill and every task. Readable before access is approved. |
POST /tasks | Create a task; 201 {"task", "webhook_secret"} (the secret only when a webhook is set, once) |
GET /tasks/{task} | Read a task ({task} is its id or name) |
PATCH /tasks/{task} | Change any writable field; "rotate_webhook_secret": true for a new secret |
DELETE /tasks/{task} | Delete it (204) |
POST /tasks/{task}/run | Run it now; 202 with the run |
GET /tasks/{task}/runs?limit=&before= | Runs, newest first: {"items", "next"} |
GET /tasks/browser-profiles?key_id= | Profiles of a browser key: {"items": [{"id", "name"}]} |
GET /task-runs/{run} | One run with its approvals and artifacts |
GET /task-runs/{run}/logs?after=<seq> | Log lines after seq: {"items": [{"seq", "at", "level", "type", "message"}]} |
GET /task-runs/{run}/artifacts/{id} | A screenshot (image/jpeg or image/png) |
GET /task-runs/{run}/recording | 302 to the recording file, when the run recorded one |
POST /task-runs/{run}/cancel | Cancel a run; answers the run |
GET /task-approvals?status=&task= | Approvals: {"items"}; status is pending, approved, rejected, expired, executed or failed |
GET /task-approvals/{id} | One approval |
POST /task-approvals/{id}/approve | {"payload_sha256", "reason"}; answers the approval |
POST /task-approvals/{id}/reject | {"reason"}; answers the approval |
Overview
{"enabled": true,
"requirements": {"browsers": true, "egress": true, "email_verified": true, "two_factor": true},
"quota": {"runs_per_month": 200, "max_tasks": 5, "max_concurrent": 1},
"usage": {"period_start": "2026-10-01T00:00:00Z", "period_end": "2026-11-01T00:00:00Z",
"runs": 14, "active": 0, "pending_approvals": 1},
"kill": false,
"tasks": [ … ]}kill is true while the service is paused for every organization: no runs start.
Task
{"id": "…", "name": "guestbook", "description": "", "kind": "script",
"schedule": {"cron": "0 9 * * *", "timezone": "Europe/Madrid"},
"paused": false,
"browser": {"key_id": "…", "key_name": "ci-agent", "profile": "", "recording": false},
"allow": ["example.com"],
"script": "export default async function ({ page, task }) { … }",
"input": {}, "state": {},
"max_run_minutes": 10,
"approval": {"policy": "required", "expire_minutes": 120, "relaxed_domains": []},
"action_limits": [{"domain": "example.com", "min_interval_minutes": 30, "max_per_day": 4}],
"webhook": {"url": "https://hooks.example.com/openwaf", "secret_set": true},
"pool": "browsers", "egress_agent": null,
"next_run_at": "2026-10-30T08:00:00Z",
"last_run": {"id": "…", "status": "succeeded", "finished_at": "2026-10-29T08:00:41Z"},
"created_at": "…", "updated_at": "…"}| Field | Rules |
|---|---|
name | Lowercase letters, digits and hyphens, up to 63, unique in the organization |
kind | script or agent; cannot change |
schedule | {"cron", "timezone"} (5 fields, IANA zone, at most every 15 minutes) or {"at"} for one run |
browser | key_id of a browser key of the organization, profile name (empty for a clean browser), recording |
allow | 1 to 20 domains: example.com or *.example.com |
script | script only: ES module up to 256 KiB. See the script quickstart. |
agent | agent only: {"goal", "egress_target", "model", "max_steps"}. See the agent quickstart. |
input | JSON up to 64 KiB, given to the script or the agent |
max_run_minutes | 1 to 30 |
approval | expire_minutes 10 to 1440. policy and relaxed_domains are read-only; see policies. |
action_limits | Stricter per-domain limits |
webhook | {"url"} to set, null to remove. See webhook. |
state, pool, egress_agent, next_run_at and last_run are read-only.
Run
{"id": "…", "task_id": "…", "task_name": "guestbook", "number": 12, "trigger": "schedule",
"status": "awaiting_approval",
"created_at": "…", "started_at": "…", "finished_at": null, "error": "",
"output": {"posted": "pending approval"},
"steps": 0, "browser_seconds": 38, "model_calls": 0, "prompt_tokens": 0, "completion_tokens": 0,
"blocked_requests": 0, "live_url": null, "recording_available": false, "egress_agent": null,
"approvals": [ … ],
"artifacts": [{"id": "…", "name": "before", "content_type": "image/jpeg", "size": 81234, "created_at": "…"}]}status is one of queued, running, awaiting_approval, executing, succeeded, failed, rejected, expired, timed_out and canceled. trigger is schedule or manual. live_url is only set while the run has a browser.
Approval
{"id": "…", "task_id": "…", "task_name": "guestbook", "run_id": "…", "status": "pending",
"summary": "Sign the guestbook with \"Hello from Example Inc.\"",
"target": "https://example.com/guestbook", "domain": "example.com",
"payload": {"steps": [{"op": "goto", "url": "https://example.com/guestbook"}, … ]},
"payload_sha256": "9c1e…", "screenshot_id": "…",
"requested_at": "…", "expires_at": "…", "decided_at": null, "decided_by": null, "reason": null,
"not_before": null, "executed_at": null, "result": null}After the act phase, result is {"ok", "final_url", "error", "screenshot_id"}. Steps are described in approvals.
Errors
| Status | code | When |
|---|---|---|
| 403 | feature_disabled | OpenWAF Tasks is not enabled for the organization |
| 403 | forbidden | The role is too low, or the member or token is limited to a client or a zone |
| 404 | not_found | No such task, run, artifact or approval |
| 409 | payload_changed | The payload_sha256 you sent is not the approval's |
| 409 | not_pending | The approval was already decided or expired |
| 422 | invalid | A field breaks the rules above; field names it |
CLI
openwaf-cloud ctl tasks … calls the API above. See get started with the CLI.
The organization comes from --org or OPENWAF_ORG. Changes show what they will do and ask before applying (--yes skips the question, --dry-run only validates).
| Command | What it does |
|---|---|
tasks list | Overview and tasks |
tasks get <task> | One task |
tasks create <task.json|-> --reason "…" | Create a task from a JSON body |
tasks update <task> <patch.json|-> --reason "…" | Change a task |
tasks delete <task> --reason "…" | Delete a task |
tasks run <task> --reason "…" | Run it now |
tasks runs <task> [--limit n] | Runs of a task |
tasks run-get <run> | One run with its approvals and artifacts |
tasks cancel <run> --reason "…" | Cancel a run |
tasks approvals [--status pending] [--task <task>] | Approvals |
tasks approve <approval> --sha256 <hash> --reason "…" | Approve the exact payload |
tasks reject <approval> --reason "…" | Reject it |
MCP tools
The OpenWAF MCP server has 12 tools for OpenWAF Tasks. Writes need a reason and take two steps: confirm=false only checks, confirm=true applies.
| Tool | Role |
|---|---|
tasks_list | readonly |
tasks_get | readonly |
task_runs_list | readonly |
task_runs_get | readonly |
task_approvals_list | readonly |
tasks_create | admin |
tasks_update | admin |
tasks_delete | admin |
tasks_run | admin |
task_runs_cancel | admin |
task_approve | admin. The check step shows the exact payload, its payload_sha256 and the screenshot id; the apply step needs that payload_sha256. |
task_reject | admin |
An assistant can prepare an approval for you, but read the steps yourself before you confirm it.