Docs
Dashboard
Guides / SessionsBeta

Browser profiles

Keep cookies and local storage between sessions, so your agent stays signed in, and link a password credential so it can sign in again when the cookies expire.

A browser profile stores a browser’s cookies and local storage, so logins carry over from one session to the next. Without one, every session starts with a clean browser, and your agent has to sign in again.

The endpoints are /v1/profiles and the session option is profile. Your plan must include browser profiles (the profiles feature); otherwise you get 402 feature_not_in_plan.

How it works

  1. Create a profile for an account your agent works with.
  2. Start a session with { id, persist: true } and sign in, either from your automation or by hand in the live view.
  3. Release the session. Its cookies and local storage are saved.
  4. Start later sessions with the same profile. They begin signed in, so your agent can go straight to work.

Create a profile

POST /v1/profiles with an optional name (up to 100 characters) returns the new profile. Store its id with the account or user it belongs to. New ids start with prof_; treat them as opaque.

curl -X POST https://api.boxline.dev/v1/profiles \  -H "x-api-key: $BOXLINE_API_KEY" \  -H "content-type: application/json" \  -d '{"name": "Work account"}'
Response (201)
{  "id": "prof_hX3k9QvT1xYw2LmN",  "name": "Work account",  "sizeBytes": 0,  "createdAt": "2026-10-03T10:00:00.000Z",  "updatedAt": "2026-10-03T10:00:00.000Z",  "inUseBy": null,  "credential": null}

Start a session with it

Pass it as profile when you create a session. With persist: true, the browser state is saved back when the session stops, so new logins are kept:

curl -X POST https://api.boxline.dev/v1/sessions \  -H "x-api-key: $BOXLINE_API_KEY" \  -H "content-type: application/json" \  -d '{"browser": true, "profile": {"id": "<profile-id>", "persist": true}}'

Leave persist off (the default) to start from the saved state without changing it, for example when several runs should all begin from the same login.

With persist: true, the session saves to the profile at every checkpoint (the first after about a minute, then every 5 minutes) as well as when it stops, so a crash loses at most the sign-ins since the last checkpoint. The session shows this as profilePersist: true, and its profileId is the profile’s id.

Agent runs take the same option in their session: session: { profile: { id, persist } } starts the run’s own session with the profile (see the run’s session). Tasks take it the same way, and every run of the task starts with it.

Save a sign-in from a running session

Signed in already, in a session that has no profile? POST /v1/profiles with fromSession saves that session’s cookies and the site storage of its open tabs, right now, into a new profile. Sign in first, for example by hand in the live view.

curl -X POST https://api.boxline.dev/v1/profiles \  -H "x-api-key: $BOXLINE_API_KEY" \  -H "content-type: application/json" \  -d '{"name": "Work account", "fromSession": "'"$SESSION_ID"'", "attach": true}'
  • Without attach, it is a copy and the session is not changed. With attach: true, the session also saves to the new profile from now on, at its checkpoints and when it stops, as if it had started with { id, persist: true }: its profileId becomes the new id and it holds the lock (inUseBy). A session that already has a profile gets 409 conflict with attach: true; send attach: false for a copy.
  • The session needs a browser (400) and must be running: one that has stopped gets 409 session_not_running (resume it first).
  • At most 16 MB of saved state (413 profile_too_large), 30 such saves a minute per project, on plans with browser profiles (402 feature_not_in_plan otherwise). name defaults to “Saved from session …”.
  • The session’s log gets a lifecycle event “sign-in saved” with the profile’s id, its size and whether it was attached.

With Playwright

The saved state is loaded into the browser’s default context. Use browser.contexts()[0] rather than creating a new one, or your pages will not see the profile’s cookies.

List, rename and delete

CallWhat it doesSDKs
GET /v1/profilesYour profiles, newest first, with total and next (see Pagination).profiles.list()
GET /v1/profiles/:idOne profile, with inUseBy: the session using it, or null.profiles.get(id)
PATCH /v1/profiles/:idRenames it, links a password credential, or both: { "name": "…", "credential": "SHOP" } (at least one).profiles.update(id, { name, credential })
DELETE /v1/profiles/:idDeletes it. Fails with 409 while a running session uses it.profiles.delete(id)

sizeBytes is the size of the saved state, and updatedAt when it was last saved. credential is the name of the password credential the profile signs in with, or null.

Signing in again by itself

Cookies expire, and some sites ask for a password again. Link a password credential to the profile, and the AI can sign in again by itself: it types the user name, the password and, if the site uses two-factor authentication, the 2FA code as placeholders, and never sees them. The code can come from an authenticator key, or from your own system when the site emails or texts it (2FA codes and sign-in links). Password credentials are on the Hobby plan and up (plan feature loginDetails; 402 feature_not_in_plan on Free).

