DocumentationBrowser steps
How to automate a task that has no action, with browser steps
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 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
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
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"]){
"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.
{ "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:
{
"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" } } ] }
]
}{
"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.
{
"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.
| op | Fields | Limits |
|---|---|---|
goto | url | http or https, at most 2,048 characters. |
perceive | Returns perception. | |
click | node or locator | Exactly one of the two. |
type | text, locator | Without a locator, types into the focused field. |
press | key | enter, escape, tab, backspace, delete, space, the arrows, page_up, page_down, home, end. |
scroll | dy | -20,000 to 20,000 pixels. |
wait | ms | At most 30,000. |
extract | container, fields, limit | 1 to 20 fields; 1 to 200 rows, 50 by default. |
screenshot | Returns 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
- "
stepsholds 1 to 50 steps, not 64". Split the work into more runs. - 422 invalid_input
- "step 3:
opmust 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-emptylocator" - 422 invalid_input
- "step 4 (wait):
msmust be at most 30000"
See also
- Browser steps reference
- Prices
- How to monitor mentions of your brand and reply
- How to retry API calls without posting twice
- How to check and fix a signed-out account
To run this on your own accounts, create a BuzzTake account, then add an account and an API key.