Skip to main content
Once you’ve recorded and trained a site, BrowserWire serves its views and actions as REST endpoints via bw run. You can call these from any AI agent, automation script, or HTTP client.

Working with the local API

List discovered sites

Start by confirming which sites are available:
curl http://127.0.0.1:8787/api/sites
[
  {
    "id": "mf_abc123",
    "domain": "example.com",
    "slug": "example-com",
    "stateCount": 4,
    "viewCount": 3,
    "actionCount": 7
  }
]

Get the full manifest

Fetch the manifest to see all states, views, and actions:
curl http://127.0.0.1:8787/api/sites/example-com/manifest
The manifest contains every state with its views and actions, including their input schemas. You can pass this to an LLM as context so it knows what operations are available.

Call operations

# Views use GET — reads structured data from a page state
curl http://127.0.0.1:8787/api/sites/example-com/views/HomePage/product_list

URL pattern

All endpoints follow this pattern:
/api/sites/{slug}/views/{state}/{viewName}     # GET
/api/sites/{slug}/actions/{state}/{actionName}  # POST
  • slug — site identifier (e.g., example-com)
  • state — page state name from the manifest (e.g., HomePage, LoginPage)
  • viewName / actionName — the specific view or action within that state

Feeding to an LLM agent

A practical approach for tool-using LLMs:
  1. Fetch the manifest: GET /api/sites/{slug}/manifest
  2. Extract the list of states, views, and actions into a tool schema
  3. Let the LLM decide which endpoints to call based on the user’s request
  4. Execute the chosen endpoint and return the result
import requests

# Fetch manifest
manifest = requests.get("http://127.0.0.1:8787/api/sites/example-com/manifest").json()

# Build tool descriptions from states
tools = []
for state in manifest["states"]:
    for view in state.get("views", []):
        tools.append({
            "name": f"read_{state['name']}_{view['name']}",
            "description": f"Read {view['name']} from {state['name']}",
            "endpoint": f"GET /api/sites/example-com/views/{state['name']}/{view['name']}"
        })
    for action in state.get("actions", []):
        tools.append({
            "name": f"do_{state['name']}_{action['name']}",
            "description": f"Execute {action['name']} on {state['name']}",
            "inputs": action.get("inputs", []),
            "endpoint": f"POST /api/sites/example-com/actions/{state['name']}/{action['name']}"
        })

# Pass `tools` to your LLM as available functions

Response format

All endpoints return JSON with an ok field: Success (view):
{
  "ok": true,
  "data": [ ... ],
  "state": "HomePage",
  "steps": ["navigate:https://example.com/", "view:product_list"]
}
Success (action):
{
  "ok": true,
  "state": "DashboardPage",
  "steps": ["navigate:https://example.com/login", "action:submit_login"]
}
Error:
{
  "error": "Action 'submit_login' failed: element not found"
}

CORS

The local server includes permissive CORS headers:
Access-Control-Allow-Origin: *
Access-Control-Allow-Methods: GET, POST, OPTIONS
Access-Control-Allow-Headers: Content-Type
You can call the API directly from browser-based agents or frontends without proxy configuration.
The server listens on 127.0.0.1 by default (local only). The bw run command does not currently support a --host flag — it always binds to localhost.