# Quickstart: script

> Create a scheduled script task that reads a page with Playwright, saves a screenshot and proposes a form submission for approval.

This guide creates a `script` task that opens a page every day, records what it sees and, when there is something to send, proposes a form submission that you approve from the dashboard.

## Before you start

- **Access** to OpenWAF Tasks. See [get access](https://developers.openwaf.dev/tasks/#get-access).
- **A browser key** of [OpenWAF Browsers](https://developers.openwaf.dev/browsers/quickstart/#create-a-key). The task's runs use it and count against its quota.
- **An organization-wide `admin`** to create tasks and approve actions. Reading runs needs `readonly`.

## 1. Write the script

A script is an ES module that exports one async function. It gets a Playwright `page` (and `context` and `browser`) connected to the task's browser, and `task`:

| `task.…` | What it is |
|---|---|
| `input` | The task's `input` JSON, set by you |
| `state` | JSON the script saved in the last run (`{}` the first time) |
| `setState(value)` | Save JSON for the next run (up to 64 KiB) |
| `log(message)` | A line in the run's logs |
| `screenshot(name)` | Save a screenshot of the page as an artifact of the run |
| `output(value)` | The run's output JSON (up to 64 KiB) |
| `approve({summary, target, steps})` | Propose an action for a person to approve; see [approvals](https://developers.openwaf.dev/tasks/approvals/) |

```js
export default async function ({ page, task }) {
  const { url, message } = task.input;
  await page.goto(url);
  await page.getByRole("heading", { name: "Guestbook" }).waitFor();
  await task.screenshot("before");

  const already = await page.getByText(message).count();
  task.log(`message on the page: ${already > 0}`);
  if (already > 0) {
    task.output({ posted: false, reason: "already there" });
    return;
  }

  // The browser is read-only here: clicking "Sign" would be blocked. Propose it instead.
  await task.approve({
    summary: `Sign the guestbook with "${message}"`,
    target: url,
    steps: [
      { op: "goto", url },
      { op: "fill", selector: "#name", text: "Example Inc." },
      { op: "fill", selector: "#message", text: message },
      { op: "click", selector: "#sign" },
      { op: "wait_for", text: message },
    ],
  });
  task.setState({ proposed_at: new Date().toISOString() });
  task.output({ posted: "pending approval" });
}
```

`task.approve` returns the approval id (a string) once the proposal is recorded; the action runs later, after approval, in a new browser. The script never sees whether it was approved: keep the id with `task.setState` so the next run does not propose the same action again, and read the approval or the page in the next run.

## 2. Create the task

**Dashboard**

1. Open **Tasks** in the organization menu and choose **New task**, kind **Script**.
2. Name it `guestbook`, pick the browser key, paste the script and set the input.
3. Allowed domains: `example.com`. Schedule: `0 9 * * *`, time zone `Europe/Madrid`.
4. Save.

**API**

```sh
curl -sS -X POST "https://dash.openwaf.dev/v1/orgs/$ORG/tasks" \
  -H "Authorization: Bearer $OPENWAF_TOKEN" -H "Content-Type: application/json" \
  -H "X-Change-Reason: Daily guestbook entry" \
  --data-binary @- <<EOF
{"name": "guestbook", "kind": "script",
 "schedule": {"cron": "0 9 * * *", "timezone": "Europe/Madrid"},
 "browser": {"key_id": "$KEY_ID", "profile": "", "recording": false},
 "allow": ["example.com"],
 "script": $(jq -Rs . < guestbook.mjs),
 "input": {"url": "https://example.com/guestbook", "message": "Hello from Example Inc."}}
EOF
```

The `201` answer carries the task with its `id` and `next_run_at`. Add `?dry_run=true` to only validate it.
**CLI**

Put the same JSON body in `task.json`, then:

```sh
openwaf-cloud ctl tasks create task.json --org acme --reason "Daily guestbook entry"
```


## 3. Run it now

Choose **Run now** on the task, or call `POST /v1/orgs/{org}/tasks/{task}/run`, or `openwaf-cloud ctl tasks run guestbook --org acme --reason "First run"`. The answer is the run (`202`). Follow its logs with `GET /v1/orgs/{org}/task-runs/{run}/logs?after=<seq>`. While it runs, `live_url` lets you watch the browser.

When the script proposes the action, the run goes to `awaiting_approval` and the proposal appears in **Tasks → Approvals**.

## 4. Approve it

Open the approval. Check the summary, the target, every step and the screenshot, then choose **Approve**. From the API you send back the hash you saw:

```sh
curl -sS -X POST "https://dash.openwaf.dev/v1/orgs/$ORG/task-approvals/$APPROVAL/approve" \
  -H "Authorization: Bearer $OPENWAF_TOKEN" -H "Content-Type: application/json" \
  -d '{"payload_sha256": "9c1e…", "reason": "Text checked"}'
```

The runner replays the steps in a new browser, takes a final screenshot and stores the result in the approval. The run ends `succeeded` or `failed`.

## Next steps

- [Approvals](https://developers.openwaf.dev/tasks/approvals/): every step type, the hash and the webhook.
- [Limits](https://developers.openwaf.dev/tasks/limits/): per-domain limits that may delay an approved action.
