Skip to content

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

POST /v1/sessions admission ready billing starts at readyAt active verbs, tasks, CDP warm or cold keepalive moves the deadline released DELETE, or idle timeout expired TTL or maxLifetimeAt died container failure, still billed to the end readyAt maxLifetimeAt keep-alives may extend the deadline, never past maxLifetimeAt one deadline, held server-side; a heartbeat marks activity and moves nothing
A session's states from creation to release. Billing covers the span from readyAt to the end, on every teardown path.

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

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.

Keep reading

  • The three verbs Observe, act and extract against a live session
  • Persistent profiles A full profile round trip in code
  • Plans and entitlements What sets your maxLifetimeAt ceiling
  • Sessions reference Every field and endpoint in detail