Persisting State
The Session REST API creates long-lived browser sessions that maintain cookies, localStorage, and other browser state across multiple BQL query runs. State persists even after you disconnect and reconnect to the same session. Depending on your plan, session data can persist for up to 90 days.
Comparison with BQL Reconnect
The Session API and the BQL reconnect mutation both maintain browser continuity, but they work differently.
| Feature | Session API + BQL | BQL Reconnect |
|---|---|---|
| Session Creation | Explicit via REST API | Implicit with first query |
| Lifecycle Control | Programmatic start/stop | Timeout-based only |
| State Management | Persistent across disconnections | Requires active connection |
| Proxy Configuration | Set once, applies to all queries | Per-query configuration |
| Stealth Requirement | Required for BQL support | Available for all BQL queries |
Persisting State Workflow
Create a Session
POST to
/sessionwithstealth: true. Save the returned object — it contains the URLs you need for BQL queries and session cleanup.Stealth Sessions RequiredbrowserQLis returned for all sessions, but BQL queries return a400error unless the session was created withstealth: true.- JavaScript
- Python
- cURL
const response = await fetch(
"https://production-sfo.browserless.io/session?token=YOUR_API_TOKEN_HERE",
{
method: "POST",
headers: { "Content-Type": "application/json" },
body: JSON.stringify({
ttl: 300000,
stealth: true,
}),
}
);
if (!response.ok) {
throw new Error(`Failed to create session: ${response.status}`);
}
const session = await response.json();
console.log("Session created:", session.id);import requests
response = requests.post(
"https://production-sfo.browserless.io/session",
params={"token": "YOUR_API_TOKEN_HERE"},
json={
"ttl": 300000,
"stealth": True,
},
)
response.raise_for_status()
session = response.json()
print("Session created:", session["id"])curl -X POST "https://production-sfo.browserless.io/session?token=YOUR_API_TOKEN_HERE" \
-H "Content-Type: application/json" \
-d '{
"ttl": 300000,
"stealth": true
}'Run BQL Queries
POST BQL mutations to
session.browserQL. This is a fully-qualified URL returned for all sessions that inherits all session properties (proxy, stealth mode, etc.). BQL queries against it require the session to have been created withstealth: true.- JavaScript
- Python
const query = `
mutation SetDarkMode {
goto(url: "https://docs.browserless.io/", waitUntil: networkIdle) {
status
}
click(selector: "div.toggle_vylO.colorModeToggle_x44X > button") {
time
}
}
`;
const bqlResponse = await fetch(session.browserQL, {
method: "POST",
headers: { "Content-Type": "application/json" },
body: JSON.stringify({ query }),
});
const result = await bqlResponse.json();
console.log("Dark mode toggled:", result.data);query = """
mutation SetDarkMode {
goto(url: "https://docs.browserless.io/", waitUntil: networkIdle) {
status
}
click(selector: "div.toggle_vylO.colorModeToggle_x44X > button") {
time
}
}
"""
bql_response = requests.post(
session["browserQL"],
json={"query": query},
)
result = bql_response.json()
print("Dark mode toggled:", result["data"])Stop the Session
You can POST to
session.browserQLas many times as needed across multiple script runs — state persists across all of them. Only send a DELETE tosession.stopwhen you want to permanently discard the session data. Sessions also expire automatically when their TTL elapses.- JavaScript
- Python
- cURL
const stopResponse = await fetch(session.stop, { method: "DELETE" });
if (stopResponse.ok) {
console.log("Session stopped.");
}stop_response = requests.delete(session["stop"])
stop_response.raise_for_status()
print("Session stopped.")# Use the stop URL from the session creation response
curl -X DELETE "SESSION_STOP_URL_HERE"
Complete Example
This example creates a session, toggles dark mode in one BQL query, then verifies the theme preference persists in a second query against the same session.
- JavaScript
- Python
async function main() {
// 1. Create a stealth session
const response = await fetch(
"https://production-sfo.browserless.io/session?token=YOUR_API_TOKEN_HERE",
{
method: "POST",
headers: { "Content-Type": "application/json" },
body: JSON.stringify({ ttl: 300000, stealth: true }),
}
);
if (!response.ok) {
throw new Error(`Failed to create session: ${response.status}`);
}
const session = await response.json();
console.log("Session created:", session.id);
// 2. First query: navigate and toggle dark mode
const toggleQuery = `
mutation SetDarkMode {
goto(url: "https://docs.browserless.io/", waitUntil: networkIdle) {
status
}
click(selector: "div.toggle_vylO.colorModeToggle_x44X > button") {
time
}
}
`;
const toggleResponse = await fetch(session.browserQL, {
method: "POST",
headers: { "Content-Type": "application/json" },
body: JSON.stringify({ query: toggleQuery }),
});
const toggleResult = await toggleResponse.json();
console.log("Dark mode toggled:", toggleResult.data.click.time);
// 3. Second query: verify theme persists (reuse same session.browserQL URL)
const verifyQuery = `
mutation VerifyTheme {
goto(url: "https://docs.browserless.io/", waitUntil: networkIdle) {
status
}
theme: evaluate(content: "return localStorage.getItem('theme');") {
value
}
}
`;
const verifyResponse = await fetch(session.browserQL, {
method: "POST",
headers: { "Content-Type": "application/json" },
body: JSON.stringify({ query: verifyQuery }),
});
const verifyResult = await verifyResponse.json();
console.log("Theme persisted:", verifyResult.data.theme.value);
// 4. Clean up
await fetch(session.stop, { method: "DELETE" });
console.log("Session stopped.");
}
main().catch(console.error);
import requests
TOKEN = "YOUR_API_TOKEN_HERE"
# 1. Create a stealth session
response = requests.post(
"https://production-sfo.browserless.io/session",
params={"token": TOKEN},
json={"ttl": 300000, "stealth": True},
)
response.raise_for_status()
session = response.json()
print("Session created:", session["id"])
# 2. First query: navigate and toggle dark mode
toggle_query = """
mutation SetDarkMode {
goto(url: "https://docs.browserless.io/", waitUntil: networkIdle) {
status
}
click(selector: "div.toggle_vylO.colorModeToggle_x44X > button") {
time
}
}
"""
toggle_response = requests.post(
session["browserQL"],
json={"query": toggle_query},
)
toggle_result = toggle_response.json()
print("Dark mode toggled:", toggle_result["data"]["click"]["time"])
# 3. Second query: verify theme persists (reuse same session.browserQL URL)
verify_query = """
mutation VerifyTheme {
goto(url: "https://docs.browserless.io/", waitUntil: networkIdle) {
status
}
theme: evaluate(content: "return localStorage.getItem('theme');") {
value
}
}
"""
verify_response = requests.post(
session["browserQL"],
json={"query": verify_query},
)
verify_result = verify_response.json()
print("Theme persisted:", verify_result["data"]["theme"]["value"])
# 4. Clean up
requests.delete(session["stop"])
print("Session stopped.")
Session Response Schema
A successful POST /session returns a JSON object with these fields:
| Property | Type | Description |
|---|---|---|
id | string | Unique session identifier |
connect | string | WebSocket URL for CDP-based libraries (Puppeteer, Playwright). Token is pre-embedded. |
browserQL | string | URL for running BQL queries against this session. Returned for all sessions. Requires stealth: true to use. Token is pre-embedded. |
stop | string | URL for session termination via DELETE request. Token is pre-embedded. |
ttl | number | Session time-to-live in milliseconds |
Persisted Session Data Duration
Session data (cookies, localStorage, cache) persists for the duration of the session TTL, up to the plan maximum:
| Plan | Maximum Session Lifetime |
|---|---|
| Free | 1 day |
| Prototyping | 7 days |
| Starter | 30 days |
| Scale | 90 days |
| Enterprise | Custom |
You can also limit persistence duration by passing a lower ttl value to the session API.
Session Configuration Options
All session configuration options apply to both WebSocket connections and BQL queries:
| Parameter | Type | Default | Description |
|---|---|---|---|
ttl | number | 300000 | Time-to-live in milliseconds. Max varies by plan. |
stealth | boolean | false | Required for BQL support. Enables stealth mode. |
headless | boolean | true | Run browser in headless mode. |
browser | string | 'chromium' | Browser type: 'chromium' or 'chrome'. |
blockAds | boolean | false | Enable ad blocking. |
args | string[] | [] | Additional Chrome launch arguments. |
proxy | object | null | Proxy configuration. When set, applies to all BQL queries and WebSocket connections for this session. See proxy docs for the full configuration shape. |
Best Practices
-
Store session URLs together. Save
session.id,session.browserQL, andsession.stopas a unit after session creation. You need all three for queries, cleanup, and debugging. -
Delete sessions when done. Send a DELETE to
session.stopwhen your workflow completes. Relying on TTL expiry alone wastes resources and can hit session limits. -
Handle expired sessions. If a POST to
session.browserQLreturns a non-2xx status, the session has likely expired or been deleted. Create a new session and retry.