Skip to content
BuzzTake
MenuClose
DocumentationBrowser steps

How to automate a task that has no action, with browser steps

Last updated

Browser steps run your own sequence in an account's signed-in browser: open a page, look at it, click, type, read rows and take screenshots. Use them for a job no named action covers. The example reads the X bookmarks list, which no action reads.

You need a BuzzTake API key and the id of an active account.

1. Look at the page first

POST/v1/accounts/{id}/actions

Send type: "browser" with steps. Start with a run that only opens the page, reads it with perceive and takes a screenshot. A run costs 2 credits plus 1 per step, charged when it starts, so these four steps cost 6.

curl

curl
curl https://api.buzztake.com/v1/accounts/$ACCOUNT_ID/actions \
  -H "Authorization: Bearer $BUZZTAKE_API_KEY" \
  -H "Content-Type: application/json" \
  -d '{
    "type": "browser",
    "wait": 90,
    "steps": [
      { "op": "goto", "url": "https://x.com/i/bookmarks" },
      { "op": "wait", "ms": 2000 },
      { "op": "perceive" },
      { "op": "screenshot" }
    ]
  }'

JavaScript

JavaScript
const res = await fetch(
  `https://api.buzztake.com/v1/accounts/${process.env.ACCOUNT_ID}/actions`,
  {
    method: "POST",
    headers: {
      Authorization: `Bearer ${process.env.BUZZTAKE_API_KEY}`,
      "Content-Type": "application/json",
    },
    body: JSON.stringify({
      type: "browser",
      wait: 90,
      steps: [
        { op: "goto", url: "https://x.com/i/bookmarks" },
        { op: "wait", ms: 2000 },
        { op: "perceive" },
        { op: "screenshot" },
      ],
    }),
  },
);
if (!res.ok) throw new Error(`${res.status}: ${await res.text()}`);
const action = await res.json();
for (const step of action.result?.steps ?? []) {
  if (!step.ok) {
    console.log("failed:", step.op, step.error);
  } else if (step.op === "perceive") {
    for (const node of step.perception.nodes.slice(0, 40)) {
      console.log(node.id, node.role, node.name ?? "");
    }
  } else if (step.op === "screenshot") {
    console.log("screenshot:", step.image_url);
  }
}

Python

Python
import os
import requests

resp = requests.post(
    f"https://api.buzztake.com/v1/accounts/{os.environ['ACCOUNT_ID']}/actions",
    headers={"Authorization": f"Bearer {os.environ['BUZZTAKE_API_KEY']}"},
    json={
        "type": "browser",
        "wait": 90,
        "steps": [
            {"op": "goto", "url": "https://x.com/i/bookmarks"},
            {"op": "wait", "ms": 2000},
            {"op": "perceive"},
            {"op": "screenshot"},
        ],
    },
    timeout=100,
)
if not resp.ok:
    raise SystemExit(f"{resp.status_code}: {resp.text}")
for step in (resp.json().get("result") or {}).get("steps", []):
    if not step["ok"]:
        print("failed:", step["op"], step["error"])
    elif step["op"] == "perceive":
        for node in step["perception"]["nodes"][:40]:
            print(node["id"], node["role"], node.get("name") or "")
    elif step["op"] == "screenshot":
        print("screenshot:", step["image_url"])
The perceive step (trimmed)
{
  "op": "perceive",
  "ok": true,
  "perception": {
    "url": "https://x.com/i/bookmarks",
    "title": "...",
    "nodes": [
      { "id": "f1:42", "role": "article", "name": "...", "in_viewport": true },
      { "id": "f1:57", "role": "button", "name": "...", "disabled": false }
    ],
    "text": "...",
    "truncated": false
  }
}

nodes lists what is on the page, with a role, a name and an id such as f1:42. The screenshot's image_url shows what the browser saw.

2. Write locators from what you saw

A locator finds an element by its role, name, text or attributes. Build it from the role and name in the perception. The keys are camelCase:

