# Sessions

Source: https://docs.lookover.ai/webagent/concepts/sessions

> A session is one cloud browser. How it starts, how long it lives and how it ends.

A session is one browser that webagent runs for you in the cloud. You open it, give it work (a goal, a skill, plain actions), and close it. Everything else, such as [tasks](/concepts/tasks), [live view](/concepts/live-view) and [evidence](/concepts/evidence), belongs to a session.

Session ids look like `sess_us_…`. The part after `sess_` is the region.

## Open a session [#open-a-session]

You get a session in three ways:

- Call the `create_session` MCP tool, or `POST /v1/sessions`. You choose the options and keep the session until you stop it. See [Tools](/mcp/tools#create_session).
- Call `run_task` without a `sessionId`. The tool opens a session, runs the goal and closes the session after the task unless `keepOpen` is `true`. It takes the common options itself: `record`, `headers`, `proxyUrl`, `allowHosts` and `credentials`.
- Run a skill (including `smoke_test`), or a quick action such as `scrape`. Each run gets its own session. You do not manage it.

A session opens in Chromium. Firefox and WebKit are not available yet. The create call takes options such as `startUrl`, `device`, `viewport`, `record`, `headers`, `proxyUrl`, `locale` and `timezone`. The full list is in the [API reference](/api-reference).

Every page the browser opens goes through a guard. Private, loopback and link-local addresses are blocked. A cloud browser cannot open `localhost` on your machine. See [Test a local app](/guides/local-apps).

## States [#states]

| Status | Meaning |
|---|---|
| `starting` | The browser is being placed and started |
| `live` | Ready. It takes actions and tasks |
| `paused` | A person took over. The agent waits, and agent actions are refused with `session_paused` |
| `ended` | Closed. The record stays |
| `lost` | The browser went away without a clean close |

An ended session has an `endReason`:

| `endReason` | Meaning |
|---|---|
| `closed` | You stopped it |
| `idle_timeout` | No action for the idle time. See [How long a session lives](#how-long-a-session-lives) |
| `max_duration` | It reached its maximum age |
| `runner_lost` | The browser went away without a clean close |
| `error` | Something failed |
| `memory_limit` | It went over its memory cap a second time |
| `capacity_timeout` | It waited in the queue and no browser freed up. See [When no browser is free](#when-no-browser-is-free) |
| `maintenance` | The machine that runs the browser was taken out of service. See [Maintenance](#maintenance) |
| `revoked` | The MCP sign-in the session was started under was revoked |

`maintenance` and `revoked` are not errors.

### Maintenance [#maintenance]

Lookover sometimes takes a browser machine out of service, for example for an update. The sessions on it end in this order:

1. A step in progress finishes. A task is not cut in the middle of a step.
2. Idle sessions end first. The rest end at a set time, with the end reason `maintenance`.
3. The recording and logs are uploaded and billing stops, as for any ended session.

About 5 minutes before that time, the live view socket (`endpoints.live`) sends one message to everyone connected:

```json title="maintenance_warning"
{ "type": "maintenance_warning", "endsAt": 1800000000000 }
```

`endsAt` is a Unix time in milliseconds. The dashboard live view shows it as "This browser restarts for maintenance at" and the local time, with the note "Finish what you are doing, then start a new session." If your own client reads the live socket, do the same: finish before `endsAt`, then open a new session. An ended session does not come back. The dashboard shows it as "Ended for maintenance".

### Revoked [#revoked]

When an MCP sign-in is revoked, the sessions started under it end with `revoked`. The dashboard shows "Ended: connection revoked".

## When no browser is free [#when-no-browser-is-free]

By default, a create call fails with `capacity_exceeded` when every browser is busy. Two options change that:

- `waitForCapacityMs` (0 to 30 000): wait up to this long for a browser to free up, then fail.
- `queueForCapacity` (`{ "maxWaitSeconds": 1 to 900 }`): keep the session in a queue. Not together with `waitForCapacityMs` above 0.

A queued session has the status `starting` and a `queue` object with `position`, `etaSeconds`, `reason` (`vpc_full` or `scaling_up`, when a new browser is starting up), `queuedAt` and `expiresAt`. The `queue` object goes away when the session starts. Read the session with `GET /v1/sessions/:id` until `queue` is gone and the status is `live`, then ask for a token with `POST /v1/sessions/:id/token`. A session that finds no browser before `expiresAt` ends with the end reason `capacity_timeout`. A quota refusal (too many open sessions, memory, budget or credits) is not queued: it fails at once. When the platform is under high demand, the open-session limit can drop for a while: see [High demand and requesting more sessions](/concepts/limits#high-demand-and-requesting-more-sessions).

## How long a session lives [#how-long-a-session-lives]

A session ends on its own in two cases:

- **Idle.** No action for 5 minutes. Change it with `idleTimeoutMs`.
- **Maximum age.** One hour after it started. Change it with `maxDurationMs`.

Both options accept 10 seconds up to 24 hours. These are today's defaults and they may change.

A session that uses more memory than its cap is stopped the second time it goes over the cap, with the end reason `memory_limit`.

## Stop a session [#stop-a-session]

Call the `stop_session` MCP tool, or `DELETE /v1/sessions/:id`. Stopping is safe to repeat. A session you forget is closed by the idle and age limits above, but it holds browser capacity and counts as compute until then. See [Limits](/concepts/limits) and [Billing](/concepts/billing).

## Talk to a live session [#talk-to-a-live-session]

`POST /v1/sessions` returns `endpoints` and a short-lived `token`. They let a client talk to the browser directly. For most uses you do not need them: the MCP tools and the dashboard do this for you. To get a fresh token, call `POST /v1/sessions/:id/token`.

### Connect over CDP [#connect-over-cdp]

`endpoints.cdp` is a WebSocket address. Every connection needs the session `token` from the same reply (not your API key) in an `Authorization: Bearer <token>` header. A connection with no header is refused with `401`. With Playwright, pass the header in `connectOverCDP`:

```ts
import { chromium } from "playwright";

// `cdp` and `token` come from the POST /v1/sessions reply (endpoints.cdp and token).
const browser = await chromium.connectOverCDP(cdp, {
  headers: { Authorization: `Bearer ${token}` },
});
```

A client that cannot set headers, such as a browser page, sends the token as a WebSocket subprotocol instead: `["webagent", "webagent.bearer.<token>"]`. Never put the token in the URL. The token is short-lived: when `tokenExpiresAt` is near, call `POST /v1/sessions/:id/token` for a new one.

## Read sessions back [#read-sessions-back]

`GET /v1/sessions` lists sessions, `GET /v1/sessions/:id` reads one. The `list_sessions` and `get_session` MCP tools do the same. Each session may have a `title`. It is made from the first goal you give it, or you can set it with `PATCH /v1/sessions/:id`.
