cdpfleet Docs GitHub Dashboard

Persistent profiles

Launch with "profile": "shop-login" and the browser starts from that profile: the cookies, localStorage and IndexedDB of the last session that used it. When the session ends, the profile is saved again. Log in once and stay logged in across sessions, machines and days — with no state files to store yourself.

Use it

{
  "proxy": "http://user:[email protected]:8080",
  "profile": "shop-login"
}

The first launch with a new name creates the profile; every later launch with the same name restores it. Names are up to 64 letters, digits, - or _. Then work in browser.contexts()[0] — that context is the profile:

import { chromium } from 'playwright';

const KEY = process.env.CDPFLEET_API_KEY;
async function launch() {
  const res = await fetch('https://starter.cdpfleet.com/chrome/session', {
    method: 'POST',
    headers: { 'x-api-key': KEY, 'content-type': 'application/json' },
    body: JSON.stringify({ proxy: process.env.PROXY_URL, headless: true, profile: 'shop-login' }),
  });
  if (!res.ok) throw new Error(`launch: ${res.status} ${await res.text()}`);
  return chromium.connect((await res.json()).wsUrl, { headers: { 'x-api-key': KEY } });
}

// First run: log in once. The profile is saved when the session ends.
let browser = await launch();
let context = browser.contexts()[0];          // the profile — not browser.newContext()
let page = context.pages()[0] ?? await context.newPage();
await page.goto('https://example.com/login');
// ... log in ...
await browser.close();

// Any later run, from any machine: already logged in.
browser = await launch();
context = browser.contexts()[0];
page = context.pages()[0] ?? await context.newPage();
await page.goto('https://example.com/account');
await browser.close();
# The same in Python (sync API): use browser.contexts[0]
browser = p.chromium.connect(ws_url, headers={"x-api-key": KEY})
context = browser.contexts[0]                 # the profile — not browser.new_context()
page = context.pages[0] if context.pages else context.new_page()

Use contexts()[0], not newContext(). A context you create with newContext() inside a profile session is a throwaway incognito context: nothing in it is saved. This holds for Playwright connect() and for connectOverCDP (CDP) alike.

What is saved

Read-only, and one writer at a time

Browsers and limits

Manage profiles

GET    https://cdpfleet.com/v1/me/profiles            # your limit, and each profile: name, browser, size, last used, in use, frozen, keep
PUT    https://cdpfleet.com/v1/me/profiles/{name}     # {"keep": true} — stays active if the account goes over its limit
DELETE https://cdpfleet.com/v1/me/profiles/{name}     # 409 while a session is using it

Errors

StatusErrorMeaning
400invalid_profileNot a valid name or {id, mode} object.
400profile_unsupported_engineProfiles aren't available on this browser yet.
403profile_limitThe account already has as many profiles as its plan allows; delete one first, or add threads.
403profile_frozenThe profile is frozen because the account is over its plan's limit; upgrade, or delete other profiles.
404no_such_profileA read-only launch of a profile that doesn't exist yet.
409profile_in_useAnother session is writing this profile; retry when it ends, or launch read-only.
409profile_engine_mismatchThe profile was created with another browser.
409profile_version_downgradeThis browser version is older than the one that last saved the profile.
503profile_restore_failed / profile_service_unavailableThe profile couldn't be loaded right now; retry.

Security

Profiles hold logins, so they're stored encrypted with a key of their own (AES-256-GCM) in our private object storage in Germany, and the key is kept separately from the data. Browser servers never hold storage credentials: each session gets one-time links to load and save its profile, decrypts it in memory and wipes it after the save. Only your account can use or list your profiles.

Profiles or storageState?

Playwright's storageState does a similar job from your side: you save cookies and storage to a file and load it into a new context (case 11; IndexedDB needs an option, case 39). Profiles keep the state on our side, keep a Camoufox device stable and need no file handling. Use storageState when you want the state in your own hands, or on browsers profiles don't cover yet; see case 40 for exactly what a profile brings back.