# 0Browser — complete agent guide Updated: 2026-10-09. Use this guide to perform the user's task with 0Browser. Read https://0browser.io/openapi.json for current endpoint schemas before using an unfamiliar endpoint. That specification is authoritative if this guide and the API differ. ## Addresses and authentication - API base: https://api.0browser.io/api/v1 - Console and live sessions: https://app.0browser.io - API reference: https://0browser.io/openapi - Discovery index: https://0browser.io/llms.txt - Specification: https://0browser.io/openapi.json (public, no key required). - Health: GET https://api.0browser.io/health (outside /api/v1). Use the user's existing API key, preferably passed as ZERO_API_KEY in the runtime environment. Keys are issued by the user's 0Browser operator; there is no public self-service signup. Send Authorization: Bearer sk_... on every REST request and Content-Type: application/json for JSON bodies. REST does not accept ?token= authentication. Browser WebSockets accept ?token=sk_... because they cannot set arbitrary headers. Regular keys see only their own resources. Admin keys can access resources across accounts; account AI settings and stored secrets remain specific to the calling key. Missing or inaccessible resources return 404. Do not put real API keys, model keys, cookies or account passwords into shared examples or logs. Use returned IDs exactly, including all UUID characters; redacted or shortened IDs are not valid endpoint paths. ## Pick the execution method 1. Use direct CDP when the client already has Puppeteer, Playwright or custom browser tooling. Both ephemeral and profile-backed sessions work. 2. Use the Agent API for one-off natural-language actions, action discovery and structured extraction without creating a Skill. It requires an active profile-backed session. The engine is Stagehand 3.7.3, attached to the existing browser via CDP. 3. Use Operations and Skills for reusable automation. A Build authors a draft, publishing creates an immutable version, and a Run executes a published version using saved profile input. Use one control method at a time. A live profile lease held by a Run, Build or Agent API request blocks other managed controllers. Agent API uses the same lease mechanism; it cannot take control from a Run or Build. Raw CDP connections remain independent and can still race another controller, so coordinate them in your client. ## Create a profile and an active session A profile persists browser data between sessions. A session is a running browser associated with that profile, or an ephemeral browser when profile_id is omitted. Close a session after use to release browser resources; this does not delete the profile. The examples below require curl and jq. Set ZERO_API_KEY to your existing key before running them. Replace placeholders with actual returned IDs; never use a redacted sample UUID. ```bash API='https://api.0browser.io/api/v1' PROFILE=$(curl --fail-with-body -sS -X POST "$API/profiles" \ -H "Authorization: Bearer $ZERO_API_KEY" \ -H 'Content-Type: application/json' \ -d '{"name":"Research browser","metadata":{"purpose":"research"}}') PROFILE_ID=$(printf '%s' "$PROFILE" | jq -r '.id // .data.id') SESSION=$(curl --fail-with-body -sS -X POST "$API/sessions" \ -H "Authorization: Bearer $ZERO_API_KEY" \ -H 'Content-Type: application/json' \ -d "{\"profile_id\":\"$PROFILE_ID\",\"browser_version\":150,\"headless\":false}") SESSION_ID=$(printf '%s' "$SESSION" | jq -r '.id // .data.id') CDP_URL=$(printf '%s' "$SESSION" | jq -r '.cdp_url // .data.cdp_url') ``` For an ephemeral CDP session, POST /sessions with {"browser_version":150,"headless":true} and omit profile_id. Ephemeral sessions are not supported by the Agent API or Skills Build/Run. GET /sessions/{id} reads status. The console URL is https://app.0browser.io/sessions/{id}. Session creation returns cdp_url for direct connection. If an existing profile already has an active_session, inspect and reuse it where appropriate instead of starting duplicate browsers. New profiles capture two effective creation defaults: stealth mode and desktop browser version. Admin can change both in Console Settings → Browser or with GET/PUT /settings/browser. The version setting is default_browser_version and accepts 144, 148, or 150; the built-in fallback is 150. When POST /profiles omits browser_version, the backend snapshots the configured default into that profile. Existing profiles keep their saved version when the default later changes. The legacy GET/PUT /settings/stealth routes remain available for stealth-only clients. Optional proxy, timezone, fingerprint, dimensions and extension fields are documented in the creation schemas. Use only browser_version values accepted by the schema (144, 148, 150). Do not guess legacy browser launch flags. ## Agent API: navigate, discover, act and extract Prefix: /sessions/{session_id}/agent Use the same Bearer key as session creation. Sessions must be active and attached to a profile. - POST /goto: {"url":"https://example.com"}. Accepts HTTP(S) URLs without embedded credentials. - GET /state: returns URL, title, target_id, tab list, lease state and engine/version. When another controller holds a lease, returns lease metadata and url:null without connecting to CDP. - POST /observe: {"instruction":"Find the Learn more link"}. Returns candidate actions with selector, description, method and arguments. - POST /act with instruction: {"instruction":"Click the Learn more link"}. - POST /act with replay: {"action":}. Send instruction OR action. Replay selectors from actual evidence; do not invent them. - POST /extract: {"instruction":"Read the main heading","schema":{"type":"object","properties":{"heading":{"type":"string"}},"required":["heading"],"additionalProperties":false}}. The schema is JSON Schema with an object root. Output is validated against it. - DELETE /lease: cancels this account's active agent request. It does not remove a Run/Build lease or create a permanent agent session. Success envelope: {"data":,"engine":"stagehand","engine_version":"3.7.3","session_id":"..."}. observe data is an array of actions; act data includes success and executed actions; extract data is the schema-shaped object. If act returns success:false, inspect before taking another action; HTTP 200 alone does not prove an action succeeded. Optional target_id in the request selects a specific browser tab. Obtain it from state; an unavailable explicit tab is rejected. Without it, the initial session tab is used when available, otherwise a non-extension tab. Example navigation and extraction using the session created above: ```bash curl --fail-with-body -sS -X POST "$API/sessions/$SESSION_ID/agent/goto" \ -H "Authorization: Bearer $ZERO_API_KEY" \ -H 'Content-Type: application/json' \ -d '{"url":"https://example.com"}' curl --fail-with-body -sS -X POST "$API/sessions/$SESSION_ID/agent/extract" \ -H "Authorization: Bearer $ZERO_API_KEY" \ -H 'Content-Type: application/json' \ -d '{"instruction":"Extract the main heading","schema":{"type":"object","properties":{"heading":{"type":"string"}},"required":["heading"],"additionalProperties":false}}' ``` observe, extract and instruction-based act require AI settings configured for the calling API key. Use Console Settings → AI or PUT /automation/settings with endpoint, model and key. endpoint is an HTTPS API base URL; key is required on first setup. Omit key on later updates to retain it. Agent API uses an OpenAI-compatible Responses endpoint. An Admin token does not automatically inherit another account's AI configuration. goto, state and action replay need no model key. Each request acquires a profile lease with a 90-second TTL. A worker has a 60-second hard execution limit and the proxy a 70-second budget. Normal completion releases the lease. A timeout, interrupted connection or cancellation can leave the lease until its original expiry; inspect state before retrying. Limits: 20 requests/minute/API key, four workers globally, 64 KiB input, 2 MiB response. There is no multi-step agent or MCP endpoint in this release. Malformed structured model responses may retry once for read-only observe/extract; act is not automatically retried. ## Agent lifecycle WebSocket Connect to wss://api.0browser.io/api/v1/sessions/{session_id}/agent/stream?token=sk_... Send one operation per socket: {"operation":"extract","input":{"instruction":"Read the heading","schema":{"type":"object","properties":{"heading":{"type":"string"}},"required":["heading"],"additionalProperties":false}}} Events are {"event":"started",...}, then {"event":"completed","result":} or {"event":"error","status":...,"error":"..."}. This stream reports request lifecycle, not model reasoning or token-level output. Send {"action":"cancel"} or disconnect to cancel. started does not guarantee that control was acquired; check the final event. ## Direct CDP Use the exact cdp_url returned by session creation. For browser WebSocket authentication, pass your Bearer header if the client supports it, or append token= to the URL. Preserve existing query parameters. Never treat a redacted URL as a usable endpoint. Puppeteer (npm install puppeteer-core): ```js import puppeteer from 'puppeteer-core'; const endpoint = new URL(session.cdp_url); endpoint.searchParams.set('token', process.env.ZERO_API_KEY); const browser = await puppeteer.connect({browserWSEndpoint: endpoint.href}); try { const pages = await browser.pages(); const page = pages[0] || await browser.newPage(); await page.goto('https://example.com'); console.log(await page.title()); } finally { browser.disconnect(); } ``` Playwright: install playwright-core and call chromium.connectOverCDP(endpoint.href). Use the existing browser context and pages. Disconnecting the client is different from closing the server session; release it through DELETE /sessions/{id} when done. The legacy page-level CDP endpoint differs from the browser-level endpoint, so prefer the returned browser-level cdp_url. Live viewer: wss://api.0browser.io/live/{session_id}?token=sk_...; optional targetId selects a target. Use the console for an interactive viewer. Consult the specification's x-websockets section for other socket URLs. ## Profiles without catalog scans If you already know profile IDs, fetch them directly. Lists use data, total, limit, offset; response shape is endpoint-specific, and create/get responses may be bare objects. Do not assume every response is wrapped in data. Read the OpenAPI response schema or check the actual response. - GET /profiles/{id}: one profile and its status. - GET /profiles?metadata.inventory_browser_id=&limit=10&offset=0: exact top-level metadata match; total counts filtered results. Metadata filters apply only when supplied. Unknown metadata keys do not silently return the whole catalog. - POST /profiles/batchGet with {"ids":[""]}: status snapshots without pagination, maximum 1000 IDs/request. data includes id/status/active_session/updated_at/metadata; inaccessible or absent IDs appear in missing. ids:[] returns empty arrays. The implemented route uses /batchGet, not profiles:batchGet. - PATCH /profiles/{id}/metadata with {"metadata":{"inventory_browser_id":""}}: merges client labels by key, keeps other labels, null removes a key. Reserved internal keys are rejected. This label-only endpoint remains writable while automation controls the profile. - GET /profiles/{id}/metadata: read labels. General profile updates use PUT /profiles/{id}; they remain subject to automation control guards. search is not an exact external-ID lookup. Use metadata matching or known IDs. Do not invent unsupported query aliases, idempotency headers or updated_after filters. ## Operations, Skills, Builds and Runs All automation routes are below /automation. Workflows are retired. A profile has input assigned to an Operation; a Skill describes reusable actions/verification; a Build authors the draft; a Run locks and executes one published version. A typical existing-Skill execution sequence: 1. GET /automation/operations and select the intended Operation; GET /automation/operations/{operation_id}/skills to find its Skill. Use the real IDs and verify the published version before execution. 2. PUT /automation/operations/{operation_id}/assignments with {"profile_id":"","input":{...}}. Input must satisfy the Operation input_schema. This saves/replaces the profile assignment. 3. POST /automation/operations/{operation_id}/runs with {"profile_id":"","version_id":""}. This returns a Run id/status. Do not include input, even {}: Run input comes from the saved assignment. Follow the schema for any additional fields. 4. GET /automation/operations/{operation_id}/runs/{run_id} and its /events to inspect status and progress. GET /automation/session-runs/{session_id} gives compact Run progress for a session. Automation lists can be bare arrays; consult the response contract. 5. At human_required, inspect the actual blocker and page before intervention. POST /automation/operations/{operation_id}/runs/{run_id}/intervene supports pause/cancel/retry/guide/complete_step with action-specific requirements. guide needs a note; retry requires human_required. complete_step is only available for a failed tool step at a before checkpoint in older formats; it is rejected for format v3 milestones, which must pass locked verification. Use guide for those Runs. Where manual completion is supported, provide a note and the actual tool output, not a guessed success value. To build a new Skill, configure this key's AI settings, assign the profile, and open an active session. POST /automation/operations/{operation_id}/builds with session_id and output_expectation, then POST /automation/operations/{operation_id}/builds/{build_id}/prompt with {"message":"..."}. Follow Build events. Supply skill_id when improving an existing Skill. Stop a Build with POST /automation/operations/{operation_id}/builds/{build_id}/stop when releasing control. Review the draft and publish through the Skill's /publish endpoint under its Operation. Publishing creates a new immutable version for future Runs. Existing Runs retain their locked version. Input/output contracts are stable across published versions. Never publish or retry unrelated Runs merely to perform a one-off Agent API task. Account secrets, shared input, extension management, cookie import/export, profile grouping and other advanced endpoints are documented in OpenAPI. Follow the actual schemas and avoid logging sensitive input or revealing stored secrets into conversation. ## Errors and safe retry behavior - 400: invalid JSON/fields/schema/URL. Correct the request using the documented schema. - 401: missing, invalid or expired API key. Obtain the user's valid key through their normal secret configuration. - 403: insufficient permission, including Admin-only settings. - 404: missing or inaccessible resource. Check the full returned ID and ownership; do not enumerate other accounts. - 409: state/control conflict or missing AI setup. Read the error and agent state/Run/Build status. Wait for the existing controller or have the user pause/stop it; do not delete its lease. - 413: request too large. - 422: extracted data does not satisfy the schema. - 429: rate limit or sidecar capacity. Apply bounded backoff; honor Retry-After when present. - 502/503: model/browser/upstream unavailable. Verify health and current state. - 504 or disconnect: outcome may be uncertain. Inspect the browser before retrying an action, especially form submission or payment. Agent lease may persist until expiry. Read-only requests can be retried with bounded backoff. Do not blindly retry writes, create profiles again after a lost response, or replay clicks when the prior outcome is unknown. POST /profiles has no documented Idempotency-Key support. Reuse saved IDs and verify actual page state. ## Cleanup Disconnect your CDP client and close a session you created when the task is finished: ```bash curl --fail-with-body -sS -X DELETE "$API/sessions/$SESSION_ID" \ -H "Authorization: Bearer $ZERO_API_KEY" ``` A live Run/Build/agent lease can block closing a session. Release or finish your own controller first. Keep persistent profiles if the user needs their browser data; DELETE /profiles/{id} is a separate data-deletion action. Preserve existing user sessions and profiles unless the task explicitly calls for their closure/deletion.