Save browser state snapshots including cookies, localStorage, and session files as reusable profiles. Skip login steps and restore authenticated state instantly across agent runs.
A Browser Profile is a saved snapshot of browser state (cookies, localStorage, and session files) that you can reuse across multiple runs. Profiles let you skip login steps and restore authenticated state instantly.Profiles are ideal when you:
Run the same agent repeatedly with the same account (daily data extraction, scheduled reports)
Want multiple agents to share the same authenticated state
Need to avoid repeated authentication to save time and steps
You can create a blank profile with only name and optional description, then pass that profile to future agent runs. Blank profiles are seeded from the configured default browser profile directory when available, with a minimal loadable profile skeleton as a fallback.When an agent runs with persist_browser_session=true, Skyvern archives the browser state (cookies, storage, session files) after the run completes. This archiving happens asynchronously in the background. Once the archive is ready, you can create a profile from it, then pass that profile to future agent runs to restore the saved state.For browser sessions, profile saving is opt-in: a session archives its state when it ends only if it has generate_browser_profile enabled or was started from a saved profile. See Save a session’s profile.
Create a blank profile when you want a reusable profile ID before any browsing has happened. Create a sourced profile when you want to capture cookies, localStorage, and session files from a workflow run or browser session.
Create an agent with persist_browser_session=true in the agent definition, run it, wait for completion, then create a profile from the run. Session archiving happens asynchronously, so add brief retry logic when creating the profile.
persist_browser_session must be set when creating the agent, not when running it. It is an agent definition property, not a runtime parameter.
import asynciofrom skyvern import Skyvernasync def main(): client = Skyvern(api_key="YOUR_API_KEY") # Step 1: Create a workflow with persist_browser_session=true workflow = await client.create_workflow( json_definition={ "title": "Login to Dashboard", "persist_browser_session": True, # Set here in workflow definition "workflow_definition": { "parameters": [], "blocks": [ { "block_type": "navigation", "label": "login", "url": "https://dashboard.example.com/login", "navigation_goal": "Login with the provided credentials" } ] } } ) print(f"Created workflow: {workflow.workflow_permanent_id}") # Step 2: Run the workflow workflow_run = await client.run_workflow( workflow_id=workflow.workflow_permanent_id, wait_for_completion=True, ) print(f"Workflow completed: {workflow_run.status}") # Step 3: Create profile from the completed run # Retry briefly while session archives asynchronously for attempt in range(10): try: profile = await client.create_browser_profile( name="analytics-dashboard-login", workflow_run_id=workflow_run.run_id, description="Authenticated state for analytics dashboard", ) print(f"Profile created: {profile.browser_profile_id}") break except Exception as e: if "persisted" in str(e).lower() and attempt < 9: await asyncio.sleep(1) continue raiseasyncio.run(main())
Parameters:
Parameter
Type
Description
name
string
Required. Display name for the profile. Must be unique within your organization
workflow_run_id
string
ID of the completed workflow run to create the profile from
You can also create a profile from a closed Browser Session that was set to save its profile. Create the session with generate_browser_profile enabled (or turn it on while the session is alive), close the session, then pass the session ID instead of the workflow run ID.
Sessions do not save their profile by default. If the session did not have generate_browser_profile enabled and was not started from a saved profile, this call fails with a 400 error — retrying does not help. Update API scripts and integrations that create profiles from closed sessions; n8n users may need updated Skyvern nodes. Profiles created from workflow runs (persist_browser_session=true) are unaffected.
For opted-in sessions, the archive uploads asynchronously after the session closes, so keep brief retry logic for the transient “not persisted yet” window.
import asynciofrom skyvern import Skyvernasync def main(): client = Skyvern(api_key="YOUR_API_KEY") # browser_session_id from a closed session created with generate_browser_profile enabled session_id = "pbs_your_session_id" # Create profile from the closed session (retry while archive uploads) for attempt in range(10): try: profile = await client.create_browser_profile( name="dashboard-admin-login", browser_session_id=session_id, description="Admin account for dashboard access", ) print(f"Profile created: {profile.browser_profile_id}") break except Exception as e: if "persisted" in str(e).lower() and attempt < 9: await asyncio.sleep(2) continue raiseasyncio.run(main())
Parameters:
Parameter
Type
Description
name
string
Required. Display name for the profile. Must be unique within your organization
browser_session_id
string
ID of the closed browser session (starts with pbs_). The session must have had generate_browser_profile enabled or been started from a saved profile
Pass browser_profile_id when running an agent to restore the saved state. Skyvern restores cookies, localStorage, and session files before the first step runs.
import asynciofrom skyvern import Skyvernasync def main(): client = Skyvern(api_key="YOUR_API_KEY") # Run workflow with saved profile, no login needed result = await client.run_workflow( workflow_id="wpid_daily_metrics", browser_profile_id="bp_490705123456789012", wait_for_completion=True, ) print(f"Output: {result.output}")asyncio.run(main())
browser_profile_id is supported for agents only. It is not available for standalone tasks via run_task. You also cannot use both browser_profile_id and browser_session_id in the same request.
This walkthrough demonstrates the full profile lifecycle: create an agent that saves browser state, capture that state as a profile, then reuse it in a second agent. Each step shows the code and the actual API response.
1
Create an agent with persist_browser_session
The agent must have persist_browser_session=true so Skyvern archives the browser state after the run.
Archiving happens asynchronously after the run completes, so add retry logic. In practice the archive is usually ready within a few seconds.
for attempt in range(10): try: profile = await client.create_browser_profile( name="hn-browsing-state", workflow_run_id=run.run_id, description="Hacker News cookies and browsing state", ) print(profile.browser_profile_id) # bp_494674399951999772 break except Exception as e: if "persisted" in str(e).lower() and attempt < 9: await asyncio.sleep(2) continue raise
Response
{ "browser_profile_id": "bp_494674399951999772", "organization_id": "o_475582633898688888", "name": "hn-browsing-state", "description": "Hacker News cookies and browsing state", "created_at": "2026-02-12T01:09:18.048208", "modified_at": "2026-02-12T01:09:18.048212", "deleted_at": null}
4
Verify the profile exists
List all profiles or fetch one by ID to confirm it was saved.
# List all profilesprofiles = await client.list_browser_profiles()print(len(profiles)) # 1# Get a single profilefetched = await client.get_browser_profile(profile_id=profile.browser_profile_id)print(fetched.name) # hn-browsing-state
Pass browser_profile_id when running an agent. Skyvern restores the saved cookies, localStorage, and session files before the first block runs. The second agent starts with the browser state from step 2, no repeat navigation needed.
result = await client.run_workflow( workflow_id=data_workflow.workflow_permanent_id, browser_profile_id=profile.browser_profile_id, wait_for_completion=True,)print(result.status) # completed
In a real scenario, step 1 would be a login agent that authenticates with a site. The saved profile then lets all future agents skip the login step entirely.
A browser profile can carry a pinned residential ISP proxy identity so that every run reusing the profile reaches the target site from the same IP and locale. Pinning is opt-in and uses two fields:
proxy_location — set to RESIDENTIAL_ISP (or a GeoTarget object) to opt the profile into a static ISP identity.
proxy_session_id — opaque Skyvern-managed sticky-session key. Omit it to let Skyvern generate one automatically when proxy_location is RESIDENTIAL_ISP.
When a run uses a profile that has a pinned proxy, Skyvern routes traffic through the same residential ISP identity the profile was last associated with, even if the run request does not set proxy_location.
Update the profile with rotate_proxy_session_id: true to ask Skyvern to mint a fresh sticky-session id while keeping the same proxy_location. Use this when the existing IP gets flagged or when you want to start a new identity for the same profile.
Session tokens and cookies expire. Re-run your login agent and create fresh profiles before they go stale. Adding the date to the name makes it easy to track which profile is current.
from datetime import date# Create dated profile after each successful loginprofile = await client.create_browser_profile( name=f"crm-login-{date.today()}", workflow_run_id=new_login_run.run_id,)# Delete old profileawait client.delete_browser_profile(old_profile_id)
To capture state changes during a run (like token refreshes), the agent must have persist_browser_session=true in its definition. This lets you create a fresh profile from each completed run.
from datetime import date# Step 1: Create workflow with persist_browser_session in the definitionworkflow = await client.create_workflow( json_definition={ "title": "Daily Sync", "persist_browser_session": True, # Set here, not in run_workflow "workflow_definition": { "parameters": [], "blocks": [...] } })# Step 2: Run with an existing profileresult = await client.run_workflow( workflow_id=workflow.workflow_permanent_id, browser_profile_id="bp_current", wait_for_completion=True,)# Step 3: Create updated profile from the completed runif should_refresh_profile: new_profile = await client.create_browser_profile( name=f"daily-sync-{date.today()}", workflow_run_id=result.run_id, )