# Sessions

> A session is one run of a profile on one machine — its lifecycle, attach tickets and how it ends.

A **session** is a profile open on a machine. A profile has at most one live session at a time, and every session is fenced: a machine that lost its claim can no longer act for it.

Apps that run many accounts can hold a profile's browser instead of starting and stopping it themselves — see [Hold a browser from your app](https://browlix.com/en/docs/app-sessions).

## Lifecycle

| State | Meaning | What to do |
|---|---|---|
| `queued` | Accepted, waiting for the machine | Wait |
| `preflight` | Checking network, proxy and profile data | Wait |
| `starting` | Launching the browser | Wait |
| `ready` | Browser running; attach is possible | Attach or watch |
| `quiescing` | Finishing before stop (data being saved) | Wait |
| `stopping` | Closing the browser | Wait |
| `closed` | Ended; no browser process remains | Done |
| `failed` | Could not start or ended with an error | Read the reason, then start again |
| `recovery_required` | Ended uncertainly; the profile needs a decision | [Recover](#recovering), then start again |

Poll `sessions.get`, or subscribe to the `session.ready`, `session.failed` and `session.closed` [webhooks](https://browlix.com/en/docs/webhooks). The SDK's `waitForSession` and the CLI's `sessions wait` only wait: stopping the wait never stops the session.

## Starting

`sessions.start` needs a `profile_id`, a `device_id` and a `command_id`. Typed refusals:

- `BUSY` — the profile is already open; `error.details` names that session.
- `SYNC_PENDING` — another machine still holds newer data for this profile.
- `RECOVERY_REQUIRED` — the last session ended uncertainly; `error.details` names it. See [Recovering](#recovering).
- `QUOTA_EXCEEDED` — the workspace already runs every concurrent session its limit allows.

## Attaching

`sessions.attach` issues a ticket for one ready session, valid for `ttl_seconds` (30–900, default 300), together with the machine's local broker address. Connect from that machine with the ticket as a bearer header — see [Attach with Playwright](https://browlix.com/en/docs/playwright). Closing your Playwright connection does not stop the session.

## Stopping

`sessions.stop` is the only call that ends a session. It is reported `closed` only after the machine proves no browser process is left; profile data is then sealed and uploaded.

## Recovering

A session ends *uncertainly* when its machine stops confirming it — powered off, offline, or wiped — or reports a stop it cannot prove. Browlix never guesses: the session becomes `recovery_required` and the profile stays closed everywhere, so two browsers can never write the same profile.

When you know the machine is gone, call `sessions.recover` with a `command_id`. The session is settled as `failed` with reason `RECOVERED`, the profile can open again (with a higher fence) and a late report from the old machine is refused; if the old browser comes back it is told to stop. The call needs `device.manage` — owner or admin, and a key with that scope — and is audited. While the machine still confirms the session it answers `CONFLICT`: stop it instead.

```bash
curl -s -X POST "$BROWLIX_API/sessions/$SESSION_ID/recover" \
  -H "Authorization: Bearer $BROWLIX_API_KEY" -H "Content-Type: application/json" \
  -d "{\"command_id\":\"$(uuidgen)\"}"
```

If the machine also held profile data it never uploaded, opening elsewhere still answers `SYNC_PENDING`; `profiles.sync.release` drops that machine's copy on purpose.

## History

`sessions.list` with `scope=live` returns open sessions; `scope=recent` returns the last 7 days, optionally for one profile with `profile=<profile id>`.
