OpenClaw Browser Control API, CLI, and Scripting Guide

Reference for the OpenClaw browser control HTTP API, CLI, and scripting actions. Covers snapshots, refs, waits, and debug flows for local integrations and automation.

Read this when

  • Scripting or debugging the agent browser via the local control API
  • Looking for the `openclaw browser` CLI reference
  • Adding custom browser automation with snapshots and refs

For setup, configuration, and troubleshooting guidance, refer to Browser.
This page documents the local control HTTP API, the openclaw browser CLI, and scripting patterns covering snapshots, refs, waits, and debug flows.

Control API (optional)

The Gateway provides a small loopback HTTP API intended for local integrations only. This standalone server is optional: set the OPENCLAW_EAGER_BROWSER_CONTROL_SERVER=1 environment variable in the gateway service environment and restart the gateway to make the HTTP endpoints active. Without that variable, the browser control runtime continues to function through the CLI and agent tools, but no process listens on the loopback control port.

  • Status/start/stop: GET /, GET /doctor, POST /start, POST /stop, POST /reset-profile
  • Profiles: GET /profiles, POST /profiles/create, DELETE /profiles/:name
  • Tabs: GET /tabs, POST /tabs/open, POST /tabs/focus, DELETE /tabs/:targetId, POST /tabs/action
  • Snapshot/screenshot: GET /snapshot, POST /screenshot
  • Actions: POST /navigate, POST /act
  • Hooks: POST /hooks/file-chooser, POST /hooks/dialog
  • Downloads: POST /download, POST /wait/download
  • Permissions: POST /permissions/grant
  • Debugging: GET /console, POST /pdf
  • Debugging: GET /errors, GET /requests, GET /dialogs, POST /trace/start, POST /trace/stop, POST /highlight
  • Network: POST /response/body
  • State: GET /cookies, POST /cookies/set, POST /cookies/clear
  • State: GET /storage/:kind, POST /storage/:kind/set, POST /storage/:kind/clear
  • Settings: POST /set/offline, POST /set/headers, POST /set/credentials, POST /set/geolocation, POST /set/media, POST /set/timezone, POST /set/locale, POST /set/device

Internally, the CLI relies on POST /tabs/action, the batched form used for browser tab subcommands ({"action":"new"|"label"|"select"|"close"|"list", ...}); when scripting directly, favor the dedicated tab routes listed above.

Every endpoint accepts ?profile=<name>. For local managed profiles, POST /start?headless=true triggers a one-shot headless launch without altering persisted browser configuration; attach-only, remote CDP, and existing-session profiles reject that override, as OpenClaw does not start those browser processes.

For tab endpoints, targetId serves as the compatibility field name. Passing suggestedTargetId from GET /tabs or POST /tabs/open is preferred; labels and tabId handles such as t1 are also valid. Raw CDP target ids and unique raw target-id prefixes remain usable, though they are volatile diagnostic handles.

When shared-secret gateway auth is active, the browser HTTP endpoints also demand authentication:

  • Authorization: Bearer <gateway token>
  • x-openclaw-password: <gateway password>, or HTTP Basic auth using that same password

Additional notes:

  • This loopback-only browser API does not rely on trusted-proxy or Tailscale Serve identity headers.
  • When gateway.auth.mode is set to none or trusted-proxy, these loopback browser routes do not adopt those identity-aware modes; they must remain loopback-only.

/act error contract

POST /act responds with a structured error payload for validation and policy failures at the route level:

{ "error": "<message>", "code": "ACT_*" }

Current code options:

  • ACT_KIND_REQUIRED (HTTP 400): kind is absent or not recognized.
  • ACT_INVALID_REQUEST (HTTP 400): the action payload could not be normalized or validated.
  • ACT_SELECTOR_UNSUPPORTED (HTTP 400): selector was paired with an action kind that isn't supported.
  • ACT_EVALUATE_DISABLED (HTTP 403): evaluate (or wait --fn) is turned off by configuration.
  • ACT_TARGET_ID_MISMATCH (HTTP 403): top-level or batched targetId does not match the request target.
  • ACT_EXISTING_SESSION_UNSUPPORTED (HTTP 501): the action is unsupported for profiles tied to existing sessions.

Other runtime errors may still surface as { "error": "<message>" } without a code field.

Playwright requirement