role
The browser's role name: article, button, link, textField, image, checkBox.
name
The accessible name, exactly.
nameContains
Part of the accessible name.
text
The visible text, exactly.
textContains
Part of the visible text.
attributes
[{ "name": "data-testid", "value": "..." }], exact values.
attributePrefixes
The same shape, matched on the start of the value.
Warning: The REST API ignores a locator key it does not know. With { "role": "button", "name_contains": "Save" }, the misspelled key is dropped and the locator finds the first button on the page. Check the spelling.

A node id comes from a perceive step and is for a click later in the same run. Across runs, use a locator: it does not depend on an earlier perception.

3. Read rows with extract

extract finds every element that matches container and reads fields inside each one. A field has a key, a locator and a source: text, link (the href or src) or attribute with a name. An empty locator reads the container itself. Scroll and extract again to reach more of a long list:

Body: read bookmarks, scroll, read again
{
  "type": "browser",
  "wait": 120,
  "steps": [
    { "op": "goto", "url": "https://x.com/i/bookmarks" },
    { "op": "wait", "ms": 2000 },
    { "op": "extract", "container": { "role": "article" }, "limit": 50,
      "fields": [ { "key": "text", "locator": {}, "source": { "kind": "text" } } ] },
    { "op": "scroll", "dy": 3000 },
    { "op": "wait", "ms": 1500 },
    { "op": "extract", "container": { "role": "article" }, "limit": 50,
      "fields": [ { "key": "text", "locator": {}, "source": { "kind": "text" } } ] }
  ]
}
result (trimmed)
{
  "type": "browser",
  "steps": [
    { "op": "goto", "ok": true, "url": "https://x.com/i/bookmarks" },
    { "op": "wait", "ok": true },
    { "op": "extract", "ok": true, "rows": [ { "text": "..." }, { "text": "..." } ] },
    { "op": "scroll", "ok": true },
    { "op": "wait", "ok": true },
    { "op": "extract", "ok": true, "rows": [ { "text": "..." } ] }
  ]
}

Each extract returns its own rows, so the same post can appear in both. Remove repeats in your code. A field that matched nothing is left out of its row. These six steps cost 8 credits.

4. Click and type

The same locators drive click and type. This sketch opens a form, types into a field and saves. The page and the names are placeholders: use the URL you looked at and the names your perception shows.

Body: a sketch of a form change
{
  "type": "browser",
  "wait": 90,
  "steps": [
    { "op": "goto", "url": "https://example.com/the-page-you-looked-at" },
    { "op": "click", "locator": { "role": "button", "name": "Edit" } },
    { "op": "type", "text": "New value", "locator": { "role": "textField", "nameContains": "Title" } },
    { "op": "click", "locator": { "role": "button", "name": "Save" } },
    { "op": "wait", "ms": 1500 },
    { "op": "screenshot" }
  ]
}

The last screenshot shows whether the change took. The browser stays open for about 2 minutes after a run, so a second run in that time starts at once. Over MCP, the browser tool takes the same steps.

5. Check every step's result

result.steps has one entry per step, in order, each with op and ok. When a step fails, its entry has ok: false and an error, and the steps after it do not run. The action still ends succeeded, so read ok on each step.

opFieldsLimits
gotourlhttp or https, at most 2,048 characters.
perceiveReturns perception.
clicknode or locatorExactly one of the two.
typetext, locatorWithout a locator, types into the focused field.
presskeyenter, escape, tab, backspace, delete, space, the arrows, page_up, page_down, home, end.
scrolldy-20,000 to 20,000 pixels.
waitmsAt most 30,000.
extractcontainer, fields, limit1 to 20 fields; 1 to 200 rows, 50 by default.
screenshotReturns image_url, valid for 1 hour.

When the results pass 1.8 MB, BuzzTake drops perception first, then rows, and sets truncated: true. Ask for fewer rows if you see it.

Common errors

422 invalid_input
"steps holds 1 to 50 steps, not 64". Split the work into more runs.
422 invalid_input
"step 3: op must be one of goto, perceive, click, type, press, scroll, wait, extract, screenshot"
422 invalid_input
"step 2 (click): give exactly one of node (from perceive) or a non-empty locator"
422 invalid_input
"step 4 (wait): ms must be at most 30000"

See also

To run this on your own accounts, create a BuzzTake account, then add an account and an API key.