Browser actions
Drive a page with a sequence of clicks, typing, scrolling, and waits.
The actions array runs a sequence of browser actions against the page before returning. The API navigates to
your url first, then executes each action in order. An array can contain 1 to 50 actions.
{
"url": "https://example.com/search",
"actions": [
{ "type": "write", "text": "wireless headphones" },
{ "type": "press", "key": "Enter" },
{ "type": "wait", "selector": ".results" },
{ "type": "scroll", "direction": "down", "amount": 2000 },
{ "type": "screenshot", "full_page": true }
]
}Action types
Each action is an object with a type and its own fields.
type | Fields | Description |
|---|---|---|
navigate | url | Navigate to a URL. |
click | selector, all?, frame? | Click an element. Set all: true to click every match. |
write | text | Type text into the focused element. |
press | key | Press a key (e.g. Enter, Tab). |
hover | selector, frame? | Hover over an element. |
scroll | direction? (up/down/left/right), amount?, selector?, frame? | Scroll the page or an element. |
selectOption | selector, value (string or string array), frame? | Select option(s) in a <select>. |
wait | milliseconds? (≤ 30000), selector?, timeout? (≤ 30000), frame? | Wait for a duration, or for a selector to appear. |
screenshot | full_page?, quality? (1–100) | Capture a screenshot. Max one per request. |
waitForResponse | url, match?, methods?, statusCodes?, timeout? (≤ 30000), onTimeout? (fail/continue), asResult? | Wait for an XHR or fetch() response that matches. With asResult: true, return its body as data. See Wait for a response. |
waitForNetworkIdle | idleTime? (≤ 10000), concurrency? (≤ 10), timeout? (1–30000), onTimeout? (continue/fail) | Wait for the page's network to go idle. See The waitForNetworkIdle action. |
frame runs the action inside an iframe. See Act inside an iframe.
Timing
The API loads your url before the first action and, like every navigate, waits for the page to load. After that
page load, and after every navigate, click, press, scroll and selectOption, it lets the page settle for about
3 seconds at most, or less once the network goes quiet. write, hover, wait, screenshot, waitForResponse and
waitForNetworkIdle run without a settle. The whole sequence must finish within 180 seconds; past that it fails at
the next action.
Set waitUntil on the request to choose what loading
url waits for instead: the HTML parsed, the load event, or the network going idle. It applies only to loading url.
The settle is best effort, so don't rely on it for content that loads slowly. Wait for what you need: a wait with a
selector for an element, a waitForResponse for a network response, or a waitForNetworkIdle for the network to go
idle.
Timeouts
| Action | Waits for | Limit | When it runs out |
|---|---|---|---|
click, hover, selectOption, and scroll with a selector | The element, and with frame the iframe | 30000 ms, fixed | The sequence fails at the action. |
wait with a selector | The element to appear | timeout, up to 30000 ms (the default) | The sequence fails at the action. |
waitForResponse | A matching response | timeout, up to 30000 ms (the default) | Set by onTimeout: fail by default. |
waitForNetworkIdle | The network to go idle | timeout, up to 30000 ms (the default) | Set by onTimeout: continue by default. |
A waitForNetworkIdle that runs out under onTimeout: "continue" is listed in timedOutActions.
Capture the page's network requests
Add captureXHR with executeJS: true to get every XHR and fetch() request the session
made, including the ones your actions triggered, as xhr. Use a waitForResponse with asResult to return one API
response as data instead of the page.
{
"url": "https://shop.example.com/category/headphones",
"actions": [
{ "type": "click", "selector": "button.load-more" },
{ "type": "waitForResponse", "url": "/api/products", "statusCodes": [200], "asResult": true }
]
}Act inside an iframe
Give wait, click, scroll, hover or selectOption a frame, a selector for the <iframe> element, and the
action runs inside it, cross-origin iframes included. For an iframe inside another, frame is a list of selectors from
the outermost iframe in, at most 5. write and press type into whatever has focus, so click the field first. To get
the iframes' content back with the page, add includeIframes: true or captureIframes: true. See
Iframes.
Response
{
"statusCode": 200,
"headers": { "content-type": ["text/html"] },
"finalUrl": "https://example.com/search?q=wireless+headphones",
"data": "<!doctype html>…",
"screenshot": { "format": "png", "base64": "iVBORw0KGgo…" }
}| Field | Description |
|---|---|
finalUrl | The URL after all actions ran (navigations and redirects applied). |
data | The final page — always present. HTML by default, Markdown with format: "markdown". When the sequence succeeded and its asResult action matched, the matched response's body instead. |
screenshot | Present only if a screenshot action ran: { format, base64 }. |
matchedResponse | Present only when the sequence succeeded and its asResult action matched: the response's url, method, statusCode, headers and bodyEncoding. |
xhr | Present only with captureXHR: the captured requests from the whole session. |
xhrTruncated | true when a capture limit left requests or bodies out of xhr. |
iframes | Present only with captureIframes: the final page's iframes. |
iframesTruncated | true when includeIframes or captureIframes left an iframe or its content out. |
waitUntilTimedOut | true when loading url gave up on the request's waitUntil state, so the actions ran on the page as it was. |
timedOutActions | 0-based indexes into your actions array of the waitForNetworkIdle actions that ran out and let the sequence carry on. |
error | Present if the sequence failed. |
failedActionIndex | 0-based index into your actions array of the action that failed. |
A failed action still returns 200, and data holds the page as it stood when it failed — usually the quickest way to
see why a selector missed. Fields that describe a wait or a limit, such as timedOutActions and iframesTruncated, are
absent unless they apply.
Markdown output
Set format: "markdown" to get the final page converted in data, through the same converter a plain /fetch uses, so
a page reads the same whichever way you fetched it. Worth setting when the response feeds a model rather than a parser.
markdownMode: "full" and mainContentOnly: true work here too.
{
"url": "https://example.com/search",
"format": "markdown",
"markdownMode": "full",
"actions": [{ "type": "wait", "selector": ".results" }]
}The response envelope stays JSON either way — finalUrl, screenshot and failedActionIndex have nowhere to ride in a
bare Markdown body. format: "raw" is rejected for the same reason. A page too large to convert comes back as HTML; see
Large pages.
Access control applies to every step
Each navigation an action triggers — including clicks that follow links and script-driven redirects — is re-checked against your organization's access rules. See Access control & KYC.