> ## Documentation Index
> Fetch the complete documentation index at: https://docs.beta.b3os.org/llms.txt
> Use this file to discover all available pages before exploring further.

# Profiles

Save browser cookies and storage as an encrypted profile, load it into scrapes, sessions and screenshots, and keep it current without losing newer logins.

A profile holds a browser's cookies and storage under a name, so an agent can start signed in. Scrapes, browser sessions and render jobs load profiles by ID or name. Profiles are encrypted in the node's vault, decrypted only inside the browser that loads them, never logged and never sent to a model. They never leave your organization.

## Create one from Playwright

A profile's state is Playwright's `storageState` JSON, uploaded as is. Sign in once with Playwright, export the state, and upload it:

**curl**

```bash
    # state.json comes from Playwright: await context.storageState({ path: "state.json", indexedDB: true })
    curl -s https://beta-api.b3os.org/v1/vision/profiles \
      -H "Authorization: Bearer $B3OS_API_KEY" \
      -H "Content-Type: application/json" \
      -d "{\"name\": \"acme-staging\", \"state\": $(cat state.json)}"
    ```
  **TypeScript**

```ts
    import { readFile } from "node:fs/promises";
    import { B3OS } from "@b3os/sdk";

    const b3os = new B3OS();
    const state = JSON.parse(await readFile("state.json", "utf8"));
    const profile = await b3os.vision.createProfile({ name: "acme-staging", state });
    console.log(profile.id, profile.domains, profile.stats);
    ```
  **Python**

```python
    import json
    from b3os import B3OS

    b3os = B3OS()
    with open("state.json") as f:
        profile = b3os.vision.create_profile({"name": "acme-staging", "state": json.load(f)})
    print(profile["id"], profile["domains"], profile["stats"])
    ```
  **MCP**

Profiles hold live session cookies, so create them from your own code rather than through an agent. An agent can then use one by name where its tool takes a profile: the remote MCP server's `vision_scrape` takes the whole scrape request, `profile` included. The local server's tools take no profile; there, create a session with the profile from your code and give the agent its ID, which `vision_act` accepts as `session_id`.

Every cookie domain and storage origin must belong to the profile's `domains`. Leave `domains` out and B3OS derives it from the state; a state that strays outside the listed domains fails with [`profile_domain_mismatch`](/errors#profile_domain_mismatch). Names are unique per project. The API returns the domains and counts of cookies, origins and bytes, never the values.

## Use a profile

| Where | Field |
|---|---|
| [Scrape](/vision/scrape#use-a-profile), batch, crawl | `profile` (implies the browser tier) |
| [Browser session](/vision/sessions) | `profile`, with `persist_profile` to save back on close |
| [Screenshot](/render/screenshots), [video](/render/videos) | `profile`, or `source.profile` for videos |

## Save from a session

Sign in inside a session (an agent, or a person through the CDP socket), then save its current cookies and storage:

```bash
curl -s "https://beta-api.b3os.org/v1/vision/sessions/$SESSION_ID/profile" \
  -H "Authorization: Bearer $B3OS_API_KEY" \
  -H "Content-Type: application/json" \
  -d '{"name": "acme-staging"}'
```

This creates the profile if the name is new, or writes a new revision if it exists. A session created with `profile` and `persist_profile: true` does the same automatically when it closes.

## Revisions and conflicts

Every save increments the profile's `revision`. Two sessions can load the same revision; if one saves first, the other's save would throw away newer state, so it fails with [`profile_conflict`](/errors#profile_conflict). Save with `overwrite` (or create the session with `overwrite_profile`) to replace it anyway, or load the profile again in a new session. While a session with `persist_profile` holds a profile, its status is `locked`; other loads still see the last saved revision.

## Change, export or delete

- `PATCH /v1/vision/profiles/{profile_id}` renames it, changes its domains, expiry or metadata, or replaces its state with a new revision.
- `GET /v1/vision/profiles/{profile_id}/state` exports the current state in the same `storageState` shape, so you can load it into your own browser. It contains live session cookies: treat the response like a password. It needs `vision:write`, the scope that can replace the state.
- `DELETE /v1/vision/profiles/{profile_id}` deletes the profile and every revision. Sessions that already loaded it keep their cookies until they close.

A profile can carry an `expires_at`, after which it is deleted.