Several capabilities (navigate/act/AI snapshot/role snapshot, element screenshots, PDF) depend on Playwright. Without Playwright installed, those routes respond with a clear 501 error.

Operations that remain functional without Playwright:

  • ARIA snapshots
  • Role-style accessibility snapshots (--interactive, --compact, --depth, --efficient) when a per-tab CDP WebSocket is reachable. This serves as a fallback for inspection and ref discovery; Playwright stays the primary action engine.
  • Page screenshots for the managed openclaw browser when a per-tab CDP WebSocket is available
  • Page screenshots for existing-session / Chrome MCP profiles
  • existing-session ref-based screenshots (--ref) derived from snapshot output

Operations that still require Playwright:

  • navigate
  • act
  • AI snapshots relying on Playwright's native AI snapshot format
  • CSS-selector element screenshots (--element)
  • full browser PDF export

Element screenshots also reject --full-page; the route returns fullPage is not supported for element screenshots.

Encountering Playwright is not available in this gateway build means the packaged Gateway lacks the core browser runtime dependency. Reinstall or update OpenClaw and restart the gateway. For Docker, also add the Chromium browser binaries using the steps below.

Docker Playwright install

When your Gateway is Dockerized, steer clear of npx playwright (npm override conflicts). For custom images, embed Chromium directly into the image:

OPENCLAW_INSTALL_BROWSER=1 ./scripts/docker/setup.sh

The browser depends on system libraries too, so installing Chromium in a temporary Compose container won't persist. Rebuild the image with OPENCLAW_INSTALL_BROWSER=1 instead. To keep browser downloads and other caches, persist /home/node via OPENCLAW_HOME_VOLUME or a bind mount. Refer to Docker.

How it works (internal)

A minimal loopback control server handles HTTP requests and links to Chromium-based browsers through CDP. Advanced operations (click/type/snapshot/PDF) use Playwright layered over CDP; when Playwright is absent, only non-Playwright actions are accessible. The agent sees a single stable interface while local/remote browsers and profiles change underneath without disruption.

CLI quick reference

Every command accepts --browser-profile <name> to select a specific profile, and --json for machine-readable output.

Basics: status, tabs, open/focus/close

openclaw browser status
openclaw browser doctor
openclaw browser doctor --deep    # add a live snapshot probe
openclaw browser start
openclaw browser start --headless # one-shot local managed headless launch
openclaw browser stop            # also clears emulation on attach-only/remote CDP
openclaw browser reset-profile   # moves the profile's browser data to Trash
openclaw browser tabs
openclaw browser tab             # shortcut for current tab
openclaw browser tab new
openclaw browser tab new --label research
openclaw browser tab label abcd1234 research
openclaw browser tab select 2
openclaw browser tab close 2
openclaw browser open https://example.com
openclaw browser focus abcd1234
openclaw browser close abcd1234

Profiles: list, create, delete

openclaw browser profiles
openclaw browser create-profile --name research --color "#0066CC"
openclaw browser create-profile --name attach --driver existing-session --cdp-url http://127.0.0.1:9222
openclaw browser delete-profile --name research

Inspection: screenshot, snapshot, console, errors, requests

openclaw browser screenshot
openclaw browser screenshot --full-page
openclaw browser screenshot --ref 12        # or --ref e12
openclaw browser screenshot --labels
openclaw browser snapshot
openclaw browser snapshot --format aria --limit 200
openclaw browser snapshot --interactive --compact --depth 6
openclaw browser snapshot --efficient
openclaw browser snapshot --labels
openclaw browser snapshot --urls
openclaw browser snapshot --selector "#main" --interactive
openclaw browser snapshot --frame "iframe#main" --interactive
openclaw browser snapshot --out snapshot.txt
openclaw browser console --level error
openclaw browser errors --clear
openclaw browser requests --filter api --clear
openclaw browser pdf
openclaw browser responsebody "**/api" --max-chars 5000

Actions: navigate, click, type, drag, wait, evaluate

