Configuring sessions
What a session is, the three kinds (a browser, a shell or both), every option you can create it with, the session object it returns, and your plan's limits.
A session is one isolated cloud machine. It runs in its own gVisor sandbox and is never shared with another customer. Inside, it can have:
- a Chrome browser you control over the Chrome DevTools Protocol (see Connect with Playwright);
- a bash shell with Python, Node, ffmpeg, ImageMagick and poppler built in (see Shell);
- the shared /workspace disk, used by both (see File uploads and downloads).
A session needs a browser, a shell, or both: see Three kinds of session. There is one kind of machine, so you never pick an image. Sessions start from a pool of ready machines; the target is under one second from request to a running session.
Create a session
POST /v1/sessions starts a session and returns it with status RUNNING. See Create a session in the API reference.
curl -X POST https://api.boxline.dev/v1/sessions \ -H "x-api-key: $BOXLINE_API_KEY" \ -H "content-type: application/json" \ -d '{"browser": true, "shell": true, "timeout": 900}'import { Boxline } from "@boxline/sdk";const bx = new Boxline();const session = await bx.sessions.create({ browser: true, shell: true, timeout: 900 });console.log(session.id, session.liveUrl);from boxline import Boxlinebx = Boxline()session = bx.sessions.create(browser=True, shell=True, timeout=900)print(session.id, session.live_url)Three kinds of session
What you ask for when you create a session decides what it has. A session always has at least one of a browser and a shell, and always has the /workspace disk.
| Kind | Create it with | connectUrl, liveUrl | terminalUrl |
|---|---|---|---|
| A browser only | {}, the default | set | null |
| A shell only | { "browser": false, "shell": true } | null | set |
| Both | { "shell": true } | set | set |
All three URLs are null unless the session is RUNNING. The kind is fixed when the session is created. GET /v1/sessions filters by it with kind=browser, kind=shell or kind=combined (both); see Find sessions.
A browser only
The default. Chrome, plus /workspace for downloads. Connect Playwright or Puppeteer to connectUrl, or watch it at liveUrl. There is no shell: a command call answers 409 shell_disabled.
curl -X POST https://api.boxline.dev/v1/sessions \ -H "x-api-key: $BOXLINE_API_KEY" \ -H "content-type: application/json" \ -d '{}'import { Boxline } from "@boxline/sdk";const bx = new Boxline();const session = await bx.sessions.create({});console.log(session.connectUrl, session.liveUrl); // terminalUrl is nullfrom boxline import Boxlinebx = Boxline()session = bx.sessions.create()print(session.connect_url, session.live_url) # terminal_url is Nonesession_create{ "shell": false }boxline sessions createA shell only
A bash shell with Python, Node, ffmpeg, ImageMagick and poppler, and /workspace. There is no browser, so connectUrl and liveUrl are null. A call that needs a browser (browser actions, computer use, scripts, export cookies), the live view and the CDP connection answer 409 browser_disabled. Files, events and everything else work as on any session.
curl -X POST https://api.boxline.dev/v1/sessions \ -H "x-api-key: $BOXLINE_API_KEY" \ -H "content-type: application/json" \ -d '{"browser": false, "shell": true}'import { Boxline } from "@boxline/sdk";const bx = new Boxline();const session = await bx.sessions.create({ browser: false, shell: true });console.log(session.terminalUrl); // connectUrl and liveUrl are nullfrom boxline import Boxlinebx = Boxline()session = bx.sessions.create(browser=False, shell=True)print(session.terminal_url) # connect_url and live_url are Nonesession_create{ "browser": false, "shell": true }boxline sessions create --no-browser --shellA browser and a shell
Both, on one machine and one disk: a file the browser downloads lands in /workspace/downloads, where the shell can use it. All three URLs are set. Your plan must include shell sessions.
curl -X POST https://api.boxline.dev/v1/sessions \ -H "x-api-key: $BOXLINE_API_KEY" \ -H "content-type: application/json" \ -d '{"shell": true}'import { Boxline } from "@boxline/sdk";const bx = new Boxline();const session = await bx.sessions.create({ shell: true });console.log(session.connectUrl, session.liveUrl, session.terminalUrl);from boxline import Boxlinebx = Boxline()session = bx.sessions.create(shell=True)print(session.connect_url, session.live_url, session.terminal_url)session_create{ "shell": true }boxline sessions create --shellOptions
Every option is optional. Leave the body out for a browser session with the defaults. An agent run or a task takes these same fields, as its session.
| Field | Default | Description |
|---|---|---|
browser | true | Start Chrome in the session; false makes a session with a shell only (set shell: true too). Instead of true or false, pass { mode, locale, timezone } to match the browser’s clock and language to the proxy’s country or set them yourself; an object always means the session gets a browser. See Browser mode. In Python this object is browser_options. |
shell | false | Add a bash shell. Your plan must include shell sessions. A session needs a browser, a shell or both. |
timeout | 300 | Longest lifetime in seconds. At least 60; the maximum depends on your plan (see limits). |
idleTimeout | none | Stop the session after this many seconds with no activity (stopReason: "idle"), 30 up to its timeout. For keepAlive and shell sessions whose client may crash. See idleTimeout. |
keepAlive | false | Keep a browser-only session running after its last CDP client disconnects. See how long a session lives. |
viewport | 1280×720 | { width, height } of the page, from 200×200 to 3840×2160. It is kept across resumes. Phone sizes work: a page under 500 pixels wide (375, 390, 414, …) gets exactly that width, on every tab, from its first page on. |
userMetadata | {} | Any JSON object; returned on the session. You can search for text in it when you list sessions. |
profile | none | { id, persist }: start with a browser profile; with persist: true, save the browser’s cookies and storage back to it when the session stops. |
recordSession | true | Keep a frame-by-frame recording of the screen, on plans with recordings. See Recordings. |
setup | none | Up to 20 shell commands run in order when the session starts, and again after a resume. Needs shell: true. See Install packages. |
env | none | Environment variables for the session’s shell, { "NAME": "value" }. Needs shell: true. See Environment variables. |
credentials | none | Credentials exported into the shell, by name: a secret as $NAME, a password as $NAME_USERNAME and $NAME_PASSWORD. Needs shell: true. See Credentials. |
proxy | none | Send the session’s traffic through a residential, datacenter or your own proxy, e.g. { "type": "residential", "country": "US" }. See Proxies. |
captcha | the project default ("ask") | When a CAPTCHA waits for a person: "ask" pauses agent runs and plain-English steps until someone solves it in the live view; "ignore" lets them carry on; "solve" (opt-in, paid plans) tries to solve it automatically first, and asks a person when it cannot. Without it, the session gets your project’s default. See CAPTCHAs. |
blockAds | false | Refuse requests to ad and tracker sites, inside the machine. Change it any time with Update a session. See Ad and cookie-banner blocking. |
cookieBanners | "reject" | "reject" answers cookie consent banners with “Reject all” (never “Accept”); "off" leaves them. See Ad and cookie-banner blocking. |
extensions | none | Up to 10 uploaded Chrome extension ids (ext_…), loaded before the session is returned. Set at start only; Hobby plan and up. An extension sees every page in the session. See Extensions. |
The API reference lists every field with its type and allowed values: Create a session.
Some options can change while the session runs, with Update a session. Turning the browser or the shell on or off, viewport, profile, recordSession, setup, extensions, env and credentials are fixed when the session is created.
Ads, cookie banners and extensions
curl -X POST https://api.boxline.dev/v1/sessions \ -H "x-api-key: $BOXLINE_API_KEY" \ -H "content-type: application/json" \ -d '{"blockAds": true, "cookieBanners": "reject", "extensions": ["ext_Q2x9L0aPz7NmK3Jb"]}'import { Boxline } from "@boxline/sdk";const bx = new Boxline(); // reads BOXLINE_API_KEY and BOXLINE_API_URLconst session = await bx.sessions.create({ blockAds: true, // refuse ad and tracker sites cookieBanners: "reject", // the default; "off" leaves banners alone extensions: ["ext_Q2x9L0aPz7NmK3Jb"], // uploaded before (Hobby plan and up)});await session.update({ blockAds: false }); // blockAds and cookieBanners change at any timefrom boxline import Boxlinebx = Boxline() # reads BOXLINE_API_KEY and BOXLINE_API_URLsession = bx.sessions.create( block_ads=True, # refuse ad and tracker sites cookie_banners="reject", # the default; "off" leaves banners alone extensions=["ext_Q2x9L0aPz7NmK3Jb"], # uploaded before (Hobby plan and up))session.update(block_ads=False) # block_ads and cookie_banners change at any timeSee Ad and cookie-banner blocking and Extensions.
Common setups
| You want | Body |
|---|---|
| A browser for a quick script | {} |
| A browser that stays up between connections | { "keepAlive": true, "timeout": 1800 } |
| A browser and a shell with extra packages | { "shell": true, "setup": ["pip install yt-dlp"] } |
| A shell only, no browser | { "browser": false, "shell": true } |
| A shell with settings and a stored API token | { "shell": true, "env": { "REGION": "eu-west" }, "credentials": ["GITHUB_TOKEN"] } |
| A browser in Germany, with a German clock and language | { "proxy": { "type": "residential", "country": "DE" }, "browser": { "mode": "realistic" } } |
| Start signed in, and keep new logins | { "profile": { "id": "<profile-id>", "persist": true } } |
The session object
Every session endpoint returns this object (201 on create):
{ "id": "3f6c2a9e-8b1d-4c7a-9e2f-5d0b7a1c4e88", "status": "RUNNING", "projectId": "…", "region": "…", "browser": true, "shell": false, "keepAlive": false, "timeout": 300, "idleTimeout": null, "createdAt": "2026-09-28T12:00:00.000Z", "startedAt": "2026-09-28T12:00:00.000Z", "expiresAt": "2026-09-28T12:05:00.000Z", "stoppedAt": null, "stopReason": null, "deletesAt": null, "deletedAt": null, "deleteReason": null, "connectUrl": "wss://…/v1/connect?sessionId=3f6c2a9e-8b1d-4c7a-9e2f-5d0b7a1c4e88&token=…", "liveUrl": "https://…/live/3f6c2a9e-8b1d-4c7a-9e2f-5d0b7a1c4e88?token=…", "terminalUrl": null, "proxy": null, "captcha": "ask", "attention": null, "blockAds": false, "blockedRequests": 0, "cookieBanners": "reject", "extensions": [], "browserSettings": { "mode": "standard", "locale": null, "timezone": null }, "viewport": { "width": 1280, "height": 720 }, "trajectoriesEligible": false, "workspacePath": "/workspace", "profileId": null, "profilePersist": false, "userMetadata": {}, "checkpointAt": null, "recoveries": 0, "error": null, "recordSession": true, "hasRecording": false, "setup": [], "setupStatus": "none", "setupError": null, "env": [], "credentials": [], "usage": { "seconds": 0, "costUsd": 0 }}| Field | Description |
|---|---|
id | The session's id (a UUID). Every other session call uses it. |
status | "RUNNING", "STOPPED" (saved, free, can be resumed), "DELETED" (ended for good) or "ERROR" (it never started). |
projectId, region | The project it belongs to, and where it runs. |
browser, shell | What the session was created with: its kind (see Three kinds of session). |
keepAlive, timeout, idleTimeout | Lifetime settings (see Lifecycle): timeout and idleTimeout in seconds, idleTimeout null when off. |
createdAt, startedAt | ISO timestamps. |
expiresAt | When the session will stop by reaching its timeout. |
stoppedAt, stopReason | When and why it stopped: requested, timeout, idle, no_clients, agent_run, task, machine_lost, api_restart (its machine could not be taken back after an API restart), account_recovered (the account's password was reset before its email was confirmed, which stops its sessions), suspended (an admin suspended the organization), spend_limit (the organization or the project reached its monthly spending limit) or out_of_credit (no credit left, when refusing work for that is switched on). Both null while it runs; a resume clears them. See How runs and sessions end. |
deletesAt | When a stopped session will be deleted: stoppedAt plus your plan's retention days. null unless it is STOPPED. See Lifecycle. |
deletedAt, deleteReason | When and why it was deleted: "requested" (you deleted it) or "retention" (its deletesAt passed). null until then. |
connectUrl | Signed Chrome DevTools Protocol WebSocket URL for Playwright or Puppeteer. null without a browser. |
liveUrl | Signed URL of the live view page. Open it in a browser tab to watch or take control. Anyone with it can control the browser. null without a browser. |
terminalUrl | Signed WebSocket URL of the interactive terminal. null without a shell. |
proxy | The session's proxy or proxy rules, with defaults filled in and never a password, or null. |
captcha | "ask", "ignore" or "solve": what happens when a CAPTCHA waits for a person. |
blockAds, blockedRequests | Whether ad and tracker sites are refused, and how many requests were (kept across resumes). |
cookieBanners | "reject" or "off" ("off" for sessions without a browser). |
extensions | The ids of the Chrome extensions the session loaded, or []. |
attention | { type, kind, url, tabId, since, state, reason } while a CAPTCHA on one of the session's pages is open, otherwise null. state is "solving" while it is being solved automatically, or "waiting" when it is a person's turn; reason says why solving was refused or failed. |
browserSettings | { mode, locale, timezone }: the browser mode you asked for and the clock and language actually in force. null means the browser's own default. |
viewport | { width, height } of the page: what you asked for, or 1280×720. Kept across resumes. null without a browser. |
trajectoriesEligible | Whether the session may be used for trajectory datasets. Set once when it starts, from your project's Trajectories setting. |
workspacePath | The shared disk, "/workspace". |
profileId, profilePersist | The browser profile the session started with (or was attached to), or null; and whether it saves its sign-ins back to it (persist: true, or attached), false without one. |
userMetadata | The object you passed at creation, or set later in Update a session. |
checkpointAt | When the platform last saved the session's state automatically (null before the first time). |
recoveries | How many times the session was brought back on a new machine after its machine stopped. |
error | Error message when status is ERROR, otherwise null. |
recordSession, hasRecording | Whether the session records its screen, and whether a recording exists yet. |
setup, setupStatus, setupError | The setup commands, their progress ("none", "running", "done" or "failed") and the error of a failed one. |
env, credentials | The names of the shell's environment variables and of the credentials exported into it. Never their values. |
usage | { seconds, costUsd }: billed running time and its cost so far. |
Reading a session (GET /v1/sessions/:id, a list, or any call that returns it) signs connectUrl, liveUrl and terminalUrl afresh every time. They are null unless the session is RUNNING, and null for a part the session was created without (see Three kinds of session). The SDK’s session.refresh() reads them again.
To change a running session, see Update a session. To change or remove its proxy, or give it a new IP, see Proxies. For what to do when attention is set, see CAPTCHAs. For trajectoriesEligible, see Trajectories. For browserSettings, see Browser mode.
Limits
Your plan sets how many sessions can run at once and the largest timeout you can ask for. Creating (or resuming) a session beyond your plan’s sessions at once returns 429 concurrency_limit; a timeout above your plan’s maximum returns 402 plan_limit. When no machine is free, the start waits for one (see When no machine is free).
Only running sessions count: a stopped session is free and does not count towards your sessions at once. A stopped session is kept for your plan’s retention days, then deleted (see Lifecycle). The plan belongs to your organization, so the running sessions of all its projects count together. A project can also have a lower cap of its own (concurrencyCap): at the cap, a new session gets 429 concurrency_limit with “this project allows N sessions at the same time”. See Organizations, projects and team members.
| Plan | Running sessions | Max timeout | Stopped session kept for |
|---|---|---|---|
| Free | 3 | 900 seconds | 7 days |
| Hobby | 25 | 7,200 seconds | 30 days |
| Startup | 100 | 21,600 seconds | 30 days |
| Scale | 250+ | 86,400 seconds | 90 days |
Your plan also caps the data you can use through the platform’s proxies each month. Once it is used up, asking for one of those proxies returns 402 spend_limit; see Proxies.
Your plan also decides which features you can use, such as a shell, the platform’s proxies, browser mode or automatic CAPTCHA solving. Creating or changing a session with a feature your plan does not include returns 402 feature_not_in_plan. See Plans and features.
You can create up to 120 sessions per minute per project. See Rate limits.
When no machine is free
Sessions start from a pool of ready machines. When none is idle, the start waits while the platform adds one. It waits up to 110 seconds: about 70 seconds when a new server has to start first. Only then do you get 503 no_capacity.
So a burst of sessions starts a little slower instead of failing. The same wait applies when you resume a session, and to the web APIs that open a browser, such as fetch, screenshot and PDF.
- Set your own client’s time limit above 110 seconds. The SDKs use 120 seconds for one request. They also try a create again after a 503 (see Retries in the Node SDK and Python SDK), so a start that finds no machine can take several minutes before it throws.
- After
no_capacity, try again with backoff. Starting fewer sessions at once also helps.
Billing
Sessions are billed per second while they run, with no per-session fee. Each session reports its own running time and cost in usage. The rates, and how shell sessions, proxies and stopped sessions are billed, are on Pricing. Plans are on the pricing page.
Every Boxline session runs in its own sandbox. The security page describes the isolation model.
These docs describe a beta API. Endpoints, fields and SDK methods may change before general availability; breaking changes will be listed in the changelog.