# Ad Blocker Source: https://docs.anchorbrowser.io/advanced/adblocker Block ads, trackers, and unwanted content in your browser sessions Ad blocking is enabled by default in Anchor Browser. It blocks ads, trackers, and malicious content to improve page load times and create cleaner automation. Ad blocking is enabled by default. Disable it only if you need to test ad-related functionality. ## Quick Start ```javascript node.js theme={null} import AnchorBrowser from 'anchorbrowser'; (async () => { const anchorClient = new AnchorBrowser({apiKey: process.env.ANCHOR_API_KEY}); const session = await anchorClient.sessions.create({ // Optional: ad blocking is enabled by default, so this configuration is not required browser: { adblock: { active: true // Set to false to disable ad blocking } } }); console.log("Session:", session.data.id); })().catch(console.error); ``` ```python python theme={null} import os from anchorbrowser import Anchorbrowser anchor_client = Anchorbrowser(api_key=os.getenv("ANCHOR_API_KEY")) session = anchor_client.sessions.create( # Optional: ad blocking is enabled by default, so this configuration is not required browser={ "adblock": { "active": True # Set to False to disable ad blocking } } ) print("Session:", session.data.id) ``` ## Disabling Ad Blocker To disable ad blocking for a session, set `active: false` in the adblock configuration: ```javascript node.js theme={null} import AnchorBrowser from 'anchorbrowser'; (async () => { const anchorClient = new AnchorBrowser({apiKey: process.env.ANCHOR_API_KEY}); const session = await anchorClient.sessions.create({ browser: { adblock: { active: false // Disables ad blocking for this session } } }); console.log("Session:", session.data.id); })().catch(console.error); ``` ```python python theme={null} import os from anchorbrowser import Anchorbrowser anchor_client = Anchorbrowser(api_key=os.getenv("ANCHOR_API_KEY")) session = anchor_client.sessions.create( browser={ "adblock": { "active": False # Disables ad blocking for this session } } ) print("Session:", session.data.id) ``` ## Related Features * [Popup Blocker](/advanced/popup-blocker) - Block cookie banners and consent dialogs * [Captcha Solving](/advanced/captcha-solving) - Solve CAPTCHAs that may appear when ad blocking is detected # Anchor VPN Source: https://docs.anchorbrowser.io/advanced/anchor-vpn Integrated VPN egress that is secure, reliable, and fast - beyond what proxy setups can offer. Anchor VPN routes all outbound browser traffic through Anchor's own VPN infrastructure, giving each session a consistent egress path and a stable, dedicated client IP. Unlike commodity proxy setups, it is built to be **secure**, **reliable**, and **fast** - because the path is owned and operated by Anchor end-to-end. | **Anchor VPN** | **Market Standard** | | :------------------------------------ | :--------------------------------------------------------------- | | ✅ Always get the same IP address | 🔴 Randomized IP addresses | | ✅ High speed, cloud hosted IPs | 🔴 Variable speed, hosted on personal edge devices | | ✅ The IP address only belongs to you | 🔴 IP address is used by unkown amount of users at the same time | | ✅ Used only for enterprise automation | 🔴 Used for scraping, bot operation, malicious activity | | ✅ Built for enterprise AI workloads | 🔴 Built for scraping, retrofitted for AI | Anchor VPN is a premium feature enabled by the Anchor team upon request. It can be scoped to an entire project or to a specific identity. [Contact the Anchor team](mailto:support@anchorbrowser.io?subject=Anchor%20VPN%20enablement) to get started. ## Why it matters Unstable IPs, low-reputation shared exits, and slow proxy hops are the root cause of most browser automation failures at scale. They lead to session drops, failed auth, elevated risk scores, and sluggish page loads - all of which compound when you're running many sessions. Anchor VPN addresses all three at once. ## Secure Your assigned egress IP is used by you alone. It is not shared with other customers in a multi-tenant pool where unrelated traffic degrades the address's reputation and broadens the blast radius of any incident. There is no third-party proxy operator sitting between the browser and the internet. Traffic stays on Anchor-operated infrastructure under Anchor's security program, so no outside vendor can observe, log, or mishandle your sessions. ## Reliable Which egress IP you use - and whether the same IP is reused across sessions - is driven by your Anchor VPN configuration. Once an assignment is active, all HTTP(S) traffic follows it. There is no routine mid-session rotation for load-spreading; downstream sites see a steady origin for the lifetime of the assignment. Exits are health- and latency-monitored. If a node goes unhealthy, failover is policy-driven and designed to minimize geographic jumps rather than silently dumping you onto a random replacement. ## Fast Traffic leaves the browser through exits provisioned for Anchor sessions - not through oversubscribed shared proxy endpoints that add latency on every request. The stack is tuned for interactive pages and APIs: TLS optimization, connection reuse, and direct peering. Because the IP stays stable, you also avoid the hidden performance tax of proxy churn: fewer captchas, fewer re-auth flows, and less wall-clock time lost to recovery loops that fire every time the origin IP changes. ## How it works Consistent routing is applied at the browser networking layer before requests are dispatched. | Component | Role | | ------------------------- | ----------------------------------------------------------------------------------------------------------- | | **Session router** | Applies your VPN configuration - selecting an exit and, where enabled, reusing an egress IP across sessions | | **Exit node pool** | Regionally grouped endpoints monitored for health and latency | | **Routing policy engine** | Enforces assignment rules, stickiness settings, and failover behavior | Behavior depends on how Anchor VPN is configured for your project or identity. The system is designed to preserve geographic coherence, avoid gratuitous IP rotation, and keep a stable client origin for as long as your configuration dictates. ## Get started Anchor VPN is enabled by the Anchor team upon request. [Contact us](mailto:support@anchorbrowser.io?subject=Anchor%20VPN%20enablement) to enable it for your project or identity. ## Related capabilities Country, region, and city targeting with Anchor's built-in proxy. Route sessions through your own HTTP, HTTPS, or SOCKS5 server. A fixed IP reserved per profile across sessions. # Async execution Source: https://docs.anchorbrowser.io/advanced/async-automation-tasks Run automation tasks asynchronously and poll for results. By default, task execution is **synchronous** — the API call waits for the task to complete before returning results. For long-running tasks, you can use **asynchronous execution** to start the task and check results later. ## Running tasks asynchronously When you set `async: true`, the API returns immediately with a confirmation that the task has started. You can then poll for execution results using the task execution history endpoint. ```typescript node.js theme={null} // Run the task asynchronously const execution = await client.task.run({ taskId: taskId, version: '1', async: true, // Enable async execution inputs: { ANCHOR_TARGET_URL: 'https://example.com', ANCHOR_MAX_PAGES: '10' } }); console.log('Task execution started:', execution.data); // Response: { async: true, success: true, message: 'Task execution started', taskId: '...' } ``` ```python python theme={null} # Run the task asynchronously execution = client.task.run( task_id=task_id, version="1", async_=True, # Enable async execution inputs={ "ANCHOR_TARGET_URL": "https://example.com", "ANCHOR_MAX_PAGES": "10" } ) print(f"Task execution started: {execution.data}") # Response: {'async': True, 'success': True, 'message': 'Task execution started', 'taskId': '...'} ``` ## Checking execution results After starting an async task, you can check its execution status and results by querying the task's execution history endpoint: ```typescript node.js theme={null} // Get execution results const response = await fetch(`https://api.anchorbrowser.io/v1/task/${taskId}/executions?page=1&limit=1&version=1`, { headers: { 'anchor-api-key': process.env.ANCHORBROWSER_API_KEY, 'Content-Type': 'application/json' } }); const results = await response.json(); console.log('Execution results:', results.data); ``` ```python python theme={null} import os import requests # Get execution results response = requests.get( f"https://api.anchorbrowser.io/v1/task/{task_id}/executions", params={ # "status": "success", #Optional: filter by status (success, failure, timeout, cancelled) "page": 1, "limit": 1, "version": "1" # Optional: filter by version }, headers={ "anchor-api-key": os.environ.get("ANCHORBROWSER_API_KEY"), "Content-Type": "application/json" } ) results = response.json() print(f"Execution results: {results['data']}") ``` **Example response:** ```json theme={null} { "data": { "results": [ { "id": "550e8400-e29b-41d4-a716-446655440000", "status": "success", "executionTime": 5234, "output": "{\"success\": true, \"message\": \"Task completed successfully\"}", "errorMessage": null, "startTime": "2024-01-15T10:30:00Z" } ], "pagination": { "page": 1, "limit": 1, "total": 1, "totalPages": 1 } } } ``` Async task executions have a maximum duration of **3 hours**. Tasks that exceed this limit will be automatically cancelled. **Note:** Tasks must be deployed (not just in draft) to appear in the execution results list. ## Polling for results For async tasks, you can implement polling to wait for completion: ```typescript node.js theme={null} // Poll for task completion async function waitForTaskCompletion(taskId: string, maxAttempts: number = 60) { for (let i = 0; i < maxAttempts; i++) { const response = await fetch(`https://api.anchorbrowser.io/v1/task/${taskId}/executions?page=1&limit=1`, { headers: { 'anchor-api-key': process.env.ANCHORBROWSER_API_KEY, 'Content-Type': 'application/json' } }); const results = await response.json(); const latestResult = results.data?.results?.[0]; if (latestResult) { if (latestResult.status === 'success' || latestResult.status === 'failure') { return latestResult; } } // Wait 2 seconds before next poll await new Promise(resolve => setTimeout(resolve, 2000)); } throw new Error('Task execution timeout'); } // Usage const execution = await client.task.run({ taskId: taskId, version: '1', async: true, inputs: { ANCHOR_TARGET_URL: 'https://example.com' } }); const result = await waitForTaskCompletion(taskId); console.log('Task completed:', result); ``` ```python python theme={null} import os import time import requests # Poll for task completion def wait_for_task_completion(task_id: str, max_attempts: int = 60): for _ in range(max_attempts): response = requests.get( f"https://api.anchorbrowser.io/v1/task/{task_id}/executions", params={"page": 1, "limit": 1}, headers={ "anchor-api-key": os.environ.get("ANCHORBROWSER_API_KEY"), "Content-Type": "application/json" } ) results = response.json() latest_result = results['data']['results'][0] if results['data']['results'] else None if latest_result: if latest_result['status'] in ['success', 'failure']: return latest_result # Wait 2 seconds before next poll time.sleep(2) raise Exception('Task execution timeout') # Usage execution = client.task.run( task_id=task_id, version="1", async_=True, inputs={"ANCHOR_TARGET_URL": "https://example.com"} ) result = wait_for_task_completion(task_id) print(f"Task completed: {result}") ``` # Batch Browser Sessions Source: https://docs.anchorbrowser.io/advanced/batch-browser-sessions Create and manage multiple browser sessions simultaneously for large-scale automation tasks For a single session use [Create a Session](/quickstart/create-session). See [Browser Sessions](/quickstart/browser-sessions#how-to-create) for when to choose batch vs sync or async. Create up to 5,000 browser sessions in a single API call for large-scale automation, web scraping, and load testing. ## Quick Start ### 1. Create a Batch ```javascript theme={null} const response = await fetch('https://api.anchorbrowser.io/v1/batch-sessions', { method: 'POST', headers: { 'anchor-api-key': process.env.ANCHOR_API_KEY, 'Content-Type': 'application/json' }, body: JSON.stringify({ count: 10, configuration: { browser: { headless: { active: true }, viewport: { width: 1440, height: 900 } }, session: { timeout: { idle_timeout: 10, max_duration: 300 } } }, metadata: { project: 'web-scraping' } }) }); const batch = await response.json(); const batchId = batch.data.batch_id; console.log('Batch ID:', batchId); ``` ### 2. Monitor Progress ```javascript theme={null} const response = await fetch(`https://api.anchorbrowser.io/v1/batch-sessions/${batchId}`, { headers: { 'anchor-api-key': process.env.ANCHOR_API_KEY } }); const status = await response.json(); console.log(`Progress: ${status.data.progress.percentage}%`); console.log(`Completed: ${status.data.completed_requests}/${status.data.total_requests}`); ``` ### 3. Use Sessions ```javascript theme={null} import { chromium } from 'playwright'; const sessions = status.data.sessions.filter(s => s.status === 'completed'); for (const session of sessions) { const browser = await chromium.connectOverCDP(session.cdp_url); const page = await browser.contexts()[0].newPage(); await page.goto('https://example.com'); // ... your automation logic await browser.close(); } ``` ## Polling Strategy ```javascript theme={null} async function waitForBatchCompletion(batchId, maxWaitMinutes = 10) { const maxWaitMs = maxWaitMinutes * 60 * 1000; let checkInterval = 5000; const startTime = Date.now(); while (Date.now() - startTime < maxWaitMs) { const response = await fetch(`https://api.anchorbrowser.io/v1/batch-sessions/${batchId}`, { headers: { 'anchor-api-key': process.env.ANCHOR_API_KEY } }); const data = await response.json(); if (data.data.status === 'completed') return data.data.sessions; if (data.data.status === 'failed') throw new Error(`Batch failed: ${data.data.error}`); await new Promise(resolve => setTimeout(resolve, checkInterval)); checkInterval = Math.min(checkInterval * 1.5, 30000); } throw new Error(`Batch did not complete within ${maxWaitMinutes} minutes`); } ``` ## Parameters Number of browser sessions to create (1-1000) Session configuration that applies to all sessions in the batch Optional key-value pairs for batch identification ## Status States **Batch States**: `pending` → `processing` → `completed` / `failed` **Session States**: `pending` → `processing` → `completed` / `failed` ## Available Endpoints * `POST /v1/batch-sessions` - Create batch sessions * `GET /v1/batch-sessions/{batch_id}` - Get batch status Additional endpoints for listing, canceling, and retrying batches are planned for future releases. ## Limits * **Maximum batch size**: 1,000 sessions * **Session lifetime**: Up to 24 hours * Large batches may take several minutes to provision # Bring Your Own Keys (BYOK) Source: https://docs.anchorbrowser.io/advanced/bring-your-own-key Use your own LLM provider keys to power AI-driven browser automations Bring Your Own Keys (BYOK) lets you connect your own LLM provider - OpenAI, Anthropic, Google, Azure, or any OpenAI-compatible endpoint - to power Anchor's AI browser automations. You store your provider keys on a project, then **opt in per request** by referencing a key. When you don't reference a key, Anchor uses its default model as usual. **Why use BYOK?** * **Model choice** - pick the provider that works best for each task * **Data privacy** - LLM requests go directly to your provider, not through Anchor's inference layer * **Rate limits** - manage your own provider rate limits ## How it works * You can store **one or more keys per project**. Each key has a **key name** (a short slug you choose, e.g. `my-anthropic-key`) so you can keep several keys - even for the same provider - side by side. * BYOK is **opt-in per request**. A session or web task uses one of your keys only when it explicitly passes that key's name. Otherwise it uses Anchor's default model. * Your key isn't tied to a single model. The model is chosen **per request** (you can pass a model name), and if you don't pass one, the provider's default for that agent is used. * Your keys are **encrypted at rest** and never returned by the API - only a reference to them is stored. BYOK currently supports **`perform-web-task`** and **sessions** - not Tasks. If you need BYOK for Tasks, [contact support](mailto:support@anchorbrowser.io). ## Supported providers | Provider | API value | Example models | | -------------------------- | ----------- | --------------------------------------------------------- | | OpenAI | `openai` | gpt-4o, gpt-5.4, o3 | | Anthropic | `anthropic` | claude-sonnet-4-6, claude-opus-4-7 | | Google (AI Studio) | `google` | gemini-2.5-pro, gemini-2.5-flash | | Google Vertex AI | `vertex` | gemini-2.5-pro, gemini-2.5-flash | | Azure OpenAI | `azure` | Any deployed Azure OpenAI model | | Custom / OpenAI-compatible | `custom` | Any endpoint that follows the OpenAI chat completions API | Azure and custom endpoints require a **base URL**. Vertex AI uses a **service-account JSON** (not an API key) and an optional **region**. ## Setup (Dashboard) In the Dashboard, go to **Project Settings** and scroll to the **BYOK** section (below Members). 1. Click **Add Key** 2. Select your provider 3. Give the key a **key name** (e.g. `my-anthropic-key`) - this is how you'll reference it later 4. Paste your API key (or service-account JSON for Vertex) 5. For Azure or custom endpoints, enter the **base URL**; for Vertex, optionally set a **region** 6. Click **Verify & Save** - Anchor makes a quick live test call to confirm the credential works before storing it Repeat for any other providers or keys you want available in this project. They'll appear in a table, sorted by provider. ## Using BYOK Reference a key by its **key name** (`key_slug`) on the request. This works in two places: ### On a session Pass `key_slug` when creating a session. Every action in that session then uses your key: ```json theme={null} POST /v1/sessions { "key_slug": "my-anthropic-key" } ``` Optional fields: * `provider` - a safety check; if set, it must match the stored provider for that key (guards against typos). * `model` - a specific model name to use; omit it to use the provider's default. ```json theme={null} POST /v1/sessions { "key_slug": "my-anthropic-key", "provider": "anthropic", "model": "claude-sonnet-4-6" } ``` ### On a web task Pass `key_slug` to `perform-web-task`. If you don't pass a `sessionId`, Anchor creates a session on the fly using your key. If you pass an existing `sessionId`, the key applies **just to that task** - it doesn't change the session's saved model. ```json theme={null} POST /v1/tools/perform-web-task { "prompt": "Find the price of the product on this page", "key_slug": "my-openai-key" } ``` > **No key\_slug = Anchor default.** Leave `key_slug` out and the request uses Anchor's default model. ## Registering keys via the REST API You can also manage keys without the Dashboard. **When creating a project** - include a `model` object: ```json theme={null} POST /v1/projects { "name": "My project", "model": { "provider": "openai", "key_slug": "my-openai-key", "credentials": "sk-...", "base_url": null } } ``` **Adding or updating a key on an existing project:** ```json theme={null} PUT /v1/projects/{projectId}/byom { "model": { "provider": "anthropic", "key_slug": "my-anthropic-key", "credentials": "sk-ant-..." } } ``` In both cases Anchor verifies the credential with a live test call before saving. `base_url` is required for `azure` and `custom`; for `vertex`, put the service-account JSON in `credentials` and optionally add `location`. ## FAQs No. BYOK is opt-in: a request uses your key only when it includes `key_slug`. Anything without it uses Anchor's default model. Not currently. BYOK applies to sessions and `perform-web-task` only. If you need BYOK for Tasks, [contact support](mailto:support@anchorbrowser.io). Yes. Each key has its own key name, and you can store several — even for the same provider. You pick which one to use per request. The one you pass as `model` on the request. If you don't pass a model, the provider's default for that agent is used. Make sure your key has access to whichever model ends up being used — for example, computer-use agents need a computer-use-capable model. With BYOK, LLM requests go directly to your provider, so you pay your provider for token usage. Anchor's standard browser usage charges still apply. Yes. Keys are encrypted at rest and never returned by the API — only a reference is stored. You re-enter the key whenever you change a configuration. # Bring Your Own Proxy Source: https://docs.anchorbrowser.io/advanced/bring-your-own-proxy Use your own HTTP, HTTPS, or SOCKS5 proxy servers with Anchor Browser sessions. Anchor Browser lets you use your own proxy servers, giving you complete control over your proxy infrastructure. This is particularly useful when you have existing proxy solutions or need to comply with specific network policies. ## Configuration To use your own proxy, set `type` to `"custom"` and provide: * **`active`**: Set to `true` to enable the proxy * **`type`**: Set to `"custom"` to indicate you're using your own proxy * **`server`**: The full proxy URL in the format `protocol://hostname:port` * **`username`**: Your proxy authentication username (if required) * **`password`**: Your proxy authentication password (if required) ## Supported protocols Anchor Browser supports the following proxy protocols: * **HTTP Proxy**: Standard HTTP proxy with optional authentication (e.g., `http://proxy.example.com:8080`) * **HTTPS Proxy**: Secure HTTPS proxy connections (e.g., `https://proxy.example.com:443`) * **SOCKS5 Proxy**: SOCKS5 proxy for enhanced privacy and flexibility (e.g., `socks5://proxy.example.com:1080`) ## Examples ```javascript node.js theme={null} import AnchorBrowser from 'anchorbrowser'; const anchorClient = new AnchorBrowser({apiKey: process.env.ANCHOR_API_KEY}); const response = await anchorClient.sessions.create({ session: { proxy: { active: true, type: 'custom', server: 'https://proxy.example.com:443', // Supported protocols: http, https, socks5 username: 'myUser', password: 'myPassword', } } }); console.log('Session created:', response.data); ``` ```python python theme={null} import os import json from anchorbrowser import Anchorbrowser anchor_client = Anchorbrowser(api_key=os.getenv('ANCHOR_API_KEY')) response = anchor_client.sessions.create( session={ 'proxy': { 'active': True, 'type': 'custom', 'server': 'https://proxy.example.com:443', # Supported protocols: http, https, socks5 'username': 'myUser', 'password': 'myPassword', } } ) print('Session created:') print(response.data) ``` For Anchor's built-in proxy with country, region, and city targeting, see [Proxy](/advanced/proxy). # Embedded Browser Live UI Source: https://docs.anchorbrowser.io/advanced/browser-live-view Embed interactive browser sessions directly into your application ## Overview Anchor Browser offers a live view feature that allows you to embed an interactive frame of a website as a web element. The `live_view_url` is received when creating a session. ## Headful Mode (Default) Headful mode provides a single URL to view the full chrome view, including the address bar. This ensures the presented tab is always the active tab and provides the best user experience. Browser Live View in Headful Mode To create a browser in headful mode, [create a session](/quickstart/create-session) with `browser.headless.active` set to `false` (the default): ```javascript node.js theme={null} import AnchorBrowser from 'anchorbrowser'; // Initialize the client const client = new AnchorBrowser({ apiKey: process.env.ANCHOR_API_KEY }); // For explicit headfull session configuration (optional, default to false) const config = { browser: { headless: { active: false } } }; const session = await client.sessions.create(config); const liveViewUrl = session.data.live_view_url; console.log(`Live view URL: ${liveViewUrl}`); ``` ```python python theme={null} from anchorbrowser import Anchorbrowser import os # Initialize the client client = Anchorbrowser(api_key=os.getenv("ANCHOR_API_KEY")) # For explicit headfull session configuration (optional, default to False) config = { "headless": { "active": False } } session = client.sessions.create(browser=config) print(f"Live view URL: {session.data.live_view_url}") ``` Then, use the `live_view_url` from the response to embed the live view directly into an iframe: ```html theme={null} ``` ## Advanced Embedding Configuration ### Embed in Fullscreen View (Hide Navigation Bar) To use the fullscreen view, replace the live view URL with the following: ```html theme={null} ``` ### Disable Browser Interactivity To prevent the end user from interacting with the browser, add the `style="pointer-events: none;"` attribute to the iframe: ```html theme={null} ``` This feature is available for both headful and headless modes. ### Single-Use Live View URL To generate a live view URL that can only be used once, set `one_time_url` to `true`. After the first viewer connects and disconnects, the URL becomes permanently invalid. ```javascript node.js theme={null} import AnchorBrowser from 'anchorbrowser'; const client = new AnchorBrowser({ apiKey: process.env.ANCHOR_API_KEY }); const session = await client.sessions.create({ session: { live_view: { one_time_url: true } } }); const liveViewUrl = session.data.live_view_url; console.log(`Single-use live view URL: ${liveViewUrl}`); ``` ```python python theme={null} from anchorbrowser import Anchorbrowser import os client = Anchorbrowser(api_key=os.getenv("ANCHOR_API_KEY")) session = client.sessions.create( session={ "live_view": { "one_time_url": True } } ) print(f"Single-use live view URL: {session.data.live_view_url}") ``` `one_time_url` requires a headful browser. It cannot be used with headless mode. ## Headless Mode To obtain the browser live session URL in headless mode, start by [creating a session](/api-reference/browser-sessions/start-browser-session): ```javascript node.js theme={null} import AnchorBrowser from 'anchorbrowser'; // Initialize the client const client = new AnchorBrowser({ apiKey: process.env.ANCHOR_API_KEY }); const config = { browser: { headless: { active: true } } }; const session = await client.sessions.create(config); const liveViewUrl = session.data.live_view_url; console.log(`Live view URL: ${liveViewUrl}`); ``` ```python python theme={null} from anchorbrowser import Anchorbrowser import os # Initialize the client client = Anchorbrowser(api_key=os.getenv("ANCHOR_API_KEY")) config = { "headless": { "active": True } } session = client.sessions.create(browser=config) print(f"Live view URL: {session.data.live_view_url}") ``` Browser Live View in Headless Mode The live\_view\_url currently points to the browser default first page. Then, use the **create-session** response to embed the live view URL directly into an iframe: ```html theme={null} ``` # CA Certificates Source: https://docs.anchorbrowser.io/advanced/ca-certificates Install custom CA certificates to trust internal services and private PKI in browser sessions. ## Common use cases | Use case | Description | | ---------------------------- | ------------------------------------------------------------------------------------------- | | **Internal services** | Access private dashboards and endpoints using certificates signed by your organization's CA | | **Dev/staging environments** | Connect to environments with self-signed or private CA certificates | | **mTLS authentication** | Validate server certificate chains in mutual TLS setups | | **QA/testing** | Run automated tests with temporary CAs or simulated certificate chains | | **Enterprise networks** | Support environments with custom trust policies or air-gapped infrastructure | ## Managing certificates Certificates are managed at the team level. Upload a certificate once, then reference it by name in any session. ### Upload a certificate ```javascript node.js theme={null} import AnchorBrowser from 'anchorbrowser'; import fs from 'fs'; const anchorClient = new AnchorBrowser({ apiKey: process.env.ANCHOR_API_KEY }); const certificate = await anchorClient.post('/v1/certificates', { body: { file: fs.createReadStream('/path/to/your-ca.crt'), name: 'my-internal-ca', description: 'CA for internal services', }, }); console.log('Certificate uploaded:', certificate); ``` ```python python theme={null} import os from anchorbrowser import Anchorbrowser anchor_client = Anchorbrowser(api_key=os.getenv('ANCHOR_API_KEY')) with open('/path/to/your-ca.crt', 'rb') as f: certificate = anchor_client.post( '/v1/certificates', body={ 'file': f, 'name': 'my-internal-ca', 'description': 'CA for internal services', }, ) print('Certificate uploaded:', certificate) ``` | Parameter | Required | Description | | ------------- | -------- | ------------------------------------------------------ | | `file` | Yes | Certificate file (`.crt`, `.pem`, `.cer`, `.der`) | | `name` | Yes | Unique identifier (alphanumeric, hyphens, underscores) | | `description` | No | Optional description (max 1000 characters) | ### List certificates ```javascript node.js theme={null} import AnchorBrowser from 'anchorbrowser'; const anchorClient = new AnchorBrowser({ apiKey: process.env.ANCHOR_API_KEY }); const certificates = await anchorClient.get('/v1/certificates'); console.log('Certificates:', certificates); ``` ```python python theme={null} import os from anchorbrowser import Anchorbrowser anchor_client = Anchorbrowser(api_key=os.getenv('ANCHOR_API_KEY')) certificates = anchor_client.get('/v1/certificates') print('Certificates:', certificates) ``` ### Delete a certificate ```javascript node.js theme={null} import AnchorBrowser from 'anchorbrowser'; const anchorClient = new AnchorBrowser({ apiKey: process.env.ANCHOR_API_KEY }); const response = await anchorClient.delete('/v1/certificates/my-internal-ca'); console.log('Deleted:', response); ``` ```python python theme={null} import os from anchorbrowser import Anchorbrowser anchor_client = Anchorbrowser(api_key=os.getenv('ANCHOR_API_KEY')) response = anchor_client.delete('/v1/certificates/my-internal-ca') print('Deleted:', response) ``` ## Using a certificate After uploading a certificate, reference it by name when creating a session: ```javascript node.js theme={null} import AnchorBrowser from 'anchorbrowser'; const anchorClient = new AnchorBrowser({ apiKey: process.env.ANCHOR_API_KEY }); const session = await anchorClient.sessions.create({ browser: { ca_cert: { active: true, name: 'my-internal-ca', }, }, }); console.log('Session:', session.data.id); ``` ```python python theme={null} import os from anchorbrowser import Anchorbrowser anchor_client = Anchorbrowser(api_key=os.getenv('ANCHOR_API_KEY')) session = anchor_client.sessions.create( browser={ 'ca_cert': { 'active': True, 'name': 'my-internal-ca', }, }, ) print('Session:', session.data.id) ``` ## Related capabilities Route traffic through proxies for network control. Persistent identity for seamless access to applications. # Captcha Solving Source: https://docs.anchorbrowser.io/advanced/captcha-solving ### Visual CAPTCHA solving Anchor browser solves CAPTCHA challenges using a vision-based approach, along with extension-based fallbacks. The vision-based approach imitates human behavior to solve any CAPTCHA (including Cloudflare) without multiple challenges. CAPTCHA solving requires either an active proxy or a configured browser profile (the profile may carry its own egress IP and stable identity). For the full list of available options, view the [interactive api documentation](/api-reference) ### Enable CAPTCHA solving ```javascript node.js theme={null} import Anchorbrowser from 'anchorbrowser'; (async () => { const anchorClient = new Anchorbrowser({apiKey: process.env.ANCHOR_API_KEY}); const session = await anchorClient.sessions.create({ browser: { captcha_solver: { active: true } }, session: { proxy: { active: true // Required } } }); console.log("Session created with CAPTCHA solver:", session.data.id); })().catch(console.error); ``` ```python python theme={null} import os from anchorbrowser import Anchorbrowser anchor_client = Anchorbrowser(api_key=os.getenv("ANCHOR_API_KEY")) session = anchor_client.sessions.create( browser= { "captcha_solver": { "active": True } }, session= { "proxy": { "active": true # Required } } ) print("Session created with CAPTCHA solver:", session.data.id) ``` #### Configure Text-based CAPTCHA solving ```javascript node.js theme={null} import AnchorBrowser from 'anchorbrowser'; (async () => { const anchorClient = new AnchorBrowser({apiKey: process.env.ANCHOR_API_KEY}); const session = await anchorClient.sessions.create({ browser: { captcha_solver: { active: true, image_selector: 'ol_capcha img', input_selector: 'ol-captcha input' } }, session: { proxy: { active: true // Required } } }); console.log("Session created with text-based CAPTCHA solver:", session.data.id); })().catch(console.error); ``` ```python python theme={null} import os from anchorbrowser import Anchorbrowser anchor_client = Anchorbrowser(api_key=os.getenv("ANCHOR_API_KEY")) session = anchor_client.sessions.create( browser={ "captcha_solver": { "active": True, "image_selector": 'ol_capcha img', "input_selector": 'ol-captcha input' } }, session= { "proxy": { "active": true # Required } } ) print("Session created with text-based CAPTCHA solver:", session.data.id) ``` # Cloudflare Web Bot Auth Source: https://docs.anchorbrowser.io/advanced/cloudflare-web-bot-auth Authenticate browser sessions with Cloudflare Web Bot Auth Anchor Browser supports Cloudflare Web Bot Auth HTTP message signing for browser sessions. This allows you to identify as Anchor Browser to websites that require Cloudflare's web bot authentication, enabling access to protected content and avoiding bot detection. WebBotAuth.io Test ## How It Works 1. **Session Creation**: Create a browser session with web bot auth enabled 2. **HTTP Message Signing**: All HTTP requests are automatically signed as Anchor Browser 3. **Authentication**: Cloudflare validates the signatures 4. **Access Granted**: Successfully authenticated requests can access protected content ## Using Web Bot Auth ### How Authentication Works When you enable web bot auth, Anchor Browser automatically identifies all HTTP requests to websites using our registered identity. This allows you to access protected content that requires Cloudflare's web bot authentication without any additional configuration. ### Browser Configuration ```typescript theme={null} { "browser": { "web_bot_auth": { "active": boolean // Enable/disable web bot auth (default: false) } } } ``` ### SDK Examples Enable web bot auth by setting the `web_bot_auth.active` flag to `true` in your session configuration: ```python python theme={null} from anchorbrowser import Anchorbrowser import os # Initialize the client client = Anchorbrowser(api_key=os.getenv("ANCHOR_API_KEY")) # Session configuration with web bot auth enabled config = { "web_bot_auth": { "active": True } } # Create session with web bot auth session = client.sessions.create(browser=config) print(f"Session created: {session.data.id}") print(f"CDP URL: {session.data.cdp_url}") print(f"Live view URL: {session.data.live_view_url}") ``` ```javascript node.js theme={null} import AnchorBrowser from 'anchorbrowser'; // Initialize the client const client = new AnchorBrowser({ apiKey: process.env.ANCHOR_API_KEY }); // Session configuration with web bot auth enabled const config = { browser: { web_bot_auth: { active: true } } }; // Create session with web bot auth const session = await client.sessions.create(config); console.log('Session created:', session); ``` ## Testing You can test your web bot auth configuration by visiting [https://webbotauth.io/test](https://webbotauth.io/test) in a browser session with web bot auth enabled. This site will show you whether your requests are being properly signed and authenticated. ## Read More * [Cloudflare Verified Bots Blog](https://blog.cloudflare.com/verified-bots-with-cryptography/) * [HTTP Message Signatures (RFC 9421)](https://datatracker.ietf.org/doc/html/rfc9421) * [Web Bot Auth IETF Draft](https://datatracker.ietf.org/doc/html/draft-meunier-web-bot-auth-architecture) * [WebBotAuth.io](https://webbotauth.io) # Dedicated Sticky IP Source: https://docs.anchorbrowser.io/advanced/dedicated-sticky-ip Reserve a fixed IP address for a specific profile. A **Dedicated Sticky IP** ensures that a specific profile uses by default the same IP address, reserved exclusively for that profile. This is helpful when IP consistency is required across sessions. [Extra Stealth](/essentials/stealth) mode is automatically enabled for all sessions using a Dedicated Sticky IP profile. Use the [Create Profile API](https://docs.anchorbrowser.io/api-reference/profiles/create-profile?playground=open) to create a profile with a dedicated sticky IP by setting `dedicated_sticky_ip` to `true`: ```javascript node.js theme={null} import AnchorBrowser from 'anchorbrowser'; const anchorClient = new AnchorBrowser({ apiKey: process.env.ANCHOR_API_KEY }); const profile = await anchorClient.profiles.create({ name: 'my-sticky-profile', dedicated_sticky_ip: true, }); console.log('Profile created:', profile.data); ``` ```python python theme={null} from anchorbrowser import Anchorbrowser import os anchor_client = Anchorbrowser(api_key=os.getenv("ANCHOR_API_KEY")) profile = anchor_client.profiles.create( name="my-sticky-profile", dedicated_sticky_ip=True, ) print("Profile created:", profile.data) ``` This allocates a dedicated IP that is not shared with other profiles. Start a browser session with the profile. Set `persist` to `true` so the browser state (cookies, localStorage, etc.) is saved when the session ends. ```javascript node.js theme={null} import AnchorBrowser from 'anchorbrowser'; const anchorClient = new AnchorBrowser({ apiKey: process.env.ANCHOR_API_KEY }); const session = await anchorClient.sessions.create({ browser: { profile: { name: 'my-sticky-profile', persist: true, }, }, }); console.log('Session created:', session.data); ``` ```python python theme={null} from anchorbrowser import Anchorbrowser import os anchor_client = Anchorbrowser(api_key=os.getenv("ANCHOR_API_KEY")) session = anchor_client.sessions.create( browser={ "profile": { "name": "my-sticky-profile", "persist": True, } } ) print("Session created:", session.data) ``` The session will automatically use the reserved sticky IP and Extra Stealth mode. Connect to the session via live view, CDP, or the session view in the dashboard. Log in to accounts, configure settings, or perform any actions you need. When you're done, close the session. The profile state (cookies, localStorage, etc.) will be persisted automatically because `persist` was set to `true`. Any new session started with this profile will use the same dedicated sticky IP and have all the saved browser state from previous sessions. To override the default sticky IP, set the `proxy` field when using the [Start Browser Session API](https://docs.anchorbrowser.io/api-reference/browser-sessions/start-browser-session). See [Bring Your Own Proxy](/advanced/bring-your-own-proxy) for configuration details. # Demonstrations API Source: https://docs.anchorbrowser.io/advanced/demonstrations-api Send a shareable link so someone can demonstrate a task and Anchor generates production-ready automation from it. Send a shareable link so someone can demonstrate a task in a live browser. Anchor captures every event and compiles it into deterministic, repeatable code with an AI fallback — the same outcome as [creating a task from a demonstration](/tasks/creating-a-task#create-from-a-demonstration) in the UI. Use this when the person who knows the flow isn't at the keyboard in your Anchor workspace. ## How it works 1. **Create** a demonstration session via API — you get back a `share_url` 2. **Send** the link to the person who will demonstrate the task 3. They open it, see a live browser, and **perform the task** 4. They click **Finish Demonstration** — the demonstration is submitted 5. Anchor **compiles the captured events** into a task 6. **Poll** the status endpoint until it reaches `completed` — you get `task_id` and `tool_id` ## API Endpoints ### Create a demonstration ```javascript node.js theme={null} import AnchorBrowser from 'anchorbrowser'; const anchorClient = new AnchorBrowser({ apiKey: process.env.ANCHOR_API_KEY }); const demo = await anchorClient.post('/v1/demonstrations/', { body: { task_name: 'Download invoice report', task_description: 'Navigate to Reports, select the monthly invoice report, and download it as PDF', user_name: 'John', }, }); console.log('Share URL:', demo.share_url); console.log('Session ID:', demo.session_id); console.log('Task ID:', demo.task_id); ``` ```python python theme={null} import os from anchorbrowser import Anchorbrowser anchor_client = Anchorbrowser(api_key=os.getenv('ANCHOR_API_KEY')) demo = anchor_client.post( '/v1/demonstrations/', body={ 'task_name': 'Download invoice report', 'task_description': 'Navigate to Reports, select the monthly invoice report, and download it as PDF', 'user_name': 'John', }, ) print('Share URL:', demo['share_url']) print('Session ID:', demo['session_id']) print('Task ID:', demo['task_id']) ``` **Request body:** | Field | Type | Required | Description | | -------------------------- | ------- | -------- | --------------------------------------------------------------------------------------------- | | `task_name` | string | Yes | Name for the task being demonstrated | | `task_description` | string | Yes | Natural language description of what the task does — helps Anchor interpret the demonstration | | `identity_id` | string | No | Pre-authenticate the browser with a saved identity | | `identity_skip_validation` | boolean | No | Skip identity validation (default: `true`) | | `user_name` | string | No | Display name shown in the demonstration UI ("Thanks, John!") | | `session_config` | object | No | Override browser session settings (start URL, proxy, timeouts, etc.) | **Response:** ```json theme={null} { "session_id": "abc123-...", "live_view_url": "https://connect.anchorbrowser.io/...", "share_url": "https://app.anchorbrowser.io/demonstrate?token=eyJ...", "share_token": "eyJ...", "share_expires_at": "2026-04-15T09:00:00.000Z", "task_id": "task-uuid-..." } ``` The `share_url` is a signed JWT link valid for 1 hour. The customer does not need an Anchor account — the token contains the session and team context. ### Get demonstration status Poll this endpoint to track progress after the demonstration is submitted. ```javascript node.js theme={null} const status = await anchorClient.get(`/v1/demonstrations/${sessionId}`); console.log(status); ``` ```python python theme={null} status = anchor_client.get(f'/v1/demonstrations/{session_id}') print(status) ``` **Response:** ```json theme={null} { "session_id": "abc123-...", "status": "completed", "hektor_project_id": "proj-uuid-...", "task_id": "task-uuid-...", "task_version_id": "ver-uuid-...", "tool_id": "tool-uuid-...", "created_at": "2026-04-15T07:32:12.865Z" } ``` **Status values:** | Status | Description | | ------------ | ------------------------------------------------------ | | `recording` | Browser session is live — demonstration in progress | | `processing` | Demonstration complete — Anchor is compiling the task | | `completed` | Task generated — `task_id` and `tool_id` are available | | `failed` | Task generation failed | | `stopped` | Session was stopped before completing | ### Complete demonstration Called automatically when the user clicks **Finish Demonstration**. Can also be called programmatically via `POST .../recording/complete`. ```javascript node.js theme={null} const result = await anchorClient.post( `/v1/demonstrations/${sessionId}/recording/complete`, ); console.log(result); ``` ```python python theme={null} result = anchor_client.post(f'/v1/demonstrations/{session_id}/recording/complete') print(result) ``` **Response:** ```json theme={null} { "session_id": "abc123-...", "status": "processing", "hektor_project_id": "proj-uuid-...", "task_id": "task-uuid-..." } ``` This endpoint: 1. Ends the browser session and uploads the captured events 2. Waits for demonstration artifacts to be stored 3. Submits them for task generation 4. Returns immediately with `status: "processing"` ### Stop demonstration Stop a demonstration without generating a task. Useful for cleanup. ```javascript node.js theme={null} const result = await anchorClient.post(`/v1/demonstrations/${sessionId}/stop`); console.log(result); ``` ```python python theme={null} result = anchor_client.post(f'/v1/demonstrations/{session_id}/stop') print(result) ``` **Response:** ```json theme={null} { "success": true } ``` ## End-to-end example ```javascript node.js theme={null} import AnchorBrowser from 'anchorbrowser'; const anchorClient = new AnchorBrowser({ apiKey: process.env.ANCHOR_API_KEY }); // 1. Create demonstration const demo = await anchorClient.post('/v1/demonstrations/', { body: { task_name: 'Download invoice report', task_description: 'Go to Reports > Invoices, select this month, download PDF', identity_id: 'your-identity-id', user_name: 'Sarah', }, }); console.log(`Send this link to your customer: ${demo.share_url}`); const sessionId = demo.session_id; // 2. Poll until completed let status; while (true) { status = await anchorClient.get(`/v1/demonstrations/${sessionId}`); console.log(`Status: ${status.status}`); if (['completed', 'failed', 'stopped'].includes(status.status)) break; await new Promise((r) => setTimeout(r, 5000)); } // 3. Use the generated task if (status.status === 'completed') { console.log(`Task ID: ${status.task_id}`); console.log(`Tool ID: ${status.tool_id}`); } ``` ```python python theme={null} import os import time from anchorbrowser import Anchorbrowser anchor_client = Anchorbrowser(api_key=os.getenv('ANCHOR_API_KEY')) # 1. Create demonstration demo = anchor_client.post( '/v1/demonstrations/', body={ 'task_name': 'Download invoice report', 'task_description': 'Go to Reports > Invoices, select this month, download PDF', 'identity_id': 'your-identity-id', 'user_name': 'Sarah', }, ) print(f"Send this link to your customer: {demo['share_url']}") session_id = demo['session_id'] # 2. Poll until completed while True: status = anchor_client.get(f'/v1/demonstrations/{session_id}') print(f"Status: {status['status']}") if status['status'] in ('completed', 'failed', 'stopped'): break time.sleep(5) # 3. Use the generated task if status['status'] == 'completed': print(f"Task ID: {status['task_id']}") print(f"Tool ID: {status['tool_id']}") ``` ## Identity pre-authentication Pass an `identity_id` to pre-authenticate the browser before the link is opened. The user will see the target application already logged in and can start the demonstration immediately. ```json theme={null} { "task_name": "Export quarterly report", "task_description": "Navigate to the reports section and export Q1 data", "identity_id": "7f0db4ad-74ca-42e5-896e-3f0daea36c13" } ``` If identity authentication fails (expired credentials, site unreachable), the session still starts — the user can log in manually during the demonstration. Set `identity_skip_validation: true` (default) to ensure this behavior. ## Share URL and the demonstration UI The `share_url` opens a full-screen demonstration interface with: * **Live browser** — interact with the actual website * **Live Browser Events panel** — clicks, typing, and navigation as they happen * **Finish Demonstration** — submits the demonstration and starts task generation When the user finishes, they see a confirmation screen. If the share URL was opened as a popup (via `window.open`), the parent window receives a `demonstration-complete` postMessage with the `task_id`, `task_version_id`, and `tool_id`. ## Session configuration Override default session settings: ```json theme={null} { "task_name": "My task", "task_description": "Task description", "session_config": { "browser": { "start_url": "https://app.example.com/dashboard" }, "session": { "timeout": { "max_duration": 60, "idle_timeout": 30 } } } } ``` Default demonstration timeouts are `max_duration: 30` minutes and `idle_timeout: 30` minutes. # Email MFA Source: https://docs.anchorbrowser.io/advanced/email-otp Email-based MFA for identity creation, reauthentication, and agent sessions. Anchor supports one-time password (OTP) as its email MFA method. Each identity is assigned a dedicated mailbox — simply configure a forwarding rule with your email provider to route OTP codes to Anchor during identity creation, reauthentication, and agent sessions. ### Step 1: Add Email MFA as an authentication method 1. Go to the [Identities](https://app.anchorbrowser.io/applications) page and select your identity. 2. Choose **Preset authentication** — the available authentication flows will appear below. 3. Click the **Add flow** button to create a new authentication flow. 4. Enter a name for the flow and click **Add**. 5. Select the **Email MFA** checkbox under **Authentication Methods**. Include any other authentication methods needed for this login flow. 6. Click **Save Changes** in the top-right corner. ### Step 2: Create an identity that uses the Email MFA authentication method After creating the Email MFA authentication method: 1. Create a new identity. 2. In the credentials window, choose the Email MFA authentication method. 3. You will receive a dedicated mailbox (for example, `zesty-vale7820@mfa.anchorbrowser.io`). 4. Set up a forwarding rule from your mailbox to your dedicated mailbox. 5. Optionally, test the forwarding flow using the detailed instructions. ### Forwarding setup details Official help for forwarding in common mail clients: * [Gmail](https://support.google.com/mail/answer/10957) * [Outlook](https://support.microsoft.com/en-us/office/use-rules-to-automatically-forward-messages-45aa9664-4911-4f96-9663-ece42816d746) ## API Reference Use the mailbox and identity email APIs to provision an inbox, attach it to an identity, verify forwarding, and read OTP emails programmatically. All endpoints require the `anchor-api-key` header. ### Typical flow 1. Create an [authentication flow](/api-reference/applications/create-authentication-flow) that includes `email_mfa` in `methods`. 2. [Create a mailbox](/api-reference/identities/create-mailbox) to get a dedicated forwarding address. 3. [Create an identity](/api-reference/identities/create-identity) and pass the mailbox `id` as `mailboxId`, plus `authOptionId` for the flow you created. 4. [Send a probe email](/api-reference/identities/send-mailbox-probe) to verify delivery. 5. [List](/api-reference/identities/list-identity-emails) or [read](/api-reference/identities/get-identity-email) emails to retrieve OTP codes. | Method | Path | Description | | -------- | ---------------------------------------------------- | -------------------------------------------------- | | `POST` | `/v1/mailboxes` | Create a detached mailbox before identity creation | | `GET` | `/v1/mailboxes/{mailboxId}/emails` | List emails received by a mailbox | | `POST` | `/v1/mailboxes/{mailboxId}/send-probe` | Send a test email to verify forwarding | | `POST` | `/v1/identities/{identityId}/email` | Enable or return the identity's mailbox | | `GET` | `/v1/identities/{identityId}/email` | Get the identity's mailbox details | | `DELETE` | `/v1/identities/{identityId}/email` | Detach and delete the identity's mailbox | | `GET` | `/v1/identities/{identityId}/email/emails` | List emails in the identity's mailbox | | `GET` | `/v1/identities/{identityId}/email/emails/{emailId}` | Get full email content (text and HTML) | ### Example: provision mailbox and create identity ```javascript node.js theme={null} import Anchorbrowser from 'anchorbrowser'; const anchorClient = new Anchorbrowser(); // 1. Create an auth flow with Email MFA const authFlow = await anchorClient.applications.authFlows.create(applicationId, { name: 'Login with Email MFA', methods: ['username_password', 'email_mfa'], }); // 2. Create a detached mailbox const mailbox = await anchorClient.post('/v1/mailboxes', { body: {} }); console.log('Forwarding address:', mailbox.address); // 3. Create the identity and attach the mailbox const identity = await anchorClient.identities.create({ name: 'Work Account', source: 'https://example.com/login', authOptionId: authFlow.id, mailboxId: mailbox.id, credentials: [ { type: 'username_password', username: 'user@example.com', password: 'secret' }, ], }); ``` ```python python theme={null} import os from anchorbrowser import Anchorbrowser anchor_client = Anchorbrowser(api_key=os.getenv("ANCHOR_API_KEY")) auth_flow = anchor_client.applications.auth_flows.create( application_id=application_id, name="Login with Email MFA", methods=["username_password", "email_mfa"], ) mailbox = anchor_client.post("/v1/mailboxes", body={}) print("Forwarding address:", mailbox["address"]) identity = anchor_client.identities.create( name="Work Account", source="https://example.com/login", authOptionId=auth_flow.id, mailboxId=mailbox["id"], credentials=[ {"type": "username_password", "username": "user@example.com", "password": "secret"}, ], ) ``` ### Example: test forwarding and read OTP emails After setting up your email forwarding rule, send a probe to confirm delivery, then poll for incoming messages. Use the `since` query parameter to fetch only emails received after a given ISO timestamp. ```javascript node.js theme={null} const identityId = 'IDENTITY_ID'; const mailboxId = 'MAILBOX_ID'; // Send a probe email to verify forwarding await anchorClient.post(`/v1/mailboxes/${mailboxId}/send-probe`, { body: {} }); // List recent emails for the identity const since = new Date(Date.now() - 5 * 60 * 1000).toISOString(); const emails = await anchorClient.get(`/v1/identities/${identityId}/email/emails`, { query: { since }, }); // Read full content of the newest email if (emails.length > 0) { const email = await anchorClient.get( `/v1/identities/${identityId}/email/emails/${emails[0].id}`, ); console.log(email.subject, email.body_text); } ``` ```python python theme={null} import os from datetime import datetime, timedelta, timezone from anchorbrowser import Anchorbrowser anchor_client = Anchorbrowser(api_key=os.getenv('ANCHOR_API_KEY')) identity_id = 'IDENTITY_ID' mailbox_id = 'MAILBOX_ID' anchor_client.post(f'/v1/mailboxes/{mailbox_id}/send-probe', body={}) since = (datetime.now(timezone.utc) - timedelta(minutes=5)).isoformat() emails = anchor_client.get( f'/v1/identities/{identity_id}/email/emails', query={'since': since}, ) if emails: email = anchor_client.get( f'/v1/identities/{identity_id}/email/emails/{emails[0]["id"]}', ) print(email['subject'], email['body_text']) ``` During identity creation and agent sessions, Anchor automatically reads OTP codes from the mailbox. Use the list and get email endpoints when you need to handle codes in your own integration. ### Related API docs Provision a dedicated forwarding address Attach a mailbox to an existing identity Poll for forwarded OTP emails Verify forwarding is working # Browser Extensions Source: https://docs.anchorbrowser.io/advanced/extensions Upload and use custom browser extensions in your sessions Anchor allows you to upload and use Chrome extensions in your browser sessions. This lets you add ad blockers, privacy tools, or any other extension to enhance your browsing automation. For uploading, listing, and managing extensions, see the [interactive API documentation](/api-reference/extensions). ## Getting Extensions from Chrome Web Store To use extensions from the Chrome Web Store, you'll need to download and inspect their files: ### Download Extension Files 1. **Install CRX Extractor/Downloader** - Add this extension to your browser to download .zip files 2. **Navigate to the extension** you want on the Chrome Web Store 3. **Click the CRX Extractor icon** and download the .zip file 4. **Extract the ZIP** to inspect the contents ### Inspect Extension Contents Once extracted, you'll see the extension's files: * `manifest.json` - Contains extension metadata and permissions * `background.js` or `service_worker.js` - Background scripts * `content_scripts/` - Scripts that run on web pages * `popup.html` - Extension popup interface * `icons/` - Extension icons ### Repackage for Upload After inspecting (and optionally modifying) the files: 1. **Select all files and folders** in the extracted directory 2. **Create a new ZIP file** containing all the extension files 3. **Upload this ZIP** to AnchorBrowser using the SDK ## Extension Requirements Your extension ZIP file must contain a valid `manifest.json` with basic extension information like name and version. ### Example Manifest ```json theme={null} { "manifest_version": 3, "name": "My Extension", "version": "1.0.0", "description": "Extension description", "permissions": ["activeTab", "storage"], "background": { "service_worker": "background.js" }, "content_scripts": [{ "matches": [""], "js": ["content.js"] }] } ``` ## Code Example ```javascript node.js theme={null} import AnchorBrowser from 'anchorbrowser'; import fs from 'fs'; const anchor_client = new AnchorBrowser({apiKey: process.env.ANCHOR_API_KEY}); const extension = await anchor_client.extensions.upload({ file: fs.createReadStream('./my-extension.zip'), name: 'My Custom Extension' }); const extensionId = extension.data.id; console.log("ExtensionId:", extensionId); const session = await anchor_client.sessions.create({ browser: { extensions: [extensionId] } }); console.log("Session:", session); ``` ```python python theme={null} from anchorbrowser import Anchorbrowser import os anchor_client = Anchorbrowser(api_key=os.getenv("ANCHOR_API_KEY")) with open('./my-extension.zip', 'rb') as file: extension = anchor_client.extensions.upload(file=file,name='My Custom Extension') extensionId = extension.data.id print("ExtensionId:", extensionId) session = anchor_client.sessions.create(browser={ "extensions": [extensionId] }) print("Session:", session) ``` ## Limitations * Maximum extension size: 50MB per ZIP file * Extensions must be valid Chrome extensions # File Download Source: https://docs.anchorbrowser.io/advanced/file-download Anchor Browser supports two methods for downloading files during your browser sessions: 1. **Traditional Downloads**: Files are downloaded to the browser instance and then stored by Anchor for retrieval To download PDF files directly instead of viewing them in the browser, set `pdf_viewer` to `false` when creating your session. 2. **P2P Downloads**: Files are captured directly in the browser using peer-to-peer technology, available instantly without waiting for Anchor storage ## Traditional File Downloads The following examples demonstrate how to download a file using the traditional method and retrieve it from the browser session. Use the [create session](api-reference/browser-sessions/start-browser-session) API to create a new browser session. To enable automatic PDF downloads, include the following `browser` configuration: ```tsx node.js theme={null} browser: { pdf_viewer: { active: false } } ``` ```python python theme={null} "browser": { "pdf_viewer": { "active": False } } ``` Use the following example to perform a file download ```tsx node.js theme={null} await page.goto("https://browser-tests-alpha.vercel.app/api/download-test"); await Promise.all([page.waitForEvent("download"), page.locator("#download").click()]); // The download has completed ``` ```python python theme={null} await page.goto("https://browser-tests-alpha.vercel.app/api/download-test") async with page.expect_download() as download_info: await page.locator("#download").click() download = await download_info.value ``` You can retrieve the downloaded file from the browser session using the [get session downloads](/api-reference/browser-sessions/list-session-downloads) API ## P2P Downloads For real-time download notifications and direct file streaming without waiting for Anchor storage, see the [P2P Download Guide](/advanced/p2p-downloads). Files are available immediately when the download completes — no polling required. # File Upload Source: https://docs.anchorbrowser.io/advanced/file-upload Anchor Browser allows you to upload files during your browser sessions, enabling you to interact with web applications/forms that require files as input. The following examples demonstrate how to upload a file, either from your local development environment or one downloaded during the browser session. ## Using a local file ### Playwright example ```tsx node.js theme={null} await page.goto('https://browser-tests-alpha.vercel.app/api/upload-test') const input = await page.$("#fileUpload") await input.setInputFiles('/tmp/my-files/google.png'); // Reference the local file path ``` ```python python theme={null} page.goto('https://browser-tests-alpha.vercel.app/api/upload-test') input = page.locator("#fileUpload") input.set_input_files('/tmp/my-files/google.png') # Reference the local file path ``` # Code Tasks Source: https://docs.anchorbrowser.io/advanced/legacy-tasks Write task automation as TypeScript, or hand-edit workflow JSON for fine-grained control. Most **Automation Tasks** start from a [prompt or demonstration](/tasks/creating-a-task). **Code Tasks** are for authoring automation directly — as TypeScript you upload with the SDK, or as workflow JSON you edit by hand when you need precise control. ## Overview Code Tasks cover two related paths: | Path | When to use it | | -------------------- | ----------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- | | **TypeScript tasks** | You write a `.ts` file, upload it with the v1 Tasks SDK, and run it in Anchor's task sandbox. | | **Workflow JSON** | You already have an Automation Task (from a prompt, demonstration, or generation) and need to edit its segment graph via API — for selector fixes, branching, or changes the UI editor can't express. | For the standard create-from-prompt or demonstration flow, start with [Creating a Task](/tasks/creating-a-task) and [Run a Task](/tasks/run-a-task). Use this page when you are writing or editing code yourself. ## Writing TypeScript task code For reliable execution, **follow these guidelines:** * Write your **code in TypeScript.** * **Export** a single **default async function.** * In that function, **return** whatever your workflow requires as **output** (e.g., status, messages, domain data). Tasks can receive values as inputs. **All input names must be prefixed with `ANCHOR_`** ### Basic Task Example ```typescript typescript theme={null} import AnchorClient from 'anchorbrowser'; // Initialize the Anchor client with your API key const anchorClient = new AnchorClient({ apiKey: process.env.ANCHOR_API_KEY, }); // Export the main function as the default export export default async function run() { // Create a new browser instance const browser = await anchorClient.browser.create(); const page = browser.contexts()[0].pages()[0]; // Access input values const targetUrl = process.env.ANCHOR_TARGET_URL; const maxPages = parseInt(process.env.ANCHOR_MAX_PAGES || '10'); // Implement your automation logic await page.goto(targetUrl); console.log(`Scraping up to ${maxPages} pages from ${targetUrl}`); // Always close the browser when done await browser.close(); // Return a result object with success status and message return { success: true, message: 'Task completed successfully' }; } ``` ### Using the SDK Upload and run a TypeScript Code Task with the v1 Tasks SDK: Make sure it follows the guidelines from above. Run the script to show your base64 file version ```bash terminal theme={null} # Convert your TypeScript file to base64 base64 -i your-task.ts ``` Copy the output for later. ```typescript node.js theme={null} import Anchorbrowser from 'anchorbrowser'; const client = new Anchorbrowser({ apiKey: process.env.ANCHORBROWSER_API_KEY, }); // Create a new task const task = await client.task.create({ name: 'example-task', language: 'typescript', description: 'A task to scrape product information from e-commerce sites', code: "" // Replace with the output of the last step. }); const taskId = task.data.id console.log('Task created:', taskId); ``` ```python python theme={null} import os from anchorbrowser import Anchorbrowser client = Anchorbrowser( api_key=os.environ.get("ANCHORBROWSER_API_KEY") ) # Create a new task task = client.task.create( name="example-task", language="typescript", description="A task to scrape product information from e-commerce sites", code="" # Replace with the output of the last step. ) # Save the id for later task_id = task.data.id print(f"Task created: {task_id}") ``` ```typescript node.js theme={null} // Run the task with inputs const execution = await client.task.run({ taskId: taskId, version: 'draft', inputs: { ANCHOR_TARGET_URL: 'https://example.com', ANCHOR_MAX_PAGES: '10' } }); console.log('Task execution started:', execution.data); ``` ```python python theme={null} # Run the task with inputs execution = client.task.run( task_id=task_id, version="draft", inputs={ "ANCHOR_TARGET_URL": "https://example.com", "ANCHOR_MAX_PAGES": "10" } ) print(f"Task execution started: {execution.data}") ``` By default, the browser session is automatically closed when the task ends. Set `cleanupSessions: false` to keep the session open after task execution. Running a task with inputs ```typescript node.js theme={null} // Deploy the task to make it available for production use const deployment = await client.task.deploy({ taskId: taskId, code: "", // Replace with the base64 encoded code language: 'typescript', description: 'Optional description for this version' }); console.log('Task deployed:', deployment.data); ``` ```python python theme={null} # Deploy the task to make it available for production use deployment = client.task.deploy( task_id=task_id, code="", # Replace with the base64 encoded code language="typescript", description="Optional description for this version" ) print(f"Task deployed: {deployment.data}") ``` For long-running TypeScript task runs, see [Async execution](/advanced/async-automation-tasks). ## Support For additional help with Code Tasks: * [Run a Task](/tasks/run-a-task) — execute Automation Tasks via the v2 API * Contact Anchor Browser support at [support@anchorbrowser.io](mailto:support@anchorbrowser.io) ## Manually editing workflow JSON Use this when you already have an Automation Task and need to edit the underlying workflow document via API — for example after [self-healing](/advanced/self-healing) produces a draft, or for changes too detailed for the UI editor. See [Automation Tasks Overview](/tasks/overview#the-workflow-page) for the equivalent workflow in the dashboard. A workflow is a JSON document describing an ordered graph of **segments** that run against a browser session. Each segment can run deterministic Playwright code, an AI agent, or both. Segment outputs flow forward by parameter name into a shared state, and the workflow's final output is read from that state at the end. This section covers the JSON schema for hand-editing and the API flow for saving and publishing those edits. ### API process for editing a workflow Use the `task_id` returned when you created the task, or from a completed [demonstration](/advanced/demonstrations-api) status response. ```bash theme={null} GET /v1/task/{taskId}/latest ``` The `code` field is base64-encoded. Decode it to get the raw workflow JSON. Edit the decoded JSON using the schema reference below, re-encode it to base64, then save: ```bash theme={null} POST /v1/task/{taskId}/draft { "code": "", "language": "workflow" } ``` ```bash theme={null} POST /v2/tasks/{taskId}/publish-draft ``` ### Top-level shape ```json theme={null} { "name": "string", "startSegmentName": "string", "inputParameters": [ /* Parameter[] */ ], "outputParameters": [ /* Parameter[] */ ], "segments": [ /* Segment[] */ ] } ``` | Field | Description | | ------------------ | ------------------------------------------------------------------------------- | | `name` | Human-readable workflow name. | | `startSegmentName` | The `name` of the segment that runs first. Must match one of `segments[].name`. | | `inputParameters` | Values supplied by the caller at run time. | | `outputParameters` | Values returned at the end of the run, read from the shared state by name. | | `segments` | The segments that make up the graph. | ### Parameter ```json theme={null} { "name": "snake_case_key", "type": "string", "required": true, "description": "What this is, in plain language.", "defaultValue": null, "options": null } ``` | Field | Default | Notes | | -------------- | ------- | -------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- | | `name` | — | The state key. Outputs flow forward by name — an output named `foo` becomes the input `foo` of any later segment that declares it. | | `type` | — | One of `string`, `number`, `boolean`, `secret`, `file`. `secret` is a string that is masked in logs. `file` carries a base64 data URI; the runtime writes it to a temp path before Playwright `setInputFiles`. | | `required` | `true` | Required parameters must be present and non-null. For optional parameters, see [Optionality](#optionality-the-most-common-gotcha). | | `description` | `null` | Read by the AI agent when present. Be concrete and action-oriented. | | `defaultValue` | `null` | Applied before validation when the value is missing/empty. | | `options` | `null` | If set, the value must be one of the listed strings (renders a dropdown). Treated as a string at runtime regardless of `type`. | A minimal parameter is `{ "name": "...", "type": "..." }` — the rest take their defaults. Use `secret` for credentials, tokens, API keys. Use `file` for user-uploaded attachments — declare it as a workflow input, reference it as `{{parameter_name}}` in prompts and `parameters.parameter_name` in deterministic code. Do not use `string` for binary uploads. ### Segment ```json theme={null} { "name": "snake_case_action_name", "type": "ui", "prompt": "Plain-English instruction for the agent.", "inputParameters": [], "outputParameters": [], "deterministic": "async (page, parameters) => { /* ... */ return {}; }", "next": "next_segment_name", "router": null } ``` A segment only sees parameters it **declares in `inputParameters`**. The shared state is global, but the segment's `parameters` object at runtime contains only the values the segment opted into. If you forget to declare an input, `parameters.foo` is `undefined`, even if an earlier segment produced `foo`. #### Segment types | Type | When to use | `deterministic` | `prompt` | AI fallback if deterministic throws | | --------- | ------------------------------------------------------------------ | --------------- | -------------- | ----------------------------------- | | `ui` | DOM interaction, navigation, visible-page extraction (the default) | recommended | required | yes | | `network` | Reading data out of recorded API responses | required | required | yes | | `logic` | Deterministic-only control flow / hard errors / invariant checks | required | must be `null` | **no** — segment hard-fails | | `agent` | Subjective judgment ("best", "most relevant", visual selection) | must be `null` | required | n/a — agent is the only mode | Rules of thumb: * Default to `ui`. Reach for `network` only when the response payload is the source of truth (the user said "from API", or UI parsing is brittle). * Use `logic` when you specifically want **no [AI fallback](/advanced/self-healing#ai-fallback)** — for example, throwing "no results" errors or post-filter validation. * Use `agent` when deterministic code can't reliably encode the decision. #### `deterministic` `deterministic` is a **JSON string** containing a stringified async arrow function — not a live function. Two valid signatures: ```js theme={null} async (page, parameters) => { /* Playwright */ return { /* outputs */ }; } async (page, parameters, networkResponses) => { /* network segments */ } ``` * `page` is a Playwright page bound to the active session. * `parameters` contains all declared `inputParameters` for this segment. * `networkResponses` (network segments only) — see [Network segments](#network-segments) below. * The function **must return an object** — use `{}` if there are no outputs. * Throw on missing/invalid required values; never return `null` placeholders. Because the function is serialized as a JSON string, escape internal double quotes and keep it on a single line. For example, the function body `await page.goto('https://example.com'); return {};` becomes: ```json theme={null} "deterministic": "async (page, parameters) => { await page.goto('https://example.com'); return {}; }" ``` #### `prompt` Plain English for the AI agent. Reference parameters as `{{parameter_name}}` for runtime substitution. Describe the goal in terms of logical actions, not selectors: * ✅ `"Search Amazon for {{search_query}} and submit."` * ❌ `"Click button[aria-label=Search], fill input#q, press Enter."` For `ui`/`network` segments, the prompt is also used as [AI fallback](/advanced/self-healing#ai-fallback) if the deterministic step throws — write it as if it might run on its own. #### `next` and `router` * **Linear:** `"next": "next_segment"`. * **Terminal:** `"next": null` (workflow ends after this segment). * **Branching:** `"next": ["segment_a", "segment_b"]` plus `"router": "(parameters) => parameters.x ? 'segment_a' : 'segment_b'"`. The router function receives **only this segment's `outputParameters`** (its own output object), not the merged shared state. If you want to route on a value, that value **must** be declared as an `outputParameter` of the same segment whose `router` reads it. ### Data flow State is a single flat map keyed by parameter `name`. After a segment runs, its `outputParameters` are merged into that map and made available to every later segment whose `inputParameters` declare the same name. The workflow's `outputParameters` are read from the same map at the end. Implications: * Two segments with the same output name **overwrite** each other. * A segment only reads parameters it explicitly declares in `inputParameters` — declaring an input is opt-in even though the underlying state is shared. * A router only reads its own segment's outputs — see the warning above. * If a downstream segment treats an input as `required: true`, the producing segment's output should also be `required: true`, and its deterministic code must throw (not return `null`/empty) when the value is unavailable. * Demonstration values and static URLs belong **inside the prompt**, not as parameters. ### Network segments For `type: "network"` segments, the deterministic function receives a third argument — `networkResponses` — containing all responses recorded during the session. Each entry has fields like `requestUrl`, `method`, `status`, `headers`, and a body. The body field name varies by recorder version, so always read it with the canonical fallback: ```js theme={null} const rawPayload = entry?.responseBody ?? entry?.body; ``` Match responses with stable predicates (pathname/method/status), not full URL string equality: ```json theme={null} "deterministic": "async (page, parameters, networkResponses) => { const matches = (networkResponses || []).filter(e => String(e?.requestUrl || '').includes('/api/data') && String(e?.method || '').toUpperCase() === 'GET' && Number(e?.status) === 200); if (!matches.length) throw new Error('Expected /api/data GET 200 response not found'); const last = matches[matches.length - 1]; const rawPayload = last?.responseBody ?? last?.body; if (rawPayload == null) throw new Error(\"Missing required output 'result' from response payload\"); const json = typeof rawPayload === 'string' ? JSON.parse(rawPayload) : rawPayload; return { result: JSON.stringify(json) }; }" ``` Do not use `page.waitForResponse` inside a workflow segment — `networkResponses` is the deterministic source of truth. ### Reliability patterns These are the highest-leverage rules for hand-written deterministic code. #### Split navigation from interaction Always put `page.goto(url)` in its own segment. Element waits, fills, and clicks belong in the next segment. * ❌ `nav_open_and_fill_search` — navigation + interaction in one segment * ✅ `nav_to_search_page` → `fill_search_form` The page load is handled automatically by the browser; do not call `page.waitForLoadState('networkidle')` after `page.goto`. The next segment handles waiting for specific elements to appear. Splitting them makes segments reusable and resilient to timing issues. #### Click and input stability For any element that may be off-screen or in a virtual list, use the canonical pattern: ```js theme={null} const el = page.locator('selector'); await el.waitFor({ state: 'attached', timeout: 30000 }); await el.scrollIntoViewIfNeeded(); await el.click(); ``` * `state: 'attached'` waits for DOM presence even when the element is off-screen. The default `state: 'visible'` breaks on virtual lists. * For text inputs (`input`, `textarea`, contenteditable), click/focus first, then `fill()` or `type()`. #### Selector quality Prefer stable attributes in this order: `data-testid` → `aria-label` → `name` → semantic role + text. Scope selectors to a relevant region before text matching; avoid global `.first()` unless uniqueness is guaranteed. ### Optionality (the most common gotcha) The schema validator accepts `undefined` (i.e. a missing key) for optional fields, **not `null`**. An AI agent producing structured JSON tends to emit `"field": null` for "absent", which fails validation — especially for optional fields with `options` (enums). Three patterns that work: **1. Route around it.** Declare the optional value as an `outputParameter` of the segment whose `router` will read it, so the router can branch *before* the consuming segment ever runs. The consuming segment can then treat the input as required. ```json theme={null} { "name": "get_task_details", "type": "ui", "prompt": "Read the task and return its current estimation if any.", "inputParameters": [], "outputParameters": [ { "name": "estimation", "type": "number", "required": true, "description": "Current estimation, or 0 if none." } ], "deterministic": "async (page, parameters) => { /* read DOM, default to 0 */ return { estimation: 0 }; }", "next": ["set_estimation", "skip_estimation"], "router": "(parameters) => parameters.estimation > 0 ? 'set_estimation' : 'skip_estimation'" } ``` Note that `parameters.estimation` here refers to **this segment's own output**, which is what the router receives. **2. Add an explicit "none" option.** For optional enums, include a sentinel like `"none"` in `options` and make the field `required: true`. The agent will pick `"none"` instead of `null`. **3. Omit the key.** If you control the deterministic code, return `{}` (omit the key entirely) instead of `{ field: null }`. ### Identity-linked workflows When an [identity](/essentials/authenticated-applications) is attached to the workflow, the platform pre-authenticates the session before the workflow runs. In that case: * **Do not** declare `username`, `password`, `otp_code`, `totp_secret`, `mfa_code`, login `email`, or any other auth credential as an `inputParameter`. * **Do not** add login segments to the workflow — login is handled before your first segment runs. * Treat any login actions you saw during recording as pre-conditions performed by the platform, not as part of the user's task. * Only declare business-logic parameters the user actually needs to provide at runtime (e.g. `report_name`, `invoice_number`, `search_query`). ### Authoring checklist * [ ] Every segment has a `type`. * [ ] `logic` segments have `prompt: null` and a non-null `deterministic`. * [ ] `agent` segments have `deterministic: null` and a comprehensive `prompt`. * [ ] Every value referenced in a `router` is declared as an `outputParameter` **of the same segment** as the router. * [ ] Every value a segment uses is declared in its `inputParameters`. * [ ] Required downstream inputs come from required upstream outputs; deterministic code throws instead of returning `null` for them. * [ ] Optional values are handled by routing, an explicit `"none"` option, or by omitting the key — not by emitting `null`. * [ ] Prompts use `{{parameter_name}}` for dynamic values; static URLs and demonstration values are embedded in the prompt, not declared as parameters. * [ ] `secret` for credentials/tokens; `file` for user-uploaded attachments. * [ ] Navigation segments do `page.goto` only — element waits and interactions live in the next segment. * [ ] `next: null` only on terminal segments; otherwise a single string or an array paired with a `router`. * [ ] `deterministic` is a JSON string with quotes escaped, on a single line. * [ ] If an identity is attached, no auth credentials are declared as parameters. ### Complete example A two-input, two-output workflow that searches Amazon and extracts the first result. ```json theme={null} { "name": "amazon_price_check", "startSegmentName": "nav_to_amazon", "inputParameters": [ { "name": "search_query", "type": "string", "required": true, "description": "Product to search for", "defaultValue": null, "options": null }, { "name": "max_price", "type": "number", "required": false, "description": "Optional price ceiling (USD)", "defaultValue": null, "options": null } ], "outputParameters": [ { "name": "top_result_title", "type": "string", "required": true, "description": "Title of the first matching product", "defaultValue": null, "options": null }, { "name": "top_result_price", "type": "number", "required": false, "description": "Price of the first matching product", "defaultValue": null, "options": null } ], "segments": [ { "name": "nav_to_amazon", "type": "ui", "prompt": "Navigate to https://www.amazon.com/.", "inputParameters": [], "outputParameters": [], "deterministic": "async (page, parameters) => { await page.goto('https://www.amazon.com/'); return {}; }", "next": "search_product", "router": null }, { "name": "search_product", "type": "ui", "prompt": "Type {{search_query}} into the search box and submit.", "inputParameters": [ { "name": "search_query", "type": "string", "required": true, "description": "Product to search for", "defaultValue": null, "options": null } ], "outputParameters": [], "deterministic": "async (page, parameters) => { const input = page.locator('#twotabsearchtextbox'); await input.waitFor({ state: 'attached', timeout: 30000 }); await input.fill(parameters.search_query); await page.keyboard.press('Enter'); return {}; }", "next": "extract_top_result", "router": null }, { "name": "extract_top_result", "type": "ui", "prompt": "Extract the title and price of the first product result. Return them as { top_result_title, top_result_price }.", "inputParameters": [], "outputParameters": [ { "name": "top_result_title", "type": "string", "required": true, "description": "First result title", "defaultValue": null, "options": null }, { "name": "top_result_price", "type": "number", "required": false, "description": "First result price", "defaultValue": null, "options": null } ], "deterministic": "async (page, parameters) => { const card = page.locator('[data-component-type=\"s-search-result\"]').first(); await card.waitFor({ state: 'attached', timeout: 30000 }); const title = (await card.locator('h2 span').first().textContent())?.trim(); const priceText = (await card.locator('.a-price > .a-offscreen').first().textContent())?.replace(/[^0-9.]/g, ''); const price = priceText ? Number(priceText) : undefined; if (!title) throw new Error(\"Missing required output 'top_result_title' from extract_top_result/h2\"); return { top_result_title: title, top_result_price: price }; }", "next": null, "router": null } ] } ``` # MCP - Hosted Version Source: https://docs.anchorbrowser.io/advanced/mcp Use Anchor with Model Context Protocol (MCP) in your preferred agentic tools via our hosted service Model Context Protocol ## Overview Anchor provides a **hosted** Model Context Protocol (MCP) integration, allowing you to use browser automation directly from your preferred AI tools without any local setup. Our hosted MCP server runs on our infrastructure and is available to all users with an Anchor API key. This enables seamless browser control from Cursor, VS Code, Claude, ChatGPT, and other MCP-compatible tools without managing any local dependencies. ## What is MCP? Model Context Protocol (MCP) is an open standard that allows AI assistants to interact with external tools and data sources. In our case, it enables AI-powered tools to access and control our browser automation capabilities directly within your IDE, agent apps, or CI/CD pipelines. ## Hosted vs Self-Hosted Our **hosted MCP service** provides: * ✅ Zero setup - just add your API key * ✅ Always up-to-date with latest features * ✅ Managed infrastructure and updates * ✅ Built-in scaling and reliability * ✅ Direct integration with Anchor's cloud browsers For advanced customization needs, see our [Open Source MCP Server](/advanced/mcp-open-source) documentation. ## Setup in Cursor Other MCP-compatible tools follow about the same pattern. The MCP server runs on our servers ([https://api.anchorbrowser.io/mcp](https://api.anchorbrowser.io/mcp)), and is available to all users providing their Anchor API Key. ### Configure MCP in Cursor Press Command+Shift+P (Mac) or Ctrl+Shift+P (Linux/Windows) and select "Open MCP Configuration File" Get Cursor MCP Settings Click on "Add Custom MCP" or "New MCP Server" if you already have some pre-configured. Add Custom MCP Server Add inside the `mcpServers` object the following: ```json theme={null} "Anchor Browser Agent": { "url": "https://api.anchorbrowser.io/mcp", "headers": { "anchor-api-key": "YOUR_ANCHOR_API_KEY" } } ``` If you don't have your Anchor API key yet, you can get it from [Anchor UI](https://app.anchorbrowser.io/api-keys). You should now see Anchor MCP server in the list of MCP servers in Cursor. It should say '24 tools enabled'. If you don't see it, disable and re-enable Anchor MCP server, or wait a little longer. ## Setup in VS Code Install the MCP extension for VS Code from the marketplace. Add to your VS Code MCP configuration file: ```json theme={null} { "mcpServers": { "anchor-browser": { "url": "https://api.anchorbrowser.io/mcp", "headers": { "anchor-api-key": "YOUR_ANCHOR_API_KEY" } } } } ``` Restart VS Code to load the new MCP server configuration. ## Setup in Claude Desktop Open Claude Desktop's configuration file (`claude_desktop_config.json`). Add the following to your configuration: ```json theme={null} { "mcpServers": { "anchor-browser": { "url": "https://api.anchorbrowser.io/mcp", "headers": { "anchor-api-key": "YOUR_ANCHOR_API_KEY" } } } } ``` Restart Claude Desktop to apply the configuration. # Usage Once configured, you can use Anchor Browser directly in your conversations with your AI assistant. ## Available Tools The hosted MCP integration provides access to all main Anchor capabilities: MCP Tools ### Test Generator Example ``` - You are a playwright test generator. - You are given a scenario and you need to generate a playwright test for it. - DO NOT generate test code based on the scenario alone. - DO run steps one by one using the tools provided by the Anchor Browser Agent MCP. - Only after all steps are completed, emit a Playwright TypeScript test that uses @playwright/test based on message history - Save generated test file in the tests directory - Execute the test file and iterate until the test passes Generate a Playwright test for the following scenario: 1. Navigate to https://www.imdb.com/ 2. search for 'Garfield' 3. return the director of the last movie ``` ## Programmatic Usage (Python SDK) You can also use the hosted MCP service programmatically in your Python applications using the MCP client library: ### Installation ```bash theme={null} pip install mcp ``` ### Basic Example ```python theme={null} import asyncio from mcp.client.streamable_http import streamablehttp_client from mcp import ClientSession async def list_tools(): async with streamablehttp_client( url="https://api.anchorbrowser.io/mcp", headers={"anchor-api-key": "sk-your-key"} ) as ( read_stream, write_stream, _, ): async with ClientSession(read_stream, write_stream) as session: await session.initialize() tools = await session.list_tools() for tool in tools.tools: print(f"{tool.name}: {getattr(tool, 'description', '')}") asyncio.run(list_tools()) ``` ## CI/CD Integration The hosted MCP service works in CI/CD environments without requiring local browser installations: ```yaml theme={null} # GitHub Actions example name: AI Browser Testing on: [push, pull_request] jobs: test: runs-on: ubuntu-latest steps: - uses: actions/checkout@v3 - name: Run AI tests env: ANCHOR_API_KEY: ${{ secrets.ANCHOR_API_KEY }} run: | python ai_test_runner.py ``` ## Getting Help If you encounter issues with the hosted MCP integration: 1. **Check API Key**: Ensure your API key is valid 2. **Restart MCP Client**: Disable and re-enable the MCP server in your client 3. **Contact Support**: Reach out at [support@anchorbrowser.io](mailto:support@anchorbrowser.io) ## Migration from Self-Hosted Moving from a self-hosted MCP server to our hosted service: 1. **Update Configuration**: Change your MCP client to use `https://api.anchorbrowser.io/mcp` 2. **Add API Key**: Include your Anchor API key in the headers 3. **Remove Local Dependencies**: Uninstall local MCP server and dependencies 4. **Test Integration**: Verify all your existing MCP workflows still work # MCP - Open Source Source: https://docs.anchorbrowser.io/advanced/mcp-open-source Self-host Anchor MCP server with customizable Playwright integration for your specific needs # Anchor MCP Server (Open Source) Model Context Protocol A Model Context Protocol (MCP) server that provides browser automation capabilities using [Anchor Browser](https://anchorbrowser.io)'s remote browser service with [Playwright](https://playwright.dev). This server enables LLMs to interact with web pages through Anchor's cloud-based browsers with built-in proxies, stealth features, and advanced capabilities. This is based on the open source repository at [browsermcp-com/mcp](https://github.com/browsermcp-com/mcp), which extends Microsoft's Playwright MCP with Anchor Browser's cloud infrastructure. Looking for our hosted MCP service? Check out [MCP - Hosted Version](/advanced/mcp) for zero-setup integration. ## When to Use Open Source MCP Choose the open source version when you need: * **Custom tool modifications** - Modify browser automation tools for specific use cases * **Advanced configuration** - Fine-tune browser settings and behaviors * **Local development** - Test MCP integrations during development * **Compliance requirements** - Run MCP server within your infrastructure * **Integration with existing systems** - Connect MCP to your internal tools and workflows ## Key Features * **Remote Browser Execution**: Uses Anchor Browser's cloud infrastructure instead of local browsers * **Built-in Proxies**: Automatic proxy rotation and geo-targeting * **Stealth & Anti-Detection**: Advanced browser fingerprinting and anti-bot detection * **Fast and lightweight**: Uses Playwright's accessibility tree, not pixel-based input * **LLM-friendly**: No vision models needed, operates purely on structured data * **Deterministic tool application**: Avoids ambiguity common with screenshot-based approaches * **Customizable**: Modify and extend tools for your specific needs ## Requirements * Node.js 18 or newer * **Anchor Browser API Key** ([Get one here](https://anchorbrowser.io)) * VS Code, Cursor, Windsurf, Claude Desktop, Goose or any other MCP client ## Getting Started ### 1. Clone and Build Since this is a custom Anchor MCP server, you need to build it locally: ```bash theme={null} # Clone the repository git clone https://github.com/browsermcp-com/mcp.git cd mcp # Install dependencies and build npm install npm run build ``` ### 2. Get Your Anchor API Key 1. Sign up at [anchorbrowser.io](https://anchorbrowser.io) 2. Get your API key from the dashboard 3. Copy your API key (starts with `sk-`) ### 3. Configure MCP Client #### Cursor Add to your `~/.cursor/mcp.json`: ```json theme={null} { "mcpServers": { "anchor-browser": { "command": "node", "args": [ "/path/to/mcp/cli.js" ], "env": { "ANCHOR_API_KEY": "sk-your-api-key-here" } } } } ``` #### VS Code Add to your MCP configuration: ```json theme={null} { "mcpServers": { "anchor-browser": { "command": "node", "args": [ "/path/to/mcp/cli.js" ], "env": { "ANCHOR_API_KEY": "sk-your-api-key-here" } } } } ``` #### Claude Desktop Add to your `claude_desktop_config.json`: ```json theme={null} { "mcpServers": { "anchor-browser": { "command": "node", "args": [ "/path/to/mcp/cli.js" ], "env": { "ANCHOR_API_KEY": "sk-your-api-key-here" } } } } ``` ### 4. Restart Your MCP Client After updating the configuration, restart your MCP client (Cursor, VS Code, etc.) to load the new server. ## Configuration Options The Anchor MCP server supports essential configuration options: ```bash theme={null} node cli.js --help ``` ### Available Options: * `--host ` - Host to bind server to (default: localhost, use 0.0.0.0 for all interfaces) * `--port ` - Port to listen on for HTTP transport (Docker/server mode) ### Example with Options: ```json theme={null} { "mcpServers": { "anchor-browser": { "command": "node", "args": [ "/path/to/mcp/cli.js" ], "env": { "ANCHOR_API_KEY": "sk-your-api-key-here" } } } } ``` ## How It Works 1. **Browser Session Creation**: When you use browser tools, the MCP server calls Anchor's API to create a remote browser session 2. **Remote Connection**: Connects to the remote browser via WebSocket using Chrome DevTools Protocol (CDP) 3. **Tool Execution**: All browser automation happens in Anchor's cloud infrastructure 4. **Proxy & Stealth**: Automatic proxy rotation and advanced anti-detection features 5. **Session Management**: Each session is isolated and can be viewed live via Anchor's dashboard ## Production & CI/CD Usage ### Self-Hosted in Production The open source MCP server can be deployed in production environments: * **Docker Containers** - Run in containerized environments * **CI/CD Pipelines** - Integrate with Jenkins, GitHub Actions, GitLab CI * **Serverless Functions** - Deploy as microservices or serverless functions * **Kubernetes** - Scale horizontally in Kubernetes clusters ### CI/CD Integration Example ```yaml theme={null} # GitHub Actions with self-hosted MCP name: E2E Testing on: [push, pull_request] jobs: test: runs-on: ubuntu-latest services: anchor-mcp: image: your-registry/anchor-mcp:latest env: ANCHOR_API_KEY: ${{ secrets.ANCHOR_API_KEY }} ports: - 8931:8931 steps: - uses: actions/checkout@v3 - name: Run AI tests against MCP server run: | python ai_test_runner.py --mcp-url http://localhost:8931/mcp ``` ## Benefits Over Local Browsers ### 🌐 **Global Proxy Network** * Automatic proxy rotation * Geo-targeting for different regions * No proxy configuration needed ### 🛡️ **Advanced Stealth** * Browser fingerprinting protection * Anti-bot detection bypass * Real browser environments ### ☁️ **Cloud Infrastructure** * No local browser dependencies * Consistent browser versions * Scalable execution ### 📊 **Monitoring & Debugging** * Live view of browser sessions * Session recordings and traces * Network request logging # Custom MFA Source: https://docs.anchorbrowser.io/advanced/mfa Inject MFA codes from your own systems during browser automation using event coordination. # Custom MFA Use **event coordination** to pass MFA codes from external systems — your mobile app, webhook, or backend — into an active browser session during automation. ## Overview The system provides two operations: * **Signal Event**: Send data to an event channel * **Wait for Event**: Listen for data on an event channel with timeout Events are user-scoped and work across multiple browser instances. ## API Endpoints ### Signal an Event ```http theme={null} POST https://api.anchorbrowser.io/api/v1/events/{eventName} ``` ### Wait for an Event ```http theme={null} POST https://api.anchorbrowser.io/api/v1/events/{eventName}/wait ``` Both endpoints require `anchor-api-key` header and accept JSON payloads. #### ## Custom MFA flow Handle MFA codes during automated login flows: ```javascript theme={null} // In your browser automation script async function handleMFAFlow() { await page.fill('#username', 'user@example.com'); await page.fill('#password', 'password'); await page.click('#login-button'); // Wait for MFA code from external system const mfaEvent = await waitForEvent('mfa_code', 30000); if (mfaEvent?.data?.code) { await page.fill('#mfa-code', mfaEvent.data.code); await page.click('#verify-button'); } } ``` ```javascript theme={null} // In your external system (mobile app, webhook, etc.) async function sendMFACode(code) { await signalEvent('mfa_code', { code }); } ``` ## Implementation ### Helper Functions ```javascript node.js theme={null} import AnchorBrowser from 'anchorbrowser'; const anchor_client = new AnchorBrowser({apiKey: process.env.ANCHOR_API_KEY}); async function signalEvent(eventName, data) { try { const response = await anchor_client.events.signal(eventName, { data: data || {} }); return response; } catch (error) { throw new Error(`Failed to signal event: ${error.message}`); } } async function waitForEvent(eventName, timeoutMs = 60000) { try { const response = await anchor_client.events.waitFor(eventName, { timeoutMs }); return response; } catch (error) { if (error.message.includes("408")) return null; // Timeout throw new Error(`Failed to wait for event: ${error.message}`); } } ``` ```python python theme={null} import os from anchorbrowser import Anchorbrowser anchor_client = Anchorbrowser(api_key=os.getenv("ANCHOR_API_KEY")) def signal_event(event_name, data): try: response = anchor_client.events.signal(event_name, data=data or {}) return response except Exception as error: raise Exception(f"Failed to signal event: {error}") def wait_for_event(event_name, timeout_ms=60000): try: response = anchor_client.events.wait_for(event_name, timeout_ms=timeout_ms) return response except Exception as error: if "408" in str(error): return None # Timeout raise Exception(f"Failed to wait for event: {error}") ``` ## Event Flow Patterns **Signal First, Wait Later** (immediate consumption): ```javascript theme={null} await signalEvent("data", { value: "preloaded" }); const data = await waitForEvent("data", 1000); // Short timeout ``` **Wait First, Signal Later** (typical MFA flow): ```javascript theme={null} const waitPromise = waitForEvent("mfa_code", 60000); // ... other operations ... const mfaData = await waitPromise; ``` ## Best Practices * Use descriptive event names: `mfa_code_login`, `mfa_code_transfer` * Always set appropriate timeouts * Validate received event data * Handle timeout scenarios gracefully # OS-Level Control Source: https://docs.anchorbrowser.io/advanced/os-level-control Direct operating system control for precise browser automation and AI agent interactions # OS-Level Control OS-level control provides direct access to operating system primitives like mouse movements, keyboard input, and screen interactions within your browser sessions. This approach offers more precise control than traditional web automation methods and is particularly powerful when combined with AI agents and vision-based models. ## Why OS-Level Control? ### Superior AI Agent Performance **Vision-based AI models perform significantly better** when they can interact with the browser using the same primitives humans use: * **OS-level UI elements**: Dropdowns, context menus, and system dialogs that aren't part of the webpage DOM. * **Visual coordinate targeting**: AI agents can directly click on elements they see in screenshots * **Keyboard shortcuts work naturally**: `Ctrl+F` for searching, `Ctrl+L` for browser navbar interaction, `Ctrl+T` for new tabs ## Supported Keys For Keyboard Shortcuts | | | | | | | | | | ------- | ---------- | ---- | ----- | ---------- | ----------- | --------- | - | | `A`-`Z` | `Up` | `F1` | `F7` | `Control` | `Enter` | `Command` | | | `0`-`9` | `Down` | `F2` | `F8` | `Ctrl` | `Return` | `Cmd` | | | `Space` | `Left` | `F3` | `F9` | `Alt` | `Backspace` | `Windows` | | | `Home` | `Right` | `F4` | `F10` | `Shift` | `Delete` | `Win` | | | `End` | `PageUp` | `F5` | `F11` | `CapsLock` | `Escape` | `Insert` | | | `Tab` | `PageDown` | `F6` | `F12` | `NumLock` | `Esc` | `Ins` | | ## Core Capabilities - Beyond Traditional Web Automation Control mouse interactions with pixel-level precision: ### Basic Click ```javascript node.js theme={null} import AnchorBrowser from 'anchorbrowser'; const anchor_client = new AnchorBrowser({apiKey: process.env.ANCHOR_API_KEY}); const response = await anchor_client.sessions.mouse.click("Your Session ID", { // Single click at coordinates x: 100, y: 700, }); console.log(response.data.status) ``` ```python python theme={null} from anchorbrowser import Anchorbrowser import os client = Anchorbrowser( api_key = os.getenv("ANCHOR_API_KEY") ) response = client.sessions.mouse.click( session_id = "Your Session ID", # Single click at coordinates x = 100, y = 700, ) print(response.data['status']) ``` ### Advanced Mouse Control ```javascript node.js theme={null} import AnchorBrowser from 'anchorbrowser'; const anchorClient = new AnchorBrowser({apiKey: process.env.ANCHOR_API_KEY}); const sessionId = "Your Session ID"; // Double-click for text selection await anchorClient.sessions.mouse.doubleClick(sessionId, { x: 500, y: 200 }); // Mouse down and up for custom gestures await anchorClient.sessions.mouse.down(sessionId, { x: 100, y: 100, }); // Move while holding down (drag) await anchorClient.sessions.mouse.move(sessionId, { x: 300, y: 300 }); // Release await anchorClient.sessions.mouse.up(sessionId, { x: 300, y: 300 }); ``` ```python python theme={null} from anchorbrowser import Anchorbrowser import os anchor_client = Anchorbrowser(api_key=os.getenv("ANCHOR_API_KEY")) session_id = "Your Session ID" # Double-click for text selection anchor_client.sessions.mouse.double_click(session_id, x = 500, y = 200) # Mouse down and up for custom gestures anchor_client.sessions.mouse.down(session_id, x = 100, y = 100) # Move while holding down (drag) anchor_client.sessions.mouse.move(session_id, x = 300, y = 300) # Release anchor_client.sessions.mouse.up(session_id, x = 300, y = 300) ``` ### Drag and Drop Perform complex drag and drop operations in a single command: ```javascript node.js theme={null} import AnchorBrowser from 'anchorbrowser'; const anchorClient = new AnchorBrowser({apiKey: process.env.ANCHOR_API_KEY}); const sessionId = "Your Session ID"; // Drag and Drop await anchorClient.sessions.dragAndDrop(sessionId, { startX: 200, startY: 150, endX: 600, endY: 400, }); ``` ```python python theme={null} from anchorbrowser import Anchorbrowser import os anchor_client = Anchorbrowser(api_key=os.getenv("ANCHOR_API_KEY")) session_id = "Your Session ID" # Drag and Drop anchor_client.sessions.drag_and_drop(session_id, start_x = 200, start_y = 150, end_x = 600, end_y = 400 ) ``` ### Keyboard Input Send text and keyboard shortcuts with human-like timing: #### Text Input ```javascript node.js theme={null} import AnchorBrowser from 'anchorbrowser'; const anchorClient = new AnchorBrowser({apiKey: process.env.ANCHOR_API_KEY}); const sessionId = "Your Session ID"; // Type text with optional delay between keystrokes await anchorClient.sessions.keyboard.type(sessionId, { text: "Hello, world!", delay: 50 // milliseconds between keystrokes }); ``` ```python python theme={null} from anchorbrowser import Anchorbrowser import os anchor_client = Anchorbrowser(api_key=os.getenv("ANCHOR_API_KEY")) session_id = "Your Session ID" # Type text with optional delay between keystrokes anchor_client.sessions.keyboard.type(session_id, text = "Hello, world!", delay = 50 # milliseconds between keystrokes ) ``` #### Keyboard Shortcuts ```javascript node.js theme={null} import AnchorBrowser from 'anchorbrowser'; const anchorClient = new AnchorBrowser({apiKey: process.env.ANCHOR_API_KEY}); const sessionId = "Your Session ID"; // Execute keyboard shortcuts anchorClient.sessions.keyboard.shortcut(sessionId, { keys: ['Ctrl', 'a'], // Select all holdTime: 100 // Hold keys for 100ms }); // Common shortcuts const shortcuts = { selectAll: ['Ctrl', 'a'], copy: ['Ctrl', 'c'], paste: ['Ctrl', 'v'], undo: ['Ctrl', 'z'], newTab: ['Ctrl', 't'], closeTab: ['Ctrl', 'w'], focusAddressBar: ['Ctrl', 'l'] }; ``` ```python python theme={null} from anchorbrowser import Anchorbrowser import os anchor_client = Anchorbrowser(api_key=os.getenv("ANCHOR_API_KEY")) session_id = "Your Session ID" # Execute keyboard shortcuts anchor_client.sessions.keyboard.shortcut(session_id, keys = ['Ctrl', 'a'], hold_time = 100 # Hold keys for 100ms ) # Common shortcuts shortcuts = { 'select_all': ['Ctrl', 'a'], 'copy': ['Ctrl', 'c'], 'paste': ['Ctrl', 'v'], 'undo': ['Ctrl', 'z'], 'new_tab': ['Ctrl', 't'], 'close_tab': ['Ctrl', 'w'], 'focus_address_bar': ['Ctrl', 'l'] } ``` ### Scrolling Control page scrolling with precision: ```javascript node.js theme={null} import AnchorBrowser from 'anchorbrowser'; const anchorClient = new AnchorBrowser({apiKey: process.env.ANCHOR_API_KEY}); const sessionId = "Your Session ID"; // Scroll at specific coordinates anchorClient.sessions.scroll(sessionId, { x: 400, // Where to perform scroll (cursor position) y: 300, // Where to perform scroll (cursor position) deltaX: 0, // Horizontal scroll amount (does not correlate with pixels) deltaY: 200, // Vertical scroll amount (does not correlate with pixels, positive = down) steps: 5 // Number of steps for smooth scrolling }); ``` ```python python theme={null} from anchorbrowser import Anchorbrowser import os anchor_client = Anchorbrowser(api_key=os.getenv("ANCHOR_API_KEY")) session_id = "Your Session ID" # Scroll at specific coordinates anchor_client.sessions.scroll(session_id, x = 400, # Where to perform scroll (cursor position) y = 300, # Where to perform scroll (cursor position) delta_x = 0, # Horizontal scroll amount (does not correlate with pixels) delta_y = 200, # Vertical scroll amount (does not correlate with pixels, positive = down) steps = 5 # Number of steps for smooth scrolling ) ``` ### Screenshots Capture visual state for AI analysis: ```javascript node.js theme={null} import AnchorBrowser from 'anchorbrowser'; import { writeFile } from 'node:fs/promises'; const anchorClient = new AnchorBrowser({apiKey: process.env.ANCHOR_API_KEY}); const sessionId = "Your Session ID"; // Take screenshot of current browser state const response = await anchorClient.sessions.retrieveScreenshot(sessionId); console.log(response); const imageBuffer = response.body; // Process screenshot with vision AI model (add code below) console.log(imageBuffer); // Or save screenshot to file const ab = await response.arrayBuffer(); // rs is a web ReadableStream await writeFile('image.png', Buffer.from(ab)); ``` ```python python theme={null} from anchorbrowser import Anchorbrowser import os anchor_client = Anchorbrowser(api_key=os.getenv("ANCHOR_API_KEY")) session_id = "Your Session ID" # Take screenshot of current browser state response = anchor_client.sessions.retrieve_screenshot(session_id) print(response) # Save screenshot to file with open("image.png", "wb") as f: for chunk in response.iter_bytes(chunk_size=8192): f.write(chunk) # Process screenshot with vision AI model (add code below) print(f"Received {response}") ``` ### Clipboard Operations Manage clipboard content programmatically: #### Reading Clipboard ```javascript node.js theme={null} import AnchorBrowser from 'anchorbrowser'; const anchorClient = new AnchorBrowser({apiKey: process.env.ANCHOR_API_KEY}); const sessionId = "Your Session ID"; // Get current clipboard content const response = await anchorClient.sessions.clipboard.get(sessionId); console.log('Clipboard content:', response.data.text); ``` ```python python theme={null} from anchorbrowser import Anchorbrowser import os anchor_client = Anchorbrowser(api_key=os.getenv("ANCHOR_API_KEY")) session_id = "Your Session ID" # Get current clipboard content response = anchor_client.sessions.clipboard.get(session_id) print(response) ``` #### Setting Clipboard ```javascript node.js theme={null} import AnchorBrowser from 'anchorbrowser'; const anchorClient = new AnchorBrowser({apiKey: process.env.ANCHOR_API_KEY}); const sessionId = "Your Session ID"; // Set clipboard content await anchorClient.sessions.clipboard.set(sessionId, { text: "Content to copy" }); // Trigger copy operation (copies selected text) const copyResponse = await anchorClient.sessions.copy(sessionId); // Trigger paste operation await anchorClient.sessions.paste(sessionId, { text: "Text to paste" }); ``` ```python python theme={null} from anchorbrowser import Anchorbrowser import os anchor_client = Anchorbrowser(api_key=os.getenv("ANCHOR_API_KEY")) session_id = "Your Session ID" # Set clipboard content anchor_client.sessions.clipboard.set(session_id, text="Content to copy") # Trigger copy operation (copies selected text) copy_response = anchor_client.sessions.copy(session_id) # Trigger paste operation anchor_client.sessions.paste(session_id, text="Text to paste") ``` ### Navigation Direct URL navigation at the OS level on the currently selected tab: ```javascript node.js theme={null} import AnchorBrowser from 'anchorbrowser'; const anchorClient = new AnchorBrowser({apiKey: process.env.ANCHOR_API_KEY}); const sessionId = "Your Session ID"; // Navigate to a specific URL (completely OS-level, operates on selected tab) const response = await anchorClient.sessions.goto(sessionId, { url: "https://example.com" }); console.log("Navigation response:", response); ``` ```python python theme={null} from anchorbrowser import Anchorbrowser import os anchor_client = Anchorbrowser(api_key=os.getenv("ANCHOR_API_KEY")) session_id = "Your Session ID" # Navigate to a specific URL (completely OS-level, operates on selected tab) response = anchor_client.sessions.goto(session_id, url="https://example.com") print("Navigation response:", response) ``` ## AI Agent Integration Patterns ### OpenAI Computer Use Integration Anchor includes an integrated **OpenAI Computer Use agent** that leverages OS-level control for enhanced AI interactions. This agent can perform complex tasks by combining vision models with precise OS-level operations. ```python python theme={null} class AnchorBrowser(BasePlaywrightComputer): """ Computer implementation for Anchor browser (https://anchorbrowser.io) Uses OS-level control endpoints for browser automation within the container. IMPORTANT: The `goto` and navigation tools are already implemented and recommended when using the Anchor computer to help the agent navigate more effectively. """ def __init__(self, width: int = 1024, height: int = 900, session_id: str = None): """Initialize the Anchor browser session""" super().__init__() self.dimensions = (width, height) self.session_id = session_id self.base_url = "http://localhost:8484/api/os-control" def screenshot(self) -> str: """ Capture a screenshot using OS-level control API. Returns: str: A base64 encoded string of the screenshot for AI model consumption. """ try: response = requests.get(f"{self.base_url}/screenshot") if not response.ok: print(f"OS-level screenshot failed, falling back to standard screenshot") return super().screenshot() # OS-level API returns binary PNG data, encoded for AI models return base64.b64encode(response.content).decode('utf-8') except Exception as error: print(f"OS-level screenshot failed, falling back: {error}") return super().screenshot() def click(self, x: int, y: int, button: str = "left") -> None: """ Click at the specified coordinates using OS-level control. Args: x: The x-coordinate to click. y: The y-coordinate to click. button: The mouse button to use ('left', 'right'). """ try: response = requests.post( f"{self.base_url}/mouse/click", json={"x": x, "y": y, "button": button} ) if not response.ok: print(f"OS-level click failed, falling back to standard click") super().click(x, y, button) except Exception as error: print(f"OS-level click failed, falling back: {error}") super().click(x, y, button) def type(self, text: str) -> None: """ Type text using OS-level control with realistic delays. """ try: response = requests.post( f"{self.base_url}/keyboard/type", json={"text": text, "delay": 30} ) if not response.ok: print(f"OS-level type failed, falling back to standard type") super().type(text) except Exception as error: print(f"OS-level type failed, falling back: {error}") super().type(text) def keypress(self, keys: List[str]) -> None: """ Press keyboard shortcut using OS-level control. Args: keys: List of keys to press simultaneously (e.g., ['Ctrl', 'c']). """ try: response = requests.post( f"{self.base_url}/keyboard/shortcut", json={"keys": keys, "holdTime": 100} ) if not response.ok: print(f"OS-level keyboard shortcut failed, falling back") # Fallback to standard implementation for key in keys: self._page.keyboard.down(key) for key in reversed(keys): self._page.keyboard.up(key) except Exception as error: print(f"OS-level keyboard shortcut failed: {error}") ``` ### Usage with OpenAI Models The integrated computer use agent works seamlessly with OpenAI's vision models: ```python theme={null} # Initialize the agent with your session agent = AnchorBrowser(width=1440, height=900, session_id="your-session-id") # The agent can now: # 1. Take screenshots for AI analysis screenshot = agent.screenshot() # 2. Perform precise clicks based on AI vision agent.click(x=400, y=300) # 3. Type text naturally agent.type("Hello from AI agent!") # 4. Execute keyboard shortcuts agent.keypress(['Ctrl', 'l']) # Focus address bar agent.keypress(['Ctrl', 'f']) # Open search ``` The computer use integration provides **automatic fallbacks** to standard browser automation if OS-level operations aren't available, ensuring reliability across different environments. ## Limitations and Considerations ### Session Requirements * **Headful Sessions Only**: OS-level control requires a visible desktop environment * **Performance Impact**: Screenshots and precise positioning may be slower than DOM-based automation *** OS-level control opens up powerful possibilities for AI-driven browser automation, enabling more natural and effective interactions that mirror human behavior while providing the precision needed for reliable automation workflows. # P2P Download Source: https://docs.anchorbrowser.io/advanced/p2p-downloads Receive download events in real time and fetch files directly from the browser ## What is P2P Download? P2P downloads let you receive a real-time notification the moment a file is downloaded in your browser session, then fetch the file **directly from the active session** — no polling, no waiting for Anchor to process the file, no storage APIs required. ## How It Works **Traditional downloads:** > Browser downloads file → Anchor stores the file → You poll `GET /downloads` → Fetch from Anchor storage **P2P downloads:** > Browser downloads file → `Anchor.downloadReady` CDP event fires → You fetch directly from the browser When a file is downloaded, the browser emits a custom CDP event (`Anchor.downloadReady`) over your existing WebSocket connection. The event includes a pre-built fetch URL that streams the file bytes directly from the browser's disk. ## Implementation Connect Playwright over CDP as usual, then open a `CDPSession` on the page to listen for the `Anchor.downloadReady` event. ```typescript node.js theme={null} import { chromium } from "playwright"; import AnchorBrowser from "anchorbrowser"; import fs from "fs"; const { ANCHOR_API_KEY } = process.env; const client = new AnchorBrowser({ apiKey: ANCHOR_API_KEY }); const session = await client.sessions.create(); const { cdp_url, id: sessionId } = session.data; const browser = await chromium.connectOverCDP(cdp_url); const page = browser.contexts()[0].pages()[0]; const cdpSession = await page.context().newCDPSession(page); const downloadReady = new Promise((resolve) => { cdpSession.on("Anchor.downloadReady", (params) => resolve(params)); }); ``` ```python python theme={null} import os from playwright.sync_api import sync_playwright from anchorbrowser import Anchorbrowser ANCHOR_API_KEY = os.getenv("ANCHOR_API_KEY") client = Anchorbrowser(api_key=ANCHOR_API_KEY) session = client.sessions.create() cdp_url = session.data["cdp_url"] with sync_playwright() as p: browser = p.chromium.connect_over_cdp(cdp_url) page = browser.contexts[0].pages[0] cdp_session = page.context.new_cdp_session(page) download_params = {} def on_download_ready(params): download_params.update(params) cdp_session.on("Anchor.downloadReady", on_download_ready) ``` Use Playwright as normal to navigate and click the download link. ```typescript node.js theme={null} await page.goto("https://example.com/reports"); await page.click("#download-report"); ``` ```python python theme={null} with sync_playwright() as p: browser = p.chromium.connect_over_cdp(cdp_url) page = browser.contexts[0].pages[0] page.goto("https://example.com/reports") page.click("#download-report") ``` Wait for the `Anchor.downloadReady` event, then use the `p2pDownloadUrl` from the event params to fetch the file. ```typescript node.js theme={null} // Wait for the download event const event = await downloadReady; console.log(`Download ready: ${event.suggestedFilename} (${event.size} bytes)`); // Fetch the file directly from the session const response = await fetch( `https://api.anchorbrowser.io${event.p2pDownloadUrl}`, { headers: { "anchor-api-key": ANCHOR_API_KEY } } ); fs.writeFileSync( event.suggestedFilename, Buffer.from(await response.arrayBuffer()) ); console.log(`Saved ${event.suggestedFilename}`); await browser.close(); ``` ```python python theme={null} import requests, time # Wait for the download event (up to 15 seconds) timeout = 15 start = time.time() while not download_params and time.time() - start < timeout: time.sleep(0.1) params = download_params print(f"Download ready: {params['suggestedFilename']} ({params['size']} bytes)") # Fetch the file directly from the session response = requests.get( f"https://api.anchorbrowser.io{params['p2pDownloadUrl']}", headers={"anchor-api-key": ANCHOR_API_KEY}, stream=True, ) with open(params["suggestedFilename"], "wb") as f: for chunk in response.iter_content(chunk_size=8192): f.write(chunk) print(f"Saved {params['suggestedFilename']}") ``` ## The `Anchor.downloadReady` Event The event is emitted on the CDP WebSocket connection as soon as the file lands on disk. ```json theme={null} { "method": "Anchor.downloadReady", "params": { "localDownloadId": "4e901615-5f83-4773-a1af-773e15a19be8", "suggestedFilename": "report.pdf", "url": "https://example.com/report.pdf", "originUrl": "https://example.com/reports", "size": 1048576, "duration": 2300, "p2pDownloadUrl": "/v1/sessions/{sessionId}/downloads/{localDownloadId}/p2p" } } ``` | Field | Description | | ------------------- | ------------------------------------------------------------------ | | `localDownloadId` | Unique ID for this download, valid for the lifetime of the session | | `suggestedFilename` | The filename as suggested by the browser | | `url` | The URL the file was downloaded from | | `originUrl` | The page URL where the download was triggered | | `size` | File size in bytes | | `duration` | Time to complete the download in milliseconds | | `p2pDownloadUrl` | Relative URL to fetch the file — prepend your API base URL | ## Fetch Endpoint ``` GET /v1/sessions/:session_id/downloads/:local_download_id/p2p ``` This endpoint streams the file directly from the active session. It is only available while the session is **active**. Once the session ends, use the standard [session downloads API](/api-reference/browser-sessions/list-session-downloads) to retrieve files from Anchor storage. | Status | Meaning | | ------ | ----------------------------------------------------------------------------- | | `200` | File streamed successfully | | `404` | `local_download_id` not found (may have expired with the session) | | `410` | Session is no longer active — use the standard `/downloads/:id/fetch` instead | ## Limitations * The P2P fetch URL is only valid while the **session is running**. After the session ends, the file is no longer accessible via this endpoint. * Files are still stored by Anchor in the background as a fallback. Once the session completes, they are accessible via the standard [list session downloads](/api-reference/browser-sessions/list-session-downloads) API. * Blob and data URL downloads (files generated client-side in JavaScript) are supported. # Popup Blocker Source: https://docs.anchorbrowser.io/advanced/popup-blocker Block cookie banners and consent dialogs in your browser sessions Popup blocking is enabled by default in Anchor Browser. It blocks cookie banners and consent dialogs to create cleaner automation experiences. Popup blocking is enabled by default. Disable it only if you need to test popup-related functionality. ## Quick Start ```javascript node.js theme={null} import AnchorBrowser from 'anchorbrowser'; (async () => { const anchorClient = new AnchorBrowser({apiKey: process.env.ANCHOR_API_KEY}); const session = await anchorClient.sessions.create({ // Optional: Enabled by default (both), so this configuration is not required browser: { adblock: { active: true // Required for popup blocking }, popup_blocker: { active: true // Blocks cookie banners and consent dialogs } } }); console.log("Session:", session.data.id); })().catch(console.error); ``` ```python python theme={null} import os from anchorbrowser import Anchorbrowser anchor_client = Anchorbrowser(api_key=os.getenv("ANCHOR_API_KEY")) session = anchor_client.sessions.create( # Optional: Enabled by default (both), so this configuration is not required browser={ "adblock": { "active": True # Required for popup blocking }, "popup_blocker": { "active": True # Blocks cookie banners and consent dialogs } } ) print("Session:", session.data.id) ``` Popup blocking requires ad blocking to be active. Disabling ad blocking will result an error. ## Disabling Popup Blocker To disable popup blocking for a session, set `active: false` in the popup\_blocker configuration: ```javascript node.js theme={null} import AnchorBrowser from 'anchorbrowser'; (async () => { const anchorClient = new AnchorBrowser({apiKey: process.env.ANCHOR_API_KEY}); const session = await anchorClient.sessions.create({ browser: { // Optional: ad blocking is enabled by default, so this configuration is not required adblock: { active: true // Ad blocking must remain active }, // Required to disable popup_blocker popup_blocker: { active: false // Disables popup blocking for this session } } }); console.log("Session:", session.data.id); })().catch(console.error); ``` ```python python theme={null} import os from anchorbrowser import Anchorbrowser anchor_client = Anchorbrowser(api_key=os.getenv("ANCHOR_API_KEY")) session = anchor_client.sessions.create( browser={ # Optional: ad blocking is enabled by default, so this configuration is not required "adblock": { "active": True # Ad blocking must remain active }, # Required to disable popup_blocker "popup_blocker": { "active": False # Disables popup blocking for this session } } ) print("Session:", session.data.id) ``` ## Related Features * [Ad Blocker](/advanced/adblocker) - Block ads, trackers, and malicious content (required for popup blocking) * [Captcha Solving](/advanced/captcha-solving) - Solve CAPTCHAs that may appear when ad blocking is detected # Proxy Source: https://docs.anchorbrowser.io/advanced/proxy Anchor provides proxy configurations to access websites from different geographic locations, configurable down to the **city level.** Use [Bring Your Own Proxy](/advanced/bring-your-own-proxy) or Anchor's built-in proxy for localization. Anchor Browser infrastructure is fully hosted in the US. For GDPR compliance, upgrade to the [Growth tier](https://app.anchorbrowser.io/billing) or [contact support](https://mail.google.com/mail/?view=cm\&fs=1\&to=support@anchorbrowser.io\&su=Full%20GDPR%20Compatibility%20Request). #### Quick Start Example Here's a simple example of how to use Anchor's built-in proxy: ```javascript node.js theme={null} import AnchorBrowser from 'anchorbrowser'; const anchor_client = new AnchorBrowser({apiKey: process.env.ANCHOR_API_KEY}); const session = await anchor_client.sessions.create({ session: { proxy: { active: true, country_code: 'gb', } } }); console.log("Session created:", session.data); ``` ```python python theme={null} import os from anchorbrowser import Anchorbrowser anchor_client = Anchorbrowser(api_key=os.getenv("ANCHOR_API_KEY")) session = anchor_client.sessions.create( session={ 'proxy': { 'active': True, 'country_code': 'gb', } } ) print("Session created:", session.data) ``` This creates a browser session using Anchor's built-in proxy. You can change `country_code` to any supported country (e.g., 'gb', 'de', 'jp'). To experiment with different proxy configurations, visit our [Interactive API Reference](/api-reference/browser-sessions/start-browser-session) ## Using proxy with tasks Save proxy settings on the task via `task_default_browser_configuration` — a **one-time setup**. Every run that does not pass a `session_id` will use this config. Set it with `PATCH /v1/tools/{toolId}` or `task_browser_default_configuration` on `POST /v2/tasks/generate`: ```bash theme={null} curl -X PATCH "https://api.anchorbrowser.io/v1/tools/{toolId}" \ -H "anchor-api-key: $ANCHOR_API_KEY" \ -H "Content-Type: application/json" \ -d '{ "task_default_browser_configuration": { "session": { "proxy": { "active": true, "type": "anchor_residential", "country_code": "us" } }, "browser": { "captcha_solver": { "active": true }, "profile": { "name": "my-task-profile", "persist": true } } } }' ``` If you create a session with `POST /v1/sessions` and pass its `session_id` to `/run`, **that session's config is used** — including proxy. The task's `task_default_browser_configuration` is ignored for that run. See [Run a Task](/tasks/run-a-task#task-config-vs-session-config) for the full run flow. ## Localization You can specify a country code for your proxy to route traffic through a specific geographic location. This is useful for accessing region-specific content or testing localized experiences. The `country_code` parameter accepts country codes in lowercase. See the [complete list of supported countries](#supported-countries-with-their-country-codes) below for all available options. ### Region and City-Based Targeting For even more precise geographic targeting, you can specify both `region` and `city` parameters. This is only supported for the default proxy type (`anchor_proxy`). **Important Notes:** * The `city` parameter can only be used when `region` is also provided * If you specify a city without a region, the city parameter will be ignored * City names: use English, case-insensitive. Both "Los Angeles" and "los-angeles" work. ```javascript node.js theme={null} import AnchorBrowser from 'anchorbrowser'; const anchorClient = new AnchorBrowser({apiKey: process.env.ANCHOR_API_KEY}); const response = await anchorClient.sessions.create({ session: { proxy: { active: true, country_code: 'us', region: 'ca', city: 'los-angeles' } } }); console.log('Session created:', response.data); ``` ```python python theme={null} import os import json from anchorbrowser import Anchorbrowser anchor_client = Anchorbrowser(api_key=os.getenv('ANCHOR_API_KEY')) response = anchor_client.sessions.create( session={ 'proxy': { 'active': True, 'country_code': 'us', 'region': 'ca', 'city': 'los-angeles' } } ) print('Session created:') print(response.data) ``` ### Supported Countries with their Country Codes | | | | | | | | ---------------------- | -- | ---------------- | -- | ------------------------ | -- | | Afghanistan | af | France | fr | Netherlands | nl | | Albania | al | French Guiana | gf | New Zealand | nz | | Algeria | dz | French Polynesia | pf | Nicaragua | ni | | Andorra | ad | Gabon | ga | Nigeria | ng | | Angola | ao | Gambia | gm | Norway | no | | American Samoa | as | Georgia | ge | Pakistan | pk | | Antigua and Barbuda | ag | Germany | de | Panama | pa | | Argentina | ar | Ghana | gh | Paraguay | py | | Armenia | am | Gibraltar | gi | Peru | pe | | Aruba | aw | Greece | gr | Philippines | ph | | Australia | au | Grenada | gd | Poland | pl | | Austria | at | Guadeloupe | gp | Portugal | pt | | Azerbaijan | az | Guatemala | gt | Puerto Rico | pr | | Bahamas | bs | Guernsey | gg | Qatar | qa | | Bahrain | bh | Guinea | gn | Romania | ro | | Barbados | bb | Guinea-Bissau | gw | Saint Lucia | lc | | Belarus | by | Guyana | gy | San Marino | sm | | Belgium | be | Haiti | ht | Saudi Arabia | sa | | Belize | bz | Honduras | hn | Senegal | sn | | Benin | bj | Hungary | hu | Serbia | rs | | Bermuda | bm | Iceland | is | Seychelles | sc | | Bolivia | bo | India | in | Sierra Leone | sl | | Bosnia and Herzegovina | ba | Iran | ir | Slovakia | sk | | Brazil | br | Iraq | iq | Slovenia | si | | Bulgaria | bg | Ireland | ie | Somalia | so | | Burkina Faso | bf | Israel | il | South Africa | za | | Cameroon | cm | Italy | it | South Korea | kr | | Canada | ca | Jamaica | jm | Spain | es | | Cape Verde | cv | Japan | jp | Suriname | sr | | Chad | td | Jordan | jo | Sweden | se | | Chile | cl | Kazakhstan | kz | Switzerland | ch | | Colombia | co | Kuwait | kw | Syria | sy | | Congo | cg | Kyrgyzstan | kg | São Tomé and Príncipe | st | | Costa Rica | cr | Latvia | lv | Taiwan | tw | | Côte d’Ivoire | ci | Lebanon | lb | Tajikistan | tj | | Croatia | hr | Libya | ly | Togo | tg | | Cuba | cu | Liechtenstein | li | Trinidad and Tobago | tt | | Cyprus | cy | Lithuania | lt | Tunisia | tn | | Czech Republic | cz | Luxembourg | lu | Turkey | tr | | Denmark | dk | Macedonia | mk | Turks and Caicos Islands | tc | | Dominica | dm | Mali | ml | Ukraine | ua | | Dominican Republic | do | Malta | mt | United Arab Emirates | ae | | Ecuador | ec | Martinique | mq | United Kingdom | gb | | Egypt | eg | Mauritania | mr | United States | us | | El Salvador | sv | Mexico | mx | Uruguay | uy | | Estonia | ee | Moldova | md | Uzbekistan | uz | | Ethiopia | et | Monaco | mc | Venezuela | ve | | Faroe Islands | fo | Montenegro | me | Yemen | ye | | Finland | fi | Morocco | ma | | | # Self-healing Source: https://docs.anchorbrowser.io/advanced/self-healing How Anchor keeps Automation Tasks reliable — agentic completion during runs and durable workflow improvements afterward. ## Overview Automation Tasks combine **deterministic Playwright code** for speed and consistency with **agentic steps** when the page needs more flexibility. As websites change, Anchor helps your tasks stay reliable in two ways: | Layer | When it runs | What it does | | ------------------------- | --------------------------------------------------- | ---------------------------------------------------------------------- | | **AI fallback** | During a run, when a segment needs agentic help | Completes the step with an AI agent so the run finishes successfully | | **Workflow self-healing** | After a run, when any segment completed agentically | Analyzes what worked and creates a new draft version for you to review | AI fallback keeps today's run on track. Self-healing turns what worked agentically into a stronger deterministic workflow for tomorrow. ## AI fallback When a step needs more flexibility than deterministic code alone, Anchor completes it with an AI agent. The run keeps going — you get the result, and you can see exactly where the agent stepped in. ### How to spot agentic steps **On the execution.** Runs that used AI fallback show an **AI Fallback** badge in the task execution list and on the [run detail page](https://app.anchorbrowser.io/tools). Look for it next to the run status. **On the workflow graph.** Open a run to view the **Workflow Graph**. Any segment that completed agentically shows an **AI Fallback** badge on that segment's card — during the run and after it finishes. **In the steps panel.** The right-hand **Steps** panel lists each segment in order. Expand a segment to see its logs. When the agent completed that step, an **Agent Logs** section appears underneath — these are the agentic actions taken for that segment. Segments designed to run agentically from the start (not fallback) are marked with a **Bot** icon on the workflow graph. AI fallback is when a normally deterministic segment completed agentically instead. ## Workflow self-healing After a run where any segment completed agentically, Anchor can study the execution and propose an updated workflow that captures what worked. **Nothing goes live automatically** — self-healing creates a draft version and waits for your approval. ### When it triggers automatically Self-healing runs automatically after workflow runs that give Anchor enough signal to propose an update: * The task is a **workflow** task that **completed its run** (Anchor can study the full execution and produce a new draft version) * At least one segment completed **agentically** — via AI fallback — or a **logic segment** surfaced a condition worth updating in the workflow This often happens on runs that **finished successfully**. The task completed, but one or more steps relied on the agent instead of deterministic code. Self-healing learns from that run so the next execution can stay on the deterministic path. ### Review and approve In the [Tasks UI](https://app.anchorbrowser.io/tools), open the task and find the run that triggered self-healing. Expand the execution to see the **summary** and **suggested improvement**. Click **Apply fix** to open a side-by-side comparison of the current workflow and the healed draft. Switch between **Code** and **Graph** views to inspect what changed. Click **Test Run** to execute the draft with the same inputs as the original run (or your own). Confirm the updated workflow runs deterministically before promoting it. Click **Deploy** to promote the healed draft to the task's latest version. Until you do, production runs continue using the previous version. You can also approve via API — see [Code Tasks](/advanced/legacy-tasks#api-process-for-editing-a-workflow) for reading draft versions and calling `POST /v2/tasks/{taskId}/publish-draft`. ## Webhooks Subscribe to `task.healed` to get notified when self-healing creates a draft version ready for review: ```json theme={null} { "type": "task.healed", "data": { "task_id": "tsk_4f8w9n2b", "healed_task_version_id": "tv_demo_v4", "source_execution_id": "exr_8d2c1fda", "error_explanation": "Submit button moved into a new container.", "fix_suggestion": "Use accessible label selector instead of XPath.", "confidence_score": 0.82, "published_as_draft": true } } ``` `published_as_draft: true` means the healed workflow was saved as a draft — not deployed. Use `healed_task_version_id` in your CI or ops pipeline to review, test, and promote the update when ready. See [Webhook events](/webhooks/events) for the full event catalog. ## Next steps * [Run a Task](/tasks/run-a-task) — execute workflows and inspect execution results * [Code Tasks](/advanced/legacy-tasks#manually-editing-workflow-json) — read draft versions and deploy via API * [Webhook events](/webhooks/events) — automate on `task.healed` # Sensitive Data Masking Source: https://docs.anchorbrowser.io/advanced/sensitive-data-masking Automatically detect and mask sensitive data like passwords, emails, phone numbers, and credit cards in your browser sessions Sensitive data masking automatically detects and hides sensitive information displayed in web pages. It blurs input fields containing passwords, emails, and credit card numbers, and masks sensitive text visible on the page. This is useful for browser automation workflows that handle real user data, recordings, or live views where sensitive information should not be exposed. Sensitive data masking is disabled by default. Enable it when your sessions handle real credentials, financial data, or other PII that should not appear in recordings or live views. ## Quick Start ```javascript node.js theme={null} import AnchorBrowser from 'anchorbrowser'; (async () => { const anchorClient = new AnchorBrowser({apiKey: process.env.ANCHOR_API_KEY}); const session = await anchorClient.sessions.create({ browser: { sensitive_data_mask: { active: true } } }); console.log("Session:", session.data.id); })().catch(console.error); ``` ```python python theme={null} import os from anchorbrowser import Anchorbrowser anchor_client = Anchorbrowser(api_key=os.getenv("ANCHOR_API_KEY")) session = anchor_client.sessions.create( browser={ "sensitive_data_mask": { "active": True } } ) print("Session:", session.data.id) ``` ## What Gets Masked When enabled, the extension automatically detects and masks the following without any additional configuration. ### Input Fields (blurred) The following input fields are visually blurred with `filter: blur(8px)`: * Password fields (`type="password"`) * Email fields (`type="email"`) * Phone fields (`type="tel"`) * Credit card fields (detected by `name` or `autocomplete` attributes containing `card`, `cc`, `cvv`, `cvc`, `expir`) * SSN fields (detected by `name` containing `ssn`, `social_security`) * Token and API key fields (detected by `name` containing `token`, `secret`, `api_key`, `access_key`, `private_key`) * One-time code fields (`autocomplete="one-time-code"`) ### Text Content Sensitive patterns found in visible text on the page are handled in two ways: * **Dedicated elements**: When an element's text is primarily a sensitive value (e.g., `alice@example.com`), the entire element is blurred. * **Inline text**: When a sensitive value is embedded in a larger sentence (e.g., "Contact us at [alice@example.com](mailto:alice@example.com) for help"), only the matched portion is replaced with `****`. Built-in patterns include email addresses, phone numbers, credit card numbers, and long token-like strings (40+ characters). ## Custom CSS Selectors You can specify additional CSS selectors to blur elements that the automatic detection might not cover. Custom selectors can be applied globally or scoped to specific sites. ```javascript node.js theme={null} import AnchorBrowser from 'anchorbrowser'; (async () => { const anchorClient = new AnchorBrowser({apiKey: process.env.ANCHOR_API_KEY}); const session = await anchorClient.sessions.create({ browser: { sensitive_data_mask: { active: true, custom_selectors: [".api-key-display", "#secret-field", "[data-sensitive]"], site_selectors: { "app.example.com": [".account-number", ".routing-number"], "*.bank.com": [".balance", ".ssn-display"] } } } }); console.log("Session:", session.data.id); })().catch(console.error); ``` ```python python theme={null} import os from anchorbrowser import Anchorbrowser anchor_client = Anchorbrowser(api_key=os.getenv("ANCHOR_API_KEY")) session = anchor_client.sessions.create( browser={ "sensitive_data_mask": { "active": True, "custom_selectors": [".api-key-display", "#secret-field", "[data-sensitive]"], "site_selectors": { "app.example.com": [".account-number", ".routing-number"], "*.bank.com": [".balance", ".ssn-display"] } } } ) print("Session:", session.data.id) ``` ### Selector Options | Option | Type | Description | | ------------------ | ---------- | --------------------------------------------------------------------------------------------------------------- | | `custom_selectors` | `string[]` | CSS selectors applied globally across all sites. Matched elements are blurred. | | `site_selectors` | `object` | Per-site CSS selectors. Keys are hostnames (supports `*.` wildcard prefix), values are arrays of CSS selectors. | Site selector keys support wildcard matching: * `"example.com"` matches only `example.com` * `"*.example.com"` matches `app.example.com`, `dashboard.example.com`, etc. ## Custom Regex Patterns For data formats not covered by the built-in patterns, you can define custom regular expressions. Custom patterns follow the same blur-vs-replace logic as built-in patterns. ```javascript node.js theme={null} import AnchorBrowser from 'anchorbrowser'; (async () => { const anchorClient = new AnchorBrowser({apiKey: process.env.ANCHOR_API_KEY}); const session = await anchorClient.sessions.create({ browser: { sensitive_data_mask: { active: true, custom_patterns: [ { regex: "AKIA[0-9A-Z]{16}", mask: "[AWS_KEY]" }, { regex: "ghp_[a-zA-Z0-9]{36}", mask: "[GITHUB_TOKEN]" }, { regex: "sk-[a-zA-Z0-9]{48}" } ] } } }); console.log("Session:", session.data.id); })().catch(console.error); ``` ```python python theme={null} import os from anchorbrowser import Anchorbrowser anchor_client = Anchorbrowser(api_key=os.getenv("ANCHOR_API_KEY")) session = anchor_client.sessions.create( browser={ "sensitive_data_mask": { "active": True, "custom_patterns": [ {"regex": "AKIA[0-9A-Z]{16}", "mask": "[AWS_KEY]"}, {"regex": "ghp_[a-zA-Z0-9]{36}", "mask": "[GITHUB_TOKEN]"}, {"regex": "sk-[a-zA-Z0-9]{48}"} ] } } ) print("Session:", session.data.id) ``` ### Pattern Options | Option | Type | Required | Description | | ------- | -------- | -------- | -------------------------------------------------------- | | `regex` | `string` | Yes | A regular expression pattern to match sensitive data. | | `mask` | `string` | No | Replacement string for matched text. Defaults to `****`. | ## Configuration Reference | Option | Type | Default | Description | | ------------------ | ---------- | ------- | -------------------------------------------------------- | | `active` | `boolean` | `false` | Enable or disable sensitive data masking. | | `custom_selectors` | `string[]` | `[]` | Additional CSS selectors to blur globally. | | `site_selectors` | `object` | `{}` | Per-site CSS selectors keyed by hostname. | | `custom_patterns` | `object[]` | `[]` | Custom regex patterns with optional replacement strings. | ## Related Features * [Recording](/essentials/recording) - Session recordings where sensitive data masking prevents PII exposure * [Browser Live View](/advanced/browser-live-view) - Live view where masked data stays hidden from observers * [Stealth](/essentials/stealth) - Bot detection avoidance (separate concern from data masking) # Session Tags Source: https://docs.anchorbrowser.io/advanced/session-tags Organize and track browser sessions with custom labels ### Categorizing Browser Sessions Session tags allow you to add custom labels to your browser sessions, making it easier to organize, filter, and track sessions across your workflows. Tags are simple strings that you can use to categorize sessions by project, environment, customer, or any other criteria relevant to your use case. For the full list of available options, view the [interactive API documentation](/api-reference/browser-sessions). ### Common Use Cases Tags are particularly useful for: * **Environment tracking**: Label sessions as `production`, `staging`, or `development` * **Customer attribution**: Tag sessions with customer IDs like `customer-12345` * **Project organization**: Group sessions by project name or feature * **Workflow identification**: Mark sessions for specific automation tasks like `form-fill`, `testing`, or `data-extraction` * **Cost allocation**: Track resource usage across teams or departments ### Adding Tags to Sessions You can add tags when creating a new browser session by including the `tags` array in your session configuration. Each tag is a simple string value. ```javascript node.js theme={null} import Anchorbrowser from 'anchorbrowser'; (async () => { const anchorClient = new Anchorbrowser({apiKey: process.env.ANCHOR_API_KEY}); const session = await anchorClient.sessions.create({ session: { tags: ["production", "form-fill", "customer-12345"] } }); console.log("Session created with tags:", session.data.id); })().catch(console.error); ``` ```python python theme={null} import os from anchorbrowser import Anchorbrowser anchor_client = Anchorbrowser(api_key=os.getenv("ANCHOR_API_KEY")) session = anchor_client.sessions.create( session={ "tags": ["production", "form-fill", "customer-12345"] } ) print("Session created with tags:", session.data.id) ``` ### Combining with Other Configuration Tags can be combined with any other session configuration options: ```javascript node.js theme={null} import Anchorbrowser from 'anchorbrowser'; (async () => { const anchorClient = new Anchorbrowser({apiKey: process.env.ANCHOR_API_KEY}); const session = await anchorClient.sessions.create({ session: { tags: ["production", "checkout-flow"], initial_url: "https://example.com", proxy: { active: true, country_code: "us" }, timeout: { max_duration: 30, idle_timeout: 5 } } }); console.log("Session created:", session.data.id); })().catch(console.error); ``` ```python python theme={null} import os from anchorbrowser import Anchorbrowser anchor_client = Anchorbrowser(api_key=os.getenv("ANCHOR_API_KEY")) session = anchor_client.sessions.create( session={ "tags": ["production", "checkout-flow"], "initial_url": "https://example.com", "proxy": { "active": True, "country_code": "us" }, "timeout": { "max_duration": 30, "idle_timeout": 5 } } ) print("Session created:", session.data.id) ``` ### Viewing Tags When you retrieve session information, the tags will be included in the response: ```javascript node.js theme={null} import Anchorbrowser from 'anchorbrowser'; (async () => { const anchorClient = new Anchorbrowser({apiKey: process.env.ANCHOR_API_KEY}); const session = await anchorClient.sessions.retrieve("SESSION_ID"); console.log("Session tags:", session.data.tags); })().catch(console.error); ``` ```python python theme={null} import os from anchorbrowser import Anchorbrowser anchor_client = Anchorbrowser(api_key=os.getenv("ANCHOR_API_KEY")) session = anchor_client.sessions.retrieve("SESSION_ID") print("Session tags:", session.data.tags) ``` ### Best Practices * **Keep tags concise**: Use short, descriptive labels that are easy to read and filter * **Use consistent naming**: Establish a tagging convention across your team (e.g., kebab-case like `project-name`) * **Avoid sensitive data**: Don't include passwords, API keys, or other sensitive information in tags * **Limit tag count**: While there's no hard limit, keeping the number of tags reasonable improves organization # Session Timeout Source: https://docs.anchorbrowser.io/advanced/session-timeout ### Managing Browser Session Lifetime Anchor provides multiple ways to control and terminate browser sessions. In addition to manually stopping sessions via the [stop session API](/api-reference/browser-sessions/end-browser-session), you can configure two types of automatic timeout mechanisms to manage session lifetime effectively. For the full list of available options, view the [interactive api documentation](/api-reference/browser-sessions). ### Timeout Configuration Options The API offers two distinct timeout parameters through the `session.timeout` object to automatically manage browser session termination. #### Idle Timeout The `idle_timeout` parameter automatically terminates sessions after a period of inactivity. This timer starts after the last connection to the browser has disconnected, and any new connection will restart the timer. This includes live view sessions, CDP (Chrome DevTools Protocol) connections, and OS-level control connections. The idle timeout is particularly useful for sessions with unknown length, allowing users to interact with browsers for as long as they need without keeping stale browsers alive unnecessarily. When set to `3` minutes for example, the session will terminate after 3 minutes with no active connections. The default value is `5` minutes, and you can disable automatic termination for idle sessions by setting it to `-1`. #### Maximum Duration The `max_duration` parameter sets a hard limit on total session lifetime. Unlike the idle timeout, this will automatically terminate the session after the specified duration regardless of activity level. This acts as a safety mechanism to ensure sessions don't run indefinitely. The default maximum duration is `180` minutes (3 hours), but you can adjust this based on your needs. Setting `max_duration` to `10` will terminate the session after exactly 10 minutes, whether the browser is actively being used or not. There is no upper limit on how long you can set the maximum duration. ### Implementation Example ```javascript node.js theme={null} import AnchorBrowser from 'anchorbrowser'; (async () => { const anchor_client = new AnchorBrowser({apiKey: process.env.ANCHOR_API_KEY}); const session = await anchor_client.sessions.create({ session: { timeout: { max_duration: 10, // 10 minutes hard limit idle_timeout: 3 // 3 minutes of inactivity } } }); console.log("Session created with timeout configuration:", session.data.id); })().catch(console.error); ``` ```python python theme={null} import os from anchorbrowser import Anchorbrowser anchor_client = Anchorbrowser(api_key=os.getenv("ANCHOR_API_KEY")) session = anchor_client.sessions.create( session={ "timeout": { "max_duration": 10, # 10 minutes hard limit "idle_timeout": 3 # 3 minutes of inactivity } } ) print("Session created with timeout configuration:", session.data.id) ``` In this example, replace `"your_api_key_here"` with your actual API key. The configuration sets a 10-minute hard session limit with `max_duration`, while `idle_timeout` ensures the session terminates after 3 minutes of no active connections. These two timeout mechanisms work independently, so the session will end when whichever condition is met first. # Web Unlocker Source: https://docs.anchorbrowser.io/advanced/web-unlocker Fetch content from any webpage — including bot-protected sites — with a single API call. The Web Unlocker lets you retrieve fully-rendered page content from any URL without managing a browser session. It handles residential proxies, captcha solving, and fingerprinting automatically — so bot-protected sites respond as if you're a real user. ## Quick Start ```javascript node.js theme={null} import AnchorBrowser from 'anchorbrowser'; const anchorClient = new AnchorBrowser({ apiKey: process.env.ANCHOR_API_KEY }); const content = await anchorClient.tools.fetchWebpage({ url: 'https://www.g2.com/products/notion/reviews', }); console.log(content); ``` ```python python theme={null} import os from anchorbrowser import Anchorbrowser anchor_client = Anchorbrowser(api_key=os.getenv('ANCHOR_API_KEY')) content = anchor_client.tools.fetch_webpage( url='https://www.g2.com/products/notion/reviews', ) print(content) ``` ## Request Reference **Endpoint:** `POST https://api.anchorbrowser.io/v1/tools/fetch/webpage` **Headers:** | Header | Value | | ---------------- | ------------------ | | `anchor-api-key` | Your API key | | `Content-Type` | `application/json` | **Body:** The fully-qualified URL to fetch (e.g. `https://www.linkedin.com/company/openai`). **Response codes:** | Code | Meaning | | ----- | --------------------------------------------- | | `200` | Page content returned successfully | | `400` | Invalid request — check the URL and try again | | `422` | Could not reach the requested URL | | `429` | Rate limit exceeded | | `500` | Failed to fetch the requested page | | `504` | The page took too long to load | ## Examples ### Scrape a protected site ```javascript node.js theme={null} import AnchorBrowser from 'anchorbrowser'; const anchorClient = new AnchorBrowser({ apiKey: process.env.ANCHOR_API_KEY }); const content = await anchorClient.tools.fetchWebpage({ url: 'https://www.indeed.com/jobs?q=software+engineer&l=New+York', }); console.log(content); ``` ```python python theme={null} import os from anchorbrowser import Anchorbrowser anchor_client = Anchorbrowser(api_key=os.getenv('ANCHOR_API_KEY')) content = anchor_client.tools.fetch_webpage( url='https://www.indeed.com/jobs?q=software+engineer&l=New+York', ) print(content) ``` ### Fetch multiple pages in parallel ```javascript node.js theme={null} import AnchorBrowser from 'anchorbrowser'; const anchorClient = new AnchorBrowser({ apiKey: process.env.ANCHOR_API_KEY }); const urls = [ 'https://www.crunchbase.com/organization/openai', 'https://www.crunchbase.com/organization/anthropic', 'https://www.crunchbase.com/organization/mistral-ai', ]; const pages = await Promise.all( urls.map((url) => anchorClient.tools.fetchWebpage({ url })), ); pages.forEach((content, index) => { console.log(urls[index], content.slice(0, 200)); }); ``` ```python python theme={null} import os from anchorbrowser import Anchorbrowser anchor_client = Anchorbrowser(api_key=os.getenv('ANCHOR_API_KEY')) urls = [ 'https://www.crunchbase.com/organization/openai', 'https://www.crunchbase.com/organization/anthropic', 'https://www.crunchbase.com/organization/mistral-ai', ] for url in urls: content = anchor_client.tools.fetch_webpage(url=url) print(url, content[:200]) ``` When given a PDF URL, the Web Unlocker returns the file directly as a binary response. To extract text content from a PDF instead, use the [Get webpage content](/api-reference/tools/get-webpage-content) tool — it handles PDF pages the same as any other page. ## Web Unlocker vs. Browser Sessions | | Web Unlocker | Browser Session | | -------- | -------------------------------------- | -------------------------------------------- | | Setup | No session needed | Create a session first | | Stealth | Always on | Configurable | | Speed | No cold start | Session startup time | | Best for | High-volume scraping, one-shot fetches | Multi-step workflows, interactive automation | Use the Web Unlocker when you need content from a single URL. Use a [browser session](/api-reference/browser-sessions/start-browser-session) when you need to navigate, click, fill forms, or maintain state across multiple pages. # CrewAI Source: https://docs.anchorbrowser.io/agent-frameworks/crewai AI agents can leverage browser sessions to complete tasks in the web. The ways to use the browser in CrewAI agent platform are: * **As a Flexible Browser Tool**: Enable the CrewAI agent to explore the web freely using a general-purpose browser tool. * **As a Specific-Flow Tool Defined in CrewAI**: Create custom tools by writing CrewAI code to define specific workflows tailored to your needs. * **As an Automation Task defined in Anchor Browser**: Define tasks using the Anchor platform, which CrewAI agents can run through the Tasks API. ## Quick start - Use Anchor Browser as a flexible browser tool You can connect your CrewAI agent directly to Anchor Browser, allowing it to use browser sessions for various tasks, leveraging the power of browser automation without the need for complex integration code. Below is a quick guide to setting up and using Anchor Browser as a tool for your CrewAI agent. ```python python theme={null} import os from anchorbrowser import Anchorbrowser from crewai import Agent, Task, Crew ANCHOR_API_KEY = os.getenv("ANCHOR_API_KEY") # Create an Anchor Browser tool for CrewAI class AnchorBrowserTool: def __init__(self): self.anchor_client = Anchorbrowser(api_key=ANCHOR_API_KEY) def browse_and_extract(self, url, task_description): # Create a browser session session = self.anchor_client.sessions.create( session={ "max_duration": 30, "idle_timeout": 10 } ) try: # Use the agent to perform the task result = self.anchor_client.agent.task( task_description, task_options={ "url": url, "session_id": session.id } ) return result finally: # Clean up self.anchor_client.sessions.terminate(session.id) # Initialize the tool browser_tool = AnchorBrowserTool() # Create a CrewAI agent that uses Anchor Browser researcher = Agent( role='Web Researcher', goal='Research and extract information from websites', backstory='Expert at gathering information from web sources', tools=[browser_tool.browse_and_extract], verbose=True ) # Create a task research_task = Task( description='Go to news.ycombinator.com and extract the title of the first story', agent=researcher ) # Create and run the crew crew = Crew( agents=[researcher], tasks=[research_task], verbose=True ) result = crew.kickoff() print(result) ``` ```javascript node.js theme={null} const { AnchorClient } = require("anchorbrowser"); const { Agent, Task, Crew } = require("crewai"); class AnchorBrowserTool { constructor() { this.anchorClient = new AnchorClient({ apiKey: process.env.ANCHOR_API_KEY, }); } async browseAndExtract(url, taskDescription) { // Create a browser session const session = await this.anchorClient.sessions.create({ session: { max_duration: 30, idle_timeout: 10 } }); try { // Use the agent to perform the task const result = await this.anchorClient.agent.task( taskDescription, { sessionId: session.id } ); return result; } finally { // Clean up await this.anchorClient.sessions.terminate(session.id); } } } // Initialize the tool const browserTool = new AnchorBrowserTool(); // Create a CrewAI agent that uses Anchor Browser const researcher = new Agent({ role: 'Web Researcher', goal: 'Research and extract information from websites', backstory: 'Expert at gathering information from web sources', tools: [browserTool.browseAndExtract.bind(browserTool)], verbose: true }); // Create a task const researchTask = new Task({ description: 'Go to news.ycombinator.com and extract the title of the first story', agent: researcher }); // Create and run the crew const crew = new Crew({ agents: [researcher], tasks: [researchTask], verbose: true }); const result = await crew.kickoff(); console.log(result); ``` ## Use Anchor Browser as a specific-flow tool defined in CrewAI CrewAI can integrate closely with Anchor Browser to define tools for particular workflows. For example, if you need a customized process to interact with an authenticated application, you can create that flow as a reusable tool that CrewAI agents can use for automation. Here is an example of creating a specific workflow tool using Anchor Browser that can be utilized by CrewAI to automate targeted tasks. ```python python theme={null} import crewai from playwright.sync_api import sync_playwright ANCHOR_API_KEY = "YOUR_ANCHOR_API_KEY" # Replace with your actual API key # Register Anchor Browser as a specific application tool in CrewAI class AnchorSpecificTool(crewai.Tool): name = "SpecificAnchorTool" description = "Custom tool for interacting with a specific application." def __init__(self): super().__init__() anchor_client = Anchorbrowser(api_key=os.getenv("ANCHOR_API_KEY")) with sync_playwright() as p: # Create a browser session session = anchor_client.sessions.create() cdp_url = session.data.cdp_url # Connect to Anchor Browser session browser = p.chromium.connect_over_cdp(cdp_url) page = browser.new_page() page.goto(command['url']) # Perform specific actions based on command if command.get('action') == 'extract': result = page.text_content(command['selector']) browser.close() return result browser.close() # Add custom AnchorBrowserTool to CrewAI agent my_agent = crewai.Agent(name="Web Automation Agent") my_agent.add_tool(AnchorSpecificTool()) # Use the agent to perform a specific task result = my_agent.act({ 'tool': 'SpecificAnchorTool', 'command': { 'url': 'https://example.com', 'action': 'extract', 'selector': 'body' } }) print(result) ``` ```jsx node.js theme={null} const { chromium } = require('playwright-core'); const AnchorClient = require('anchorbrowser'); const crewai = require('crewai'); // Define a custom specific-flow tool in CrewAI that uses Anchor Browser class AnchorSpecificTool { name = "SpecificAnchorTool"; description = "Custom tool for interacting with a specific application."; async run(command) { const anchorClient = new AnchorClient({ apiKey: process.env.ANCHOR_API_KEY, }); // Create a browser session const session = await anchorClient.sessions.create(); const cdp_url = session.data.cdp_url; // Connect to Anchor Browser session const browser = await chromium.connectOverCDP(cdp_url); const page = await browser.newPage(); await page.goto(command.url); // Perform specific actions based on the command if (command.action === 'extract') { const result = await page.textContent(command.selector); await browser.close(); return result; } await browser.close(); } } // Create a CrewAI agent and add the custom specific-flow Anchor Browser tool const agent = new crewai.Agent({ name: "Web Automation Agent" }); const anchorSpecificTool = new AnchorSpecificTool(); agent.addTool(anchorSpecificTool); // Use the tool through the CrewAI agent const result = await agent.act({ tool: 'SpecificAnchorTool', command: { url: 'https://example.com', action: 'extract', selector: 'body' } }); console.log(result); ``` # Custom Integration Source: https://docs.anchorbrowser.io/agent-frameworks/custom-agent-framework Anchor Browser enables integration with custom AI frameworks to empower agents with the ability to navigate and interact with the web effectively. You can leverage browser sessions within your own custom AI framework to automate workflows, explore the web, or interact with web content dynamically. The integration methods for a custom AI framework include: * **As a Flexible Browser Tool**: Utilize Anchor Browser as a general-purpose browser tool that allows your AI agent to freely explore the web and perform dynamic interactions. * **As a Specific-Flow Tool Defined in Your Custom Framework**: Create reusable, custom tools in your AI framework to handle specific workflows or sequences of interactions. * **As an Automation Task defined in Anchor Browser**: Define tasks on the Anchor platform and invoke them from your custom AI framework through the Tasks API. ## Quick Start - Use Anchor Browser as a Flexible Browser Tool Your custom AI framework can directly integrate with Anchor Browser, allowing your agent to interact with the web dynamically. Below is an example of how you can connect your custom AI framework to Anchor Browser, enabling the agent to perform various tasks. ```python python theme={null} import requests from playwright.sync_api import sync_playwright ANCHOR_API_KEY = "YOUR_ANCHOR_API_KEY" # Replace with your actual API key # Define a function to use Anchor Browser as a tool for web-based tasks def use_anchor_browser(command): with sync_playwright() as p: # Connect to Anchor Browser session browser = p.chromium.connect_over_cdp( f"wss://connect.anchorbrowser.io?apiKey={ANCHOR_API_KEY}" ) page = browser.new_page() page.goto(command['url']) # Perform specific actions as needed if command.get('action') == 'search': page.fill(command['search_box'], command['search_text']) page.click(command['search_button']) # Extract and return data if needed result = page.content() browser.close() return result # Example usage in your custom AI framework command = { 'url': 'https://example.com', 'action': 'search', 'search_box': 'input[name="q"]', 'search_text': 'Anchor Browser', 'search_button': 'button[type="submit"]' } result = use_anchor_browser(command) print(result) ``` ```jsx node.js theme={null} import { chromium } from 'playwright-core'; const ANCHOR_API_KEY = process.env.ANCHOR_API_KEY; // Replace with your actual API key stored in environment variables // Define a function to use Anchor Browser as a tool for web-based tasks async function useAnchorBrowser(command) { // Connect to Anchor Browser session const browser = await chromium.connectOverCDP( `wss://connect.anchorbrowser.io?apiKey=${ANCHOR_API_KEY}` ); const page = await browser.newPage(); await page.goto(command.url); // Perform specific actions as needed if (command.action === 'search') { await page.fill(command.search_box, command.search_text); await page.click(command.search_button); } // Extract and return data if needed const result = await page.content(); await browser.close(); return result; } // Example usage in your custom AI framework const command = { url: 'https://example.com', action: 'search', search_box: 'input[name="q"]', search_text: 'Anchor Browser', search_button: 'button[type="submit"]' }; const result = await useAnchorBrowser(command); console.log(result); ``` ## Use Anchor Browser as a Specific-Flow Tool Defined in a Custom Framework Anchor Browser can also be integrated into your custom AI framework to create specific workflow tools. These tools can handle specialized tasks that your agent needs to perform repeatedly, providing consistency and reducing the need for writing duplicate code. Here is an example of creating a specific tool using Anchor Browser that can be utilized by your custom AI framework for automating targeted tasks. ```python python theme={null} import requests from playwright.sync_api import sync_playwright ANCHOR_API_KEY = "YOUR_ANCHOR_API_KEY" # Replace with your actual API key # Define a custom tool for interacting with a specific web application def specific_anchor_tool(command): with sync_playwright() as p: # Connect to Anchor Browser session browser = p.chromium.connect_over_cdp( f"wss://connect.anchorbrowser.io?apiKey={ANCHOR_API_KEY}" ) page = browser.new_page() page.goto(command['url']) # Perform specific actions based on the command if command.get('action') == 'extract': result = page.text_content(command['selector']) browser.close() return result browser.close() # Example usage in your custom AI framework command = { 'url': 'https://example.com', 'action': 'extract', 'selector': '#data' } result = specific_anchor_tool(command) print(result) ``` ```jsx node.js theme={null} import { chromium } from 'playwright-core'; const ANCHOR_API_KEY = process.env.ANCHOR_API_KEY; // Replace with your actual API key stored in environment variables // Define a custom specific-flow tool for interacting with a specific web application async function specificAnchorTool(command) { // Connect to Anchor Browser session const browser = await chromium.connectOverCDP( `wss://connect.anchorbrowser.io?apiKey=${ANCHOR_API_KEY}` ); const page = await browser.newPage(); await page.goto(command.url); // Perform specific actions based on the command if (command.action === 'extract') { const result = await page.textContent(command.selector); await browser.close(); return result; } await browser.close(); } // Example usage in your custom AI framework const command = { url: 'https://example.com', action: 'extract', selector: '#data' }; const result = await specificAnchorTool(command); console.log(result); ``` # LangChain Source: https://docs.anchorbrowser.io/agent-frameworks/langchain AI agents can leverage browser sessions to complete tasks on the web using LangChain, a framework that provides easy integration for AI-driven workflows. Anchor provides [LangChain tools](https://python.langchain.com/docs/integrations/tools/anchor_browser/) that allows you to use Anchor Browser as a tool in your LangChain workflows. The package contains the following tools: * `AnchorContentTool`: Get the content of a web page in markdown format. * `AnchorScreenshotTool`: Take a screenshot of a web page. * `AnchorWebTaskTools`: Perform intelligent web tasks using AI: * Simple - `SimpleAnchorWebTaskTool` * Advanced - `AdvancedAnchorWebTaskTool` See Anchor Browser package for LangChain on [PyPi](https://pypi.org/project/langchain-anchorbrowser/) for more information. ## Quickstart ### Installation Install the `langchain-anchorbrowser` package: ```bash theme={null} pip install langchain-anchorbrowser ``` ### Usage Import and utilize your intended tool. The full list of Anchor Browser available tools see **Tool Features** table in [Anchor Browser tool page](/docs/integrations/tools/anchor_browser) ```python theme={null} from langchain_anchorbrowser import AnchorContentTool # Get Markdown Content for https://www.anchorbrowser.io AnchorContentTool().invoke( {"url": "https://www.anchorbrowser.io", "format": "markdown"} ) ``` ## Additional Resources * [PyPi](https://pypi.org/project/langchain-anchorbrowser) * [Github](https://github.com/anchorbrowser/langchain-anchorbrowser) * [Anchor Browser Docs on LangChain](https://python.langchain.com/docs/integrations/tools/anchor_browser/) # Agentic File Usage Source: https://docs.anchorbrowser.io/agentic-browser-control/agentic-file-usage Upload ZIP files to browser sessions for AI agents to use **Compatibility Note**: Only works with the `browser-use` agent. Not supported with `openai-cua`, `gemini-computer-use`, and `anthropic-cua`. ## Quick Start Upload a ZIP file containing resources that your AI agent can use to complete tasks. The ZIP file is automatically extracted and made available to the agent. ## Example: Upload ZIP File ```javascript node.js theme={null} import Anchorbrowser from 'anchorbrowser'; import JSZip from 'jszip'; const ANCHOR_API_KEY = process.env.ANCHOR_API_KEY; // Initialize Anchor client const anchorClient = new Anchorbrowser({ apiKey: ANCHOR_API_KEY, }); // Create a new session const session = await anchorClient.sessions.create(); console.log('session live view url:', session.data?.live_view_url); const sessionId = session.data?.id; // 1. Create a test ZIP file with content const zip = new JSZip(); zip.file('test.txt', 'Hello from Anchor!\nThis is a test file for the agent.'); const zipBlob = await zip.generateAsync({ type: 'blob' }); const zipFile = new File([zipBlob], 'test-data.zip', { type: 'application/zip' }); console.log(`Uploading file to session...`); // 2. Upload to browser session const fileUploadResult = await anchorClient.sessions.agent.files.upload(sessionId!, { file: zipFile }); console.log('Upload result:', fileUploadResult); // 3. Use uploaded files with AI agent const result = await anchorClient.agent.task('upload a file to the server', { taskOptions: { url: 'https://v0-download-and-upload-text.vercel.app/', }, sessionId: sessionId }); console.log('AI agent result:', result); ``` ```python python theme={null} import os import zipfile import tempfile from anchorbrowser import Anchorbrowser ANCHOR_API_KEY = os.getenv("ANCHOR_API_KEY") # Initialize Anchor client anchor_client = Anchorbrowser(api_key=ANCHOR_API_KEY) # Create a new session session = anchor_client.sessions.create() print('session live view url:', session.data.live_view_url) session_id = session.data.id # 1. Create a test ZIP file with content with tempfile.NamedTemporaryFile(suffix='.zip', delete=False) as temp_zip: with zipfile.ZipFile(temp_zip.name, 'w') as zip_file: zip_file.writestr('test.txt', 'Hello from Anchor!\nThis is a test file for the agent.') # 2. Upload to browser session with open(temp_zip.name, 'rb') as zip_file: result = anchor_client.sessions.agent.files.upload( session_id=session_id, file=zip_file ) # Clean up temporary file os.unlink(temp_zip.name) print('Upload result:', result) # 3. Use uploaded files with AI agent result = anchor_client.agent.task('upload a file to the server', task_options={ "url": 'https://v0-download-and-upload-text.vercel.app/', }, session_id=session_id, ) print('AI agent result:', result) ``` That's it! The agent can now access all uploaded files and use them to complete web tasks. # Perform Web Task Source: https://docs.anchorbrowser.io/agentic-browser-control/ai-task-completion Run natural-language browser tasks with the perform-web-task API and agent.task SDK method. Anchor Browser delivers a state-of-the-art 89% Score on the industry-standard benchmark WebVoyager, leveraging browser-use as a core component of the automation capability.