First save the website password as a credential, with its sites. Then PATCH /v1/profiles/:id with credential set to its name. credential: null unlinks it. Only a password can be linked (400 invalid_request for a secret), and an unknown name gets 404 credential_not_found.

# 1. The website password, with its sites (and its 2FA key, if the site has one):curl -X POST https://api.boxline.dev/v1/credentials \  -H "x-api-key: $BOXLINE_API_KEY" \  -H "content-type: application/json" \  -d '{    "name": "SHOP",    "type": "password",    "origins": ["https://shop.example.com"],    "username": "ada@example.com",    "password": "'"$SHOP_PASSWORD"'",    "totpSecret": "'"$SHOP_2FA_KEY"'"  }'# 2. Link it to the profile:curl -X PATCH https://api.boxline.dev/v1/profiles/$PROFILE_ID \  -H "x-api-key: $BOXLINE_API_KEY" \  -H "content-type: application/json" \  -d '{"credential": "SHOP"}'

From now on, a session started with the profile, and every agent run, task run and plain-English step in such a session, gets that credential as if it were listed in its credentials. The same scope rules apply:

  • The AI gets %SHOP.username%, %SHOP.password% and %SHOP.otp% when the credential’s scope allows the AI ("agent" or "all"). With a code source of "push" or "url", %SHOP.otp% makes the run wait for the code your system sends, and it also gets %SHOP.link%, a sign-in link.
  • The shell of a session with a shell exports it ($SHOP_USERNAME, $SHOP_PASSWORD, and boxline-otp SHOP) when the scope allows the shell. The session lists it in credentials.
  • Where the scope does not allow a use, the credential is left out, without an error.
  • Scripts get it only when the scripts/run call lists it in credentials. See Scripts.
  • Sessions with extensions do not get it, unless the request sets allowWithExtensions: true. An extension can read every typed value. See Credentials.
  • On a plan without password credentials, the link is refused and nothing is offered.
  • Deleting the credential unlinks it from the profile.

Let the AI sign in

Start an agent run from the profile, and tell it where to type each part. The profile’s credential is added for you, so the run does not need to list it:

curl -X POST https://api.boxline.dev/v1/agent/runs \  -H "x-api-key: $BOXLINE_API_KEY" \  -H "content-type: application/json" \  -H "Idempotency-Key: $(uuidgen)" \  -d '{    "task": "Open https://shop.example.com. If it asks you to sign in, type %SHOP.username% into Email, %SHOP.password% into Password and %SHOP.otp% into the code field. Then download the latest invoice.",    "session": {"profile": {"id": "'"$PROFILE_ID"'", "persist": true}}  }'

The rules for typing are the credential’s own: only on its sites, only into fields, never shown to the model. See Credentials.

Or sign in with one call

The login action signs a session in with the credential the profile links, so you need not name it. A short agent run in the session does the typing, and waits for a code or sign-in link if the credential takes them from your system. See Sign in in one call.

curl -X POST https://api.boxline.dev/v1/sessions/$SESSION_ID/browser/actions \  -H "x-api-key: $BOXLINE_API_KEY" \  -H "content-type: application/json" \  -d '{"actions": [{"action": "login"}]}'

A tested example that does all of this, in Node and Python: Two-factor sign-in. To save a sign-in you make by hand, see Save a login. For a site that texts a code instead, see Hand over for a code.

Every link and every use goes into the credentials audit log. Linking is recorded on the credential as an update with changed: ["profiles"], the profile and whether it was linked or unlinked. Deleting a profile that links a credential records the unlink the same way (linked: false).

Limits

A project keeps at most maxProfiles profiles, holding at most maxProfileBytes together. Both are in the plan’s limits.

PlanProfilesSaved state together
Free1050 MB
Hobby1001 GB
Startup5005 GB
Scale2,00020 GB
  • POST /v1/profiles past either limit gets 402 plan_limit, and nothing is created. Delete profiles you no longer need to make room.
  • One profile holds at most 16 MB. A save from a session that would grow its profile past that, or past the total, is skipped instead: the session never fails or stops for it, the profile keeps what it had, and the session’s log gets a lifecycle warning “the profile was not updated”. A save that does not make a profile larger always goes through.
  • Creating a profile from a session: 30 a minute per project.
  • A PATCH that sends credential counts as a credential write: 60 a minute per project, together with the changes to credentials themselves. Renaming a profile does not count. If the credential is deleted at the moment you link it, you get 404 credential_not_found.

Use them safely

  • Keep API keys secret, on your servers only.
  • Use one profile per account, and name it so you know whose it is.
  • Delete profiles you no longer need, and when someone’s access should end.
  • Sign in by hand in the live view when you can, so the password never passes through your code. When the AI has to sign in, link a password credential limited to the site, and keep it at scope "agent". Never put a password in the task text.

These docs describe a beta API. Endpoints, fields and SDK methods may change before general availability; breaking changes will be listed in the changelog.