openclaw browser navigate https://example.com
openclaw browser resize 1280 720
openclaw browser click 12 --double           # or e12 for role refs
openclaw browser click-coords 120 340        # viewport coordinates
openclaw browser type 23 "hello" --submit
openclaw browser press Enter
openclaw browser hover 44
openclaw browser scrollintoview e12
openclaw browser drag 10 11
openclaw browser select 9 OptionA OptionB
openclaw browser download e12 report.pdf
openclaw browser waitfordownload report.pdf
openclaw browser upload /tmp/openclaw/uploads/file.pdf
openclaw browser upload /tmp/openclaw/uploads/file.pdf --ref e12
openclaw browser upload media://inbound/file.pdf
openclaw browser fill --fields '[{"ref":"1","type":"text","value":"Ada"}]'
openclaw browser dialog --accept
openclaw browser dialog --dismiss --dialog-id d1
openclaw browser wait --text "Done"
openclaw browser wait "#main" --url "**/dash" --load networkidle --fn "window.ready===true"
openclaw browser evaluate --fn '(el) => el.textContent' --ref 7
openclaw browser evaluate --fn 'const title = document.title; return title;'
openclaw browser evaluate --timeout-ms 30000 --fn 'async () => { await window.ready; return true; }'
openclaw browser highlight e12
openclaw browser trace start
openclaw browser trace stop

State: cookies, storage, offline, headers, geo, device

openclaw browser cookies
openclaw browser cookies set session abc123 --url "https://example.com"
openclaw browser cookies clear
openclaw browser storage local get
openclaw browser storage local set theme dark
openclaw browser storage session clear
openclaw browser set offline on
openclaw browser set headers --headers-json '{"X-Debug":"1"}'
openclaw browser set credentials user pass            # --clear to remove
openclaw browser set geo 37.7749 -122.4194 --origin "https://example.com"
openclaw browser set media dark
openclaw browser set timezone America/New_York
openclaw browser set locale en-US
openclaw browser set device "iPhone 14"

Notes:

  • The agent-facing browser tool exposes action=download (with required ref and path) alongside action=waitfordownload (where path is optional). Both yield the stored download URL, a suggested filename, and a guarded local path. Explicit download interception applies to managed Playwright profiles; existing-session profiles respond with an unsupported-operation error.
  • Favor atomic chooser uploads: supply the trigger --ref together with the upload so OpenClaw arms and clicks within a single request. Paths-only upload stays available when a later trigger is deliberate. Use --input-ref or --element to assign a file input directly. dialog serves as an arming call; invoke it before the click or press that opens the dialog. When an action brings up a modal, the action response carries blockedByDialog and browserState.dialogs.pending; hand that dialogId back to respond directly. Dialogs managed outside OpenClaw show up under browserState.dialogs.recent.
  • click/type/etc demand a ref sourced from snapshot (numeric 12, role ref e12, or actionable ARIA ref ax12). CSS selectors are deliberately unsupported for actions. Turn to click-coords when the visible viewport position is the sole dependable target.
  • Download and trace paths stay confined to OpenClaw temp roots: /tmp/openclaw{,/downloads} (fallback: ${os.tmpdir()}/openclaw/...).
  • upload accepts files from the OpenClaw temp uploads root and OpenClaw-managed inbound media. Managed inbound media may be cited as media://inbound/<id>, sandbox-relative media/inbound/<id>, or a resolved path inside the managed inbound media directory. Nested media refs, traversal, symlinks, hardlinks, and arbitrary local paths remain rejected.
  • upload can likewise set file inputs directly through --input-ref or --element.

Stable tab ids and labels persist across Chromium raw-target replacement when OpenClaw can demonstrate the replacement tab, for instance a unique old/new pair for the same URL or a single old tab turning into a single new tab after form submission. Ambiguous duplicate-URL replacements get fresh handles. Raw target ids stay volatile; favor suggestedTargetId from tabs in scripts.

Snapshot flags summarized:

  • --format ai (default with Playwright): AI snapshot with numeric refs (aria-ref="<n>").
  • --format aria: accessibility tree with axN refs. When Playwright is present, OpenClaw ties refs to backend DOM ids on the live page so follow-up actions can consume them; otherwise treat the output as inspection-only.
  • --efficient (or --mode efficient): compact role snapshot preset. Set browser.snapshotDefaults.mode: "efficient" to make this the default (see Gateway configuration).
  • --interactive, --compact, --depth, --selector force a role snapshot with ref=e12 refs. --frame "<iframe>" limits role snapshots to an iframe.
  • With Playwright, --labels adds a screenshot with overlaid ref labels (prints MEDIA:<path>) plus an annotations array containing each ref's bounding box. On screenshot, Playwright-backed labels work with --full-page, --ref, and --element; on snapshot, the accompanying screenshot stays viewport-only. Existing-session/chrome-mcp profiles draw overlay labels on page screenshots but do not return annotations or employ the Playwright full-page/ref/element projection helper. Without Playwright or chrome-mcp, labeled screenshots are unavailable.
  • --urls appends discovered link destinations to AI snapshots.

