Python SDK
stealth_browser: a standard-library client for the platform API, Python ≥ 3.10, synchronous with an asyncio facade.
Install
Not on PyPI yet; publishing follows the platform launch. Until then, put sdks/python from a repository checkout on your path:
export PYTHONPATH="/path/to/stealth-browser/sdks/python:$PYTHONPATH"Standard library only. It mirrors the JavaScript client resource for resource; the one difference is that it does not run the platform contracts locally, so a bad request is reported by the API rather than before sending.
Client
import osfrom stealth_browser import StealthBrowser, StealthApiError client = StealthBrowser(os.environ["STEALTH_API_KEY"], base_url=os.environ["STEALTH_API_URL"])StealthBrowser(api_key, base_url=..., timeout=60.0, retries=2, opener=None). The key's project is the project. timeout bounds each request; retries applies to idempotent requests only (GETs and creates, which carry an Idempotency-Key). Environment proxies are ignored unless you pass your own urllib.request opener.
Surfaces
| Surface | Methods |
|---|---|
client.run(task, **fields, poll_interval=, timeout=, on_event=) | Create and wait; fields are run fields (model_id, session_id, workspace_id, profile_id, judge, allowed_domains, …) |
client.runs | create, get, list, cancel, wait, events (generator), message(run_id, text), withdraw_message(run_id, message_id) |
client.agent_sessions | list, get, rename(id, title), delete(id) |
client.models() | The catalog |
client.browsers | create → CloudBrowser, attach, get, list, stop |
client.assets | list(browser_id), get, text, content (bytes), retry |
client.profiles | list, create, get, update, import_state, save(profile_id, browser_id) |
client.workspaces | list, create, get, update, sessions; files.list/upload/download/text/move/folder/delete/archive |
client.schedules | list, get, history, preview, run_now |
client.integrations, client.proxies | list, get; policy, credentials |
client.billing | get, set_budget(usd or None), statement(month), ledger(limit), rates |
client.history, client.usage, client.project | list(**query), get(days=), current |
client.request(method, path, body=, query=, ...) | Any other route, with the same errors and retries |
verify_webhook(body, headers, secret) | Standard Webhooks v1 check; returns the event or raises webhook_signature |
CloudBrowser
browser = client.browsers.create({"settings": {"locale": "en-US"}})browser.ready()browser.navigate("https://example.com")state = browser.observe(boxes=True) # the observation object as the API sends itlink = next((t for t in state["targets"] if t["role"] == "link"), None)if link: browser.click(link["ref"]) # observation frames are handled insidetext = browser.text(format="markdown")["text"]matches = browser.find(role="button", name="sign in")["matches"]browser.stop()ready, navigate, observe(boxes=, attributes=), preview, snapshot, act(name, params) plus click, type, select, press, scroll, hover, wait, click_at, back, forward, reload, switch_tab, close_tab, new_tab, dialog_policy, the read-only find(**query) and text(format=, max_chars=), and events, control, assets, stop. Frames, stale-frame retries and the read-only / page-changing distinction work as in the JavaScript client.
asyncio
AsyncStealthBrowser is the same client, awaitable: every method is a coroutine, events is an async iterator, browsers.create / attach give an AsyncCloudBrowser. It is a facade over the synchronous client, not a second transport — each call runs the synchronous implementation in a worker thread (asyncio.to_thread), so await never blocks the event loop and asyncio.gather runs calls concurrently, while retries, idempotency and frame handling stay one code path.
import asyncio, osfrom stealth_browser import AsyncStealthBrowser async def main(): async with AsyncStealthBrowser(os.environ["STEALTH_API_KEY"], base_url=os.environ["STEALTH_API_URL"]) as client: run = await client.runs.create({"task": "Open https://example.com and report the title."}) async for event in client.runs.events(run["id"]): print(event["type"]) browser = await client.browsers.create() await browser.ready() state = await browser.observe() await browser.stop() asyncio.run(main())Errors and streams
Every failure is a StealthApiError with status, code (see Errors), request_id, details and local. events() and on_event use the API's server-sent events when offered and fall back to cursor polling — also when the API refuses a stream with rate_limited; client.diagnostics counts both.
