--- title: Browser Sessions subtitle: Persist live browser state across multiple tasks and workflows slug: optimization/browser-sessions --- A **Browser Session** is a live browser instance that persists cookies, local storage, and page state between task or workflow runs. Think of it as keeping a browser tab open. Use sessions when you need back-to-back tasks to share state, human-in-the-loop approval, or real-time agents. --- ## Create a session Start a session with optional configuration for timeout, proxy, browser type, and extensions. ```python Python import asyncio from skyvern import Skyvern async def main(): client = Skyvern(api_key="YOUR_API_KEY") session = await client.create_browser_session( timeout=60, # Max 60 minutes proxy_location="RESIDENTIAL", # US residential proxy browser_type="chrome", # Chrome or Edge extensions=["ad-blocker"], # Optional extensions ) print(f"Session ID: {session.browser_session_id}") print(f"Status: {session.status}") asyncio.run(main()) ``` ```typescript TypeScript import { Skyvern } from "@skyvern/client"; async function main() { const client = new Skyvern({ apiKey: process.env.SKYVERN_API_KEY!, }); const session = await client.createBrowserSession({ timeout: 60, proxy_location: "RESIDENTIAL", browser_type: "chrome", extensions: ["ad-blocker"], }); console.log(`Session ID: ${session.browser_session_id}`); console.log(`Status: ${session.status}`); } main(); ``` ```bash cURL curl -X POST "https://api.skyvern.com/v1/browser_sessions" \ -H "x-api-key: $SKYVERN_API_KEY" \ -H "Content-Type: application/json" \ -d '{ "timeout": 60, "proxy_location": "RESIDENTIAL", "browser_type": "chrome", "extensions": ["ad-blocker"] }' ``` **Parameters:** | Parameter | Type | Description | |-----------|------|-------------| | `timeout` | integer | Session lifetime in minutes. Min: 5, Max: 1440 (24 hours). Default: `60` | | `proxy_location` | string | Geographic proxy location (Cloud only). See [Proxy & Geolocation](/going-to-production/proxy-geolocation) for available options | | `browser_type` | string | Browser type: `chrome` or `msedge` | | `extensions` | array | Extensions to install: `ad-blocker`, `captcha-solver` | **Example response:** ```json { "browser_session_id": "pbs_490705123456789012", "organization_id": "o_485917350850524254", "status": "running", "timeout": 60, "browser_type": "chrome", "extensions": ["ad-blocker"], "vnc_streaming_supported": true, "app_url": "https://app.skyvern.com/browser-session/pbs_490705123456789012", "started_at": "2026-02-01T10:30:03.110Z", "created_at": "2026-02-01T10:30:00.000Z", "modified_at": "2026-02-01T10:30:03.251Z" } ``` **Session statuses:** | Status | Description | |--------|-------------| | `running` | Browser is live and accepting tasks. This is the status returned on creation. The browser launches within seconds. | | `closed` | Session was closed manually or by timeout. No further tasks can run. | Sessions close automatically when the timeout expires, **even if a task is still running**. The timeout countdown begins when the browser launches. Set timeouts with enough margin for your longest expected task. --- ## Run tasks with a session Pass `browser_session_id` to `run_task` to execute tasks in an existing session. Each task continues from where the previous one left off: same page, same cookies, same form data. ```python Python import asyncio from skyvern import Skyvern async def main(): client = Skyvern(api_key="YOUR_API_KEY") # Create session session = await client.create_browser_session(timeout=30) session_id = session.browser_session_id try: # Task 1: Login (wait for completion before continuing) await client.run_task( prompt="Login with username 'support@company.com'", url="https://dashboard.example.com/login", browser_session_id=session_id, wait_for_completion=True, ) # Task 2: Search (already logged in from Task 1) result = await client.run_task( prompt="Find customer with email 'customer@example.com'", browser_session_id=session_id, wait_for_completion=True, ) print(f"Customer: {result.output}") finally: # Always close when done await client.close_browser_session(browser_session_id=session_id) asyncio.run(main()) ``` ```typescript TypeScript import { Skyvern } from "@skyvern/client"; async function main() { const client = new Skyvern({ apiKey: process.env.SKYVERN_API_KEY! }); const session = await client.createBrowserSession({ timeout: 30 }); const sessionId = session.browser_session_id; try { // Task 1: Login (wait for completion) await client.runTask({ body: { prompt: "Login with username 'support@company.com'", url: "https://dashboard.example.com/login", browser_session_id: sessionId, }, waitForCompletion: true, }); // Task 2: Search (reuses login state) const result = await client.runTask({ body: { prompt: "Find customer with email 'customer@example.com'", browser_session_id: sessionId, }, waitForCompletion: true, }); console.log(`Customer: ${JSON.stringify(result.output)}`); } finally { await client.closeBrowserSession(sessionId); } } main(); ``` ```bash cURL # Create session SESSION_ID=$(curl -s -X POST "https://api.skyvern.com/v1/browser_sessions" \ -H "x-api-key: $SKYVERN_API_KEY" \ -H "Content-Type: application/json" \ -d '{"timeout": 30}' | jq -r '.browser_session_id') echo "Session: $SESSION_ID" # Task 1: Login RUN_ID=$(curl -s -X POST "https://api.skyvern.com/v1/run/tasks" \ -H "x-api-key: $SKYVERN_API_KEY" \ -H "Content-Type: application/json" \ -d "{ \"prompt\": \"Login with username 'support@company.com'\", \"url\": \"https://dashboard.example.com/login\", \"browser_session_id\": \"$SESSION_ID\" }" | jq -r '.run_id') # Poll until complete while true; do STATUS=$(curl -s "https://api.skyvern.com/v1/runs/$RUN_ID" \ -H "x-api-key: $SKYVERN_API_KEY" | jq -r '.status') [ "$STATUS" != "created" ] && [ "$STATUS" != "queued" ] && [ "$STATUS" != "running" ] && break sleep 5 done # Task 2: Search (reuses login state) curl -s -X POST "https://api.skyvern.com/v1/run/tasks" \ -H "x-api-key: $SKYVERN_API_KEY" \ -H "Content-Type: application/json" \ -d "{ \"prompt\": \"Find customer with email 'customer@example.com'\", \"browser_session_id\": \"$SESSION_ID\" }" # Close session when done curl -s -X POST "https://api.skyvern.com/v1/browser_sessions/$SESSION_ID/close" \ -H "x-api-key: $SKYVERN_API_KEY" ``` --- ## Run workflows with a session Pass `browser_session_id` to `run_workflow` to execute a workflow in an existing session. This is useful when you need to run a predefined workflow but want it to continue from your current browser state. ```python Python import asyncio from skyvern import Skyvern async def main(): client = Skyvern(api_key="YOUR_API_KEY") # Create session session = await client.create_browser_session(timeout=60) session_id = session.browser_session_id try: # First, login manually via a task await client.run_task( prompt="Login with username 'admin@company.com'", url="https://app.example.com/login", browser_session_id=session_id, wait_for_completion=True, ) # Then run a workflow in the same session (already logged in) result = await client.run_workflow( workflow_id="wf_export_monthly_report", browser_session_id=session_id, wait_for_completion=True, ) print(f"Workflow completed: {result.status}") finally: await client.close_browser_session(browser_session_id=session_id) asyncio.run(main()) ``` ```typescript TypeScript import { Skyvern } from "@skyvern/client"; async function main() { const client = new Skyvern({ apiKey: process.env.SKYVERN_API_KEY! }); const session = await client.createBrowserSession({ timeout: 60 }); const sessionId = session.browser_session_id; try { // First, login manually via a task await client.runTask({ body: { prompt: "Login with username 'admin@company.com'", url: "https://app.example.com/login", browser_session_id: sessionId, }, waitForCompletion: true, }); // Then run a workflow in the same session (already logged in) const result = await client.runWorkflow({ body: { workflow_id: "wf_export_monthly_report", browser_session_id: sessionId, }, waitForCompletion: true, }); console.log(`Workflow completed: ${result.status}`); } finally { await client.closeBrowserSession(sessionId); } } main(); ``` ```bash cURL # Create session SESSION_ID=$(curl -s -X POST "https://api.skyvern.com/v1/browser_sessions" \ -H "x-api-key: $SKYVERN_API_KEY" \ -H "Content-Type: application/json" \ -d '{"timeout": 60}' | jq -r '.browser_session_id') # Login via a task RUN_ID=$(curl -s -X POST "https://api.skyvern.com/v1/run/tasks" \ -H "x-api-key: $SKYVERN_API_KEY" \ -H "Content-Type: application/json" \ -d "{ \"prompt\": \"Login with username 'admin@company.com'\", \"url\": \"https://app.example.com/login\", \"browser_session_id\": \"$SESSION_ID\" }" | jq -r '.run_id') # Poll until login completes while true; do STATUS=$(curl -s "https://api.skyvern.com/v1/runs/$RUN_ID" \ -H "x-api-key: $SKYVERN_API_KEY" | jq -r '.status') [ "$STATUS" != "created" ] && [ "$STATUS" != "queued" ] && [ "$STATUS" != "running" ] && break sleep 5 done # Run workflow in the same session (already logged in) curl -s -X POST "https://api.skyvern.com/v1/run/workflows" \ -H "x-api-key: $SKYVERN_API_KEY" \ -H "Content-Type: application/json" \ -d "{ \"workflow_id\": \"wf_export_monthly_report\", \"browser_session_id\": \"$SESSION_ID\" }" # Close session when done curl -s -X POST "https://api.skyvern.com/v1/browser_sessions/$SESSION_ID/close" \ -H "x-api-key: $SKYVERN_API_KEY" ``` You cannot use both `browser_session_id` and `browser_profile_id` in the same request. Choose one or the other. --- ## Close a session Close a session to release resources and stop billing. The browser shuts down immediately. ```python Python await client.close_browser_session( browser_session_id="pbs_490705123456789012" ) ``` ```typescript TypeScript await client.closeBrowserSession("pbs_490705123456789012"); ``` ```bash cURL curl -X POST "https://api.skyvern.com/v1/browser_sessions/pbs_490705123456789012/close" \ -H "x-api-key: $SKYVERN_API_KEY" ``` **Always close sessions when done.** Active sessions continue billing even when idle. Use try/finally blocks to ensure cleanup. ```python Python try: session = await client.create_browser_session(timeout=30) session_id = session.browser_session_id # Do work... await client.run_task( prompt="...", browser_session_id=session_id, wait_for_completion=True, ) finally: # Always close, even if task fails await client.close_browser_session(browser_session_id=session_id) ``` ```typescript TypeScript const session = await client.createBrowserSession({ timeout: 30 }); const sessionId = session.browser_session_id; try { await client.runTask({ body: { prompt: "...", browser_session_id: sessionId, }, waitForCompletion: true, }); } finally { // Always close, even if task fails await client.closeBrowserSession(sessionId); } ``` --- ## Example: Human-in-the-loop A shopping bot that pauses for human approval before completing a purchase. ```python Python import asyncio from skyvern import Skyvern async def shopping_with_approval(): client = Skyvern(api_key="YOUR_API_KEY") session = await client.create_browser_session(timeout=15) session_id = session.browser_session_id try: # Step 1: Add to cart await client.run_task( prompt="Find wireless headphones under $100, add top result to cart", url="https://shop.example.com", browser_session_id=session_id, wait_for_completion=True, ) # Step 2: Wait for human approval approval = input("Approve purchase? (yes/no): ") if approval.lower() == "yes": # Step 3: Checkout (cart persists from Step 1) result = await client.run_task( prompt="Complete checkout and confirm order", browser_session_id=session_id, wait_for_completion=True, ) print(f"Order placed: {result.output}") else: print("Purchase cancelled") finally: await client.close_browser_session(browser_session_id=session_id) asyncio.run(shopping_with_approval()) ``` ```typescript TypeScript import { Skyvern } from "@skyvern/client"; import * as readline from "readline"; async function shoppingWithApproval() { const client = new Skyvern({ apiKey: process.env.SKYVERN_API_KEY! }); const session = await client.createBrowserSession({ timeout: 15 }); const sessionId = session.browser_session_id; try { // Step 1: Add to cart await client.runTask({ body: { prompt: "Find wireless headphones under $100, add top result to cart", url: "https://shop.example.com", browser_session_id: sessionId, }, waitForCompletion: true, }); // Step 2: Wait for human approval const rl = readline.createInterface({ input: process.stdin, output: process.stdout }); const approval = await new Promise((resolve) => rl.question("Approve purchase? (yes/no): ", resolve) ); rl.close(); if (approval.toLowerCase() === "yes") { // Step 3: Checkout (cart persists from Step 1) const result = await client.runTask({ body: { prompt: "Complete checkout and confirm order", browser_session_id: sessionId, }, waitForCompletion: true, }); console.log(`Order placed: ${JSON.stringify(result.output)}`); } else { console.log("Purchase cancelled"); } } finally { await client.closeBrowserSession(sessionId); } } shoppingWithApproval(); ``` The browser maintains the cart contents during the approval pause. No state is lost. --- ## Best practices ### Set appropriate timeouts Sessions bill while open, so match the timeout to your use case. A task typically completes in 30 to 90 seconds, so a 10-minute timeout covers most multi-step sequences with margin. Human-in-the-loop flows need longer timeouts to account for wait time. ```python Python # Quick multi-step task (2-3 tasks back to back) session = await client.create_browser_session(timeout=10) # Human-in-the-loop with wait time for approval session = await client.create_browser_session(timeout=60) # Long-running agent that monitors a dashboard session = await client.create_browser_session(timeout=480) # 8 hours ``` ```typescript TypeScript // Quick multi-step task (2-3 tasks back to back) const session1 = await client.createBrowserSession({ timeout: 10 }); // Human-in-the-loop with wait time for approval const session2 = await client.createBrowserSession({ timeout: 60 }); // Long-running agent that monitors a dashboard const session3 = await client.createBrowserSession({ timeout: 480 }); // 8 hours ``` ```bash cURL # Quick multi-step task (2-3 tasks back to back) curl -s -X POST "https://api.skyvern.com/v1/browser_sessions" \ -H "x-api-key: $SKYVERN_API_KEY" \ -H "Content-Type: application/json" \ -d '{"timeout": 10}' # Human-in-the-loop with wait time for approval curl -s -X POST "https://api.skyvern.com/v1/browser_sessions" \ -H "x-api-key: $SKYVERN_API_KEY" \ -H "Content-Type: application/json" \ -d '{"timeout": 60}' # Long-running agent that monitors a dashboard curl -s -X POST "https://api.skyvern.com/v1/browser_sessions" \ -H "x-api-key: $SKYVERN_API_KEY" \ -H "Content-Type: application/json" \ -d '{"timeout": 480}' ``` ### Use workflows for predetermined sequences If your steps don't need pauses between them, a workflow runs them in a single browser instance without the overhead of creating and managing a session. Each task in a session incurs its own startup cost, while workflow blocks share one browser. ```python Python # Less efficient: multiple tasks in a session (each task has startup overhead) session = await client.create_browser_session() await client.run_task(prompt="Step 1", browser_session_id=session.browser_session_id, wait_for_completion=True) await client.run_task(prompt="Step 2", browser_session_id=session.browser_session_id, wait_for_completion=True) # More efficient: single workflow (blocks share one browser, no inter-task overhead) await client.run_workflow(workflow_id="wf_abc", wait_for_completion=True) ``` ```typescript TypeScript // Less efficient: multiple tasks in a session (each task has startup overhead) const session = await client.createBrowserSession({}); await client.runTask({ body: { prompt: "Step 1", browser_session_id: session.browser_session_id }, waitForCompletion: true }); await client.runTask({ body: { prompt: "Step 2", browser_session_id: session.browser_session_id }, waitForCompletion: true }); // More efficient: single workflow (blocks share one browser, no inter-task overhead) await client.runWorkflow({ body: { workflow_id: "wf_abc" }, waitForCompletion: true }); ``` ### Choose the right browser type Chrome has the widest compatibility. Use Edge only when a site requires or detects it specifically. ```python Python # Chrome (default) - widest compatibility session = await client.create_browser_session(browser_type="chrome") # Edge - for sites that require or fingerprint Edge session = await client.create_browser_session(browser_type="msedge") ``` ```typescript TypeScript // Chrome (default) - widest compatibility const chromeSession = await client.createBrowserSession({ browser_type: "chrome" }); // Edge - for sites that require or fingerprint Edge const edgeSession = await client.createBrowserSession({ browser_type: "msedge" }); ``` ```bash cURL # Chrome (default) - widest compatibility curl -s -X POST "https://api.skyvern.com/v1/browser_sessions" \ -H "x-api-key: $SKYVERN_API_KEY" \ -H "Content-Type: application/json" \ -d '{"browser_type": "chrome"}' # Edge - for sites that require or fingerprint Edge curl -s -X POST "https://api.skyvern.com/v1/browser_sessions" \ -H "x-api-key: $SKYVERN_API_KEY" \ -H "Content-Type: application/json" \ -d '{"browser_type": "msedge"}' ``` ### Use extensions strategically Extensions add startup time, so only enable them when needed. The ad-blocker removes overlay ads that can interfere with automation. The captcha-solver handles CAPTCHAs automatically but is only available on Cloud. ```python Python # Block ads that overlay content and interfere with clicks session = await client.create_browser_session(extensions=["ad-blocker"]) # Auto-solve captchas (Cloud only) session = await client.create_browser_session(extensions=["captcha-solver"]) ``` ```typescript TypeScript // Block ads that overlay content and interfere with clicks const adBlockSession = await client.createBrowserSession({ extensions: ["ad-blocker"] }); // Auto-solve captchas (Cloud only) const captchaSession = await client.createBrowserSession({ extensions: ["captcha-solver"] }); ``` ```bash cURL # Block ads that overlay content and interfere with clicks curl -s -X POST "https://api.skyvern.com/v1/browser_sessions" \ -H "x-api-key: $SKYVERN_API_KEY" \ -H "Content-Type: application/json" \ -d '{"extensions": ["ad-blocker"]}' # Auto-solve captchas (Cloud only) curl -s -X POST "https://api.skyvern.com/v1/browser_sessions" \ -H "x-api-key: $SKYVERN_API_KEY" \ -H "Content-Type: application/json" \ -d '{"extensions": ["captcha-solver"]}' ``` --- ## Sessions vs Profiles Skyvern also offers [Browser Profiles](/optimization/browser-profiles), saved snapshots of browser state (cookies, storage, session files) that you can reuse across days or weeks. Choose based on your use case: | Aspect | Browser Session | Browser Profile | |--------|----------------|-----------------| | **What it is** | Live browser instance | Saved snapshot of browser state | | **Lifetime** | Minutes to hours | Days to months | | **State** | Current page, cookies, open connections | Cookies, storage, session files | | **Billing** | Charged while open | No cost when not in use | | **Best for** | Back-to-back tasks, human-in-the-loop, real-time agents | Repeated logins, scheduled workflows, shared auth state | You can create a [Browser Profile](/optimization/browser-profiles) from a completed session to save its authenticated state for future reuse. --- ## Next steps Save session state for reuse across days Optimize costs with max_steps and efficient prompts