Snapshots and refs

OpenClaw offers two "snapshot" varieties:

  • AI snapshot (numeric refs): openclaw browser snapshot (default; --format ai)

    • Output: a text snapshot that includes numeric refs.
    • Actions: openclaw browser click 12, openclaw browser type 23 "hello".
    • Internally, the ref is resolved via Playwright's aria-ref.
  • Role snapshot (role refs like e12): openclaw browser snapshot --interactive (or --compact, --depth, --selector, --frame)

    • Output: a role-based list/tree with [ref=e12] (and optional [nth=1]).
    • Actions: openclaw browser click e12, openclaw browser highlight e12.
    • Internally, the ref is resolved via getByRole(...) (plus nth() for duplicates).
    • Add --labels to include a screenshot with overlayed e12 labels. On Playwright-backed profiles this also returns per-ref bounding-box metadata (annotations[]).
    • Add --urls when link text is ambiguous and the agent needs concrete navigation targets.
  • ARIA snapshot (ARIA refs like ax12): openclaw browser snapshot --format aria

    • Output: the accessibility tree as structured nodes.
    • Actions: openclaw browser click ax12 works when the snapshot path can bind the ref through Playwright and Chrome backend DOM ids.
  • If Playwright is unavailable, ARIA snapshots can still be useful for inspection, but refs may not be actionable. Re-snapshot with --format ai or --interactive when you need action refs.

  • When the driver exposes stable document identity, consecutive AI and role snapshots for the same profile, tab, document, and option family append [new] to ref-bearing lines absent from the previous snapshot. Navigation starts a fresh unmarked baseline; existing-session snapshots omit deltas. The first snapshot establishes the baseline without markers; later responses also expose newElements, and add a count footer when the value is nonzero. Structured --format aria snapshots with axN refs do not use delta markers.

  • Docker proof for the raw-CDP fallback path: pnpm test:docker:browser-cdp-snapshot starts Chromium with CDP, runs browser doctor --deep, and verifies role snapshots include link URLs, cursor-promoted clickables, and iframe metadata.

Ref behavior:

  • Refs are not stable across navigations; if something fails, re-run snapshot and use a fresh ref.
  • A batch stops after a committed main-frame navigation, including a same-URL reload, or after the page closes. Its aborted summary reports the action number and skipped count; take a fresh snapshot before issuing dependent actions, or use separate act calls when navigation is expected.
  • /act returns the current raw targetId after action-triggered replacement when it can prove the replacement tab. Keep using stable tab ids/labels for follow-up commands.
  • If the role snapshot was taken with --frame, role refs are scoped to that iframe until the next role snapshot.
  • Unknown or stale axN refs fail fast instead of falling through to Playwright's aria-ref selector. Run a fresh snapshot on the same tab when that happens.

Browser batch CLI

openclaw browser batch executes a sequence of nested /act operations within a single /act invocation, reusing the same kind="batch" runtime that the agent tool accesses. This lets CLI users and scripts bundle actions such as wait, click, type, and evaluate into one replayable workflow, avoiding separate round trips for each step. Every element inside actions[] must be a BrowserActRequest, meaning the closed union accepted by the /act endpoint (click, clickCoords, type, press, hover, scrollIntoView, drag, select, fill, resize, wait, evaluate, close, batch), rather than free-form openclaw browser subcommands. On profile="user" and other chrome-mcp profiles tied to existing sessions, batch is unavailable; those cases require sending actions one at a time.

  • CLI usage: pipe the JSON array through stdin using openclaw browser batch --actions '<json>', openclaw browser batch --actions-file plan.json, or openclaw browser batch --actions-file -. The --continue flag controls stopOnError=false; by default execution halts at the first error. With --target-id, the entire batch is confined to a single tab.
  • Ref lifecycle: refs originate from a snapshot call made before the batch begins, since snapshotting is not treated as a nested action. When a nested action alters page state, for instance a click that causes navigation or an evaluate that modifies the DOM, earlier refs may become stale for the remainder of the batch. Order state-changing actions early, or break them into a subsequent batch after taking a fresh snapshot. Navigation and re-snapshotting occur outside the batch via openclaw browser navigate and snapshot, because open, navigate, and snapshot do not count as /act action types.
  • Target id conflicts: a nested action can leave out targetId or reuse the request-level targetId; if an explicit nested targetId points to a different tab, the batch is rejected with ACT_TARGET_ID_MISMATCH before any action executes. Batched actions deliberately share the request's tab.
  • Error summary: the response is { "results": [{ "ok": true }, { "ok": false, "error": "<message>" }, ...] }, listing one entry per action in sequence. With the default stopOnError, the list truncates at the first failure; setting --continue returns results for every action. Any failed entry causes the CLI to exit with a nonzero code; adding --json keeps the full ordered response available for scripts.

Wait power-ups

Beyond time and text, other conditions can be awaited:

  • Wait for URL (Playwright glob patterns are supported):
    • openclaw browser wait --url "**/dash"
  • Wait for load state:
    • openclaw browser wait --load networkidle
    • This works on managed openclaw and raw/remote CDP profiles. Profiles running the existing-session driver, including the default user profile, will reject networkidle; instead, rely on --url, --text, a selector, or --fn waits in those cases.
  • Wait for a JS predicate:
    • openclaw browser wait --fn "window.ready===true"
  • Wait for a selector to become visible:
    • openclaw browser wait "#main"

You can chain these together:

openclaw browser wait "#main" \
  --url "**/dash" \
  --load networkidle \
  --fn "window.ready===true" \
  --timeout-ms 15000

Debug workflows

If an action fails, such as with "not visible", "strict mode violation", or "covered":

  1. Run openclaw browser snapshot --interactive
  2. Switch to click <ref> / type <ref> (in interactive mode, prefer role references)
  3. If the problem persists, use openclaw browser highlight <ref> to inspect what Playwright is focusing on
  4. For odd page behavior:
    • Try openclaw browser errors --clear
    • Try openclaw browser requests --filter api --clear
  5. For thorough troubleshooting, capture a trace:
    • Start with openclaw browser trace start
    • Recreate the issue
    • Run openclaw browser trace stop (this outputs TRACE:<path>)

JSON output

--json is meant for scripting and structured automation.

Examples:

openclaw browser --json status
openclaw browser --json snapshot --interactive
openclaw browser --json requests --filter api
openclaw browser --json cookies

Role snapshots in JSON carry refs along with a compact stats section (covering lines, chars, refs, and interactive elements) so tools can gauge payload size and density.

State and environment knobs

For workflows that need "make the site behave like X", these come in handy:

  • Cookies: cookies, cookies set, cookies clear
  • Storage: storage local|session get|set|clear
  • Offline mode: set offline on|off
  • Headers: set headers --headers-json '{"X-Debug":"1"}' (or the positional variant set headers '{"X-Debug":"1"}')
  • HTTP basic auth: set credentials user pass (or --clear)
  • Geolocation: set geo <lat> <lon> --origin "https://example.com" (or --clear)
  • Media: set media dark|light|no-preference|none
  • Timezone / locale: set timezone ..., set locale ...
  • Device / viewport:
    • set device "iPhone 14" (Playwright device presets)
    • set viewport 1280 720

Security and privacy

  • The openclaw browser profile can hold logged-in sessions, so handle it as sensitive data.
  • browser act kind=evaluate / openclaw browser evaluate and wait --fn run arbitrary JavaScript within the page context, which makes them vulnerable to prompt injection. Turn this off with browser.evaluateEnabled=false if it is not required.
  • openclaw browser evaluate --fn takes a function source, an expression, or a statement body. Statement bodies get wrapped as async functions, so return the value you need with return. When the page-side function might exceed the default evaluate timeout, go with --timeout-ms <ms>.
  • For login and anti-bot guidance (X/Twitter and similar), check Browser login + X/Twitter posting.
  • Keep the Gateway/node host private, either on loopback or a tailnet-only setup.
  • Remote CDP endpoints carry significant power, so tunnel and secure them.

Strict-mode example (private and internal destinations are blocked by default):

{
  browser: {
    ssrfPolicy: {
      dangerouslyAllowPrivateNetwork: false,
      hostnameAllowlist: ["*.example.com", "example.com"],
      allowedHostnames: ["localhost"], // optional exact allow
    },
  },
}
3,207 words · updated Aug 7, 2026