Session lifecycle: states, deadlines and billing
How a session moves from pending to released, when the billing clock starts, and why a deadline and an idle timeout are two different numbers.
Last updated:
The lifecycle at a glance
What a session is
A Browserberg session is a real Chromium browser in its own isolated, hardened container — started for you, torn down when the session ends, never shared with anyone else's work. The container is a security boundary: everything the loaded pages could touch dies with it.
The session record tracks a status of pending, ready, releasing, released or failed. That enum is open — a value you don't recognise belongs in your default branch, not in an error handler.
Billing starts at readyAt, never at admission: queue time and container start-up cost you nothing. A warm start claims a browser that is already running and is typically ready within a second; a cold start builds the container first and takes several seconds. The warmStart flag on the record tells you which one you got.
Create options
Every field on `POST /v1/sessions` is optional — `{}` is a valid body. Requests are strict: a misspelled field is refused with a 400, never silently ignored.
| Name | Type | Description |
|---|---|---|
shape
|
object |
Viewport, locale, timezone, headless flag and allowlisted extra Chromium arguments. Fixed for the life of the session.
Default: 1440×900, de-DE, Europe/Berlin, headless
|
profileId
|
string | Attaches a persistent profile. Naming an id that does not exist yet creates the profile, quota-checked against your plan. |
credentialIds
|
string[] | Vault credentials this session may release — each one only on the site it was granted for. |
egress
|
object | allowDomains and denyDomains lists restricting where the browser may connect. |
recording
|
object |
Session recording. On by default, kept for 24 hours.
Default: enabled, 24 h retention
|
ttlSeconds
|
integer |
Initial lifetime, 30 to 259200 seconds. Extendable by keep-alive, up to maxLifetimeAt.
Default: 900
|
keepAlive.idleTimeoutSeconds
|
integer | Releases the session after this long with no activity — independent of any deadline. |
keepAlive.checkpointSeconds
|
integer | Interval at which the session's state is checkpointed; checkpointedAt on the record shows the last one. |
poolId
|
string | Claims a browser from a reserved pool instead of a cold start. The shape must match the pool's; a mismatch is refused. |
metadata
|
object | Free-form key-value data, echoed back on the session record. |
Deadlines are not idle timeouts
A deadline and an idle timeout are different numbers, and one value cannot play both roles. keepAlive.idleTimeoutSeconds reclaims a session nobody is using — it watches activity, not the clock. expiresAt and maxLifetimeAt bound even a busy session: the first is the current deadline a keep-alive can move, the second is the hard ceiling your plan set at creation, and no keep-alive ever moves it.
When a session ends, shutdownReason says why. It is an open enum as well — 1.2.0 added idle_timeout to it — so route values you have never seen through a default branch instead of matching exhaustively.
Extending a session
A keep-alive asks for more time, and the response reports what was actually granted. `clampedToMaxLifetime` means plan a fresh session; `clampedToPlanLimit` means this one extension was trimmed — ask again later.
Requires: api-key
BASE="https://browserberg.com"
AUTH="Authorization: Bearer $BROWSERBERG_API_KEY"
JSON="Content-Type: application/json"
ID=$(curl -s -X POST "$BASE/v1/sessions" -H "$AUTH" -H "$JSON" -d '{"ttlSeconds": 120}' \
| python3 -c 'import json,sys; print(json.load(sys.stdin)["session"]["id"])')
# Ask for another hour. The response says what actually happened: an extension
# may be trimmed to the plan's ceiling, and maxLifetimeAt never moves.
curl -s -X POST "$BASE/v1/sessions/$ID/keepalive" -H "$AUTH" -H "$JSON" \
-d '{"ttlSeconds": 3600}' \
| python3 -c 'import json,sys; r = json.load(sys.stdin); print(json.dumps({
"expiresAt": r["session"]["expiresAt"],
"maxLifetimeAt": r["session"]["maxLifetimeAt"],
"clampedToMaxLifetime": r["clampedToMaxLifetime"],
"clampedToPlanLimit": r["clampedToPlanLimit"]}, indent=2))'
curl -s -X DELETE "$BASE/v1/sessions/$ID" -H "$AUTH" > /dev/null
{
"expiresAt": "2026-01-01T00:00:00.000Z",
"maxLifetimeAt": "2026-01-01T00:00:00.000Z",
"clampedToMaxLifetime": false,
"clampedToPlanLimit": false
}
Profiles carry state forward
A profile carries browser state — cookies, storage, logged-in sessions — from one session to the next. Name a profileId at creation: the state is captured at teardown, encrypted with your tenant's key, and restored into the next session that names the same id. The persistent profiles guide walks through a full round trip.