← Templates Sessions API template

Authenticated workflow handoff

Capture authenticated browser storage once, then reuse that storage in a later managed session so repeat workflows start signed in.

SessionsAuthPlaywright

Use this pattern when a workflow needs to log in once, then hand that authenticated browser state to later BrowserCity sessions. Keep it limited to accounts and applications you are authorized to automate; do not use stored state to bypass a site’s access controls or terms.

The handoff has two explicit phases:

  1. Capture authenticated storage. Create a session, connect with Playwright, complete login on the target site, then terminate the session with DELETE /v1/sessions/:id. The delete response can include summary.storage with Playwright-style cookies and origin localStorage entries.
  2. Reuse that storage later. Create a new BrowserCity session with the captured storage payload so the ready context/page starts authenticated.

The examples below use Playwright for the login flow. If you drive the first phase with REST or Humanized REST actions instead, still capture storage by ending the BrowserCity session with DELETE /v1/sessions/:id and reading summary.storage from that response.

Phase 1: authenticate once and capture storage

Replace the placeholder selectors, credentials, and target URLs with your own application flow. The snippets save the captured storage to storage-state.json; cookies remain arrays, so count them with .length, len(...), or .Count when you inspect them.

capture-authenticated-storage.ts
import { writeFile } from 'node:fs/promises';import { chromium } from 'playwright';const apiKey = process.env.BROWSERCITY_API_KEY;if (!apiKey) {  throw new Error('Set BROWSERCITY_API_KEY before running this script.');}const apiBase = 'https://api.browser.city/v1';type StorageState = {  cookies?: unknown[];  origins?: unknown[];};type Session = {  id: string;  endpoint: string;  token: string;};type DeleteSessionResponse = {  summary?: {    storage?: StorageState;  };};const created = await fetch(`${apiBase}/sessions`, {  method: 'POST',  headers: { Authorization: `Bearer ${apiKey}`, 'Content-Type': 'application/json' },  body: JSON.stringify({ browser: 'chromium', labels: ['template:auth-capture'] }),});if (!created.ok) {  throw new Error(`Create session failed: ${created.status} ${await created.text()}`);}const { endpoint, token, id } = (await created.json()) as Session;let capturedStorage: StorageState | undefined;try {  const browser = await chromium.connect(endpoint, {    headers: { Authorization: `Bearer ${token}` },  });  const [context] = browser.contexts();  const [page] = context?.pages() ?? [];  if (!page) {    throw new Error('BrowserCity session did not return a ready page.');  }  await page.goto('https://app.example.com/login');  await page.getByLabel('Email').fill(process.env.APP_LOGIN_EMAIL ?? '<email>');  await page.getByLabel('Password').fill(process.env.APP_LOGIN_PASSWORD ?? '<password>');  await page.getByRole('button', { name: 'Sign in' }).click();  await page.waitForURL('https://app.example.com/dashboard');} finally {  const deleted = await fetch(`${apiBase}/sessions/${id}`, {    method: 'DELETE',    headers: { Authorization: `Bearer ${apiKey}` },  });  if (!deleted.ok) {    throw new Error(`Delete session failed: ${deleted.status} ${await deleted.text()}`);  }  const { summary } = (await deleted.json()) as DeleteSessionResponse;  capturedStorage = summary?.storage;  if (capturedStorage) {    await writeFile('storage-state.json', JSON.stringify(capturedStorage, null, 2));    console.log(`Captured ${capturedStorage.cookies?.length ?? 0} cookies.`);  }}if (!capturedStorage) {  throw new Error('Session ended without summary.storage. Confirm the login completed before deletion.');}

Phase 2: start a later session with captured storage

Send the saved storage object back on session creation. BrowserCity applies it before you connect, so the first existing context/page is already the authenticated handoff target.

reuse-authenticated-storage.ts
import { readFile } from 'node:fs/promises';import { chromium } from 'playwright';const apiKey = process.env.BROWSERCITY_API_KEY;if (!apiKey) {  throw new Error('Set BROWSERCITY_API_KEY before running this script.');}const apiBase = 'https://api.browser.city/v1';const storage = JSON.parse(await readFile('storage-state.json', 'utf8')) as {  cookies?: unknown[];  origins?: unknown[];};const created = await fetch(`${apiBase}/sessions`, {  method: 'POST',  headers: { Authorization: `Bearer ${apiKey}`, 'Content-Type': 'application/json' },  body: JSON.stringify({    browser: 'chromium',    labels: ['template:auth-reuse'],    storage,  }),});if (!created.ok) {  throw new Error(`Create session failed: ${created.status} ${await created.text()}`);}const { endpoint, token, id } = (await created.json()) as { endpoint: string; token: string; id: string };try {  const browser = await chromium.connect(endpoint, {    headers: { Authorization: `Bearer ${token}` },  });  const [context] = browser.contexts();  const [page] = context?.pages() ?? [];  if (!page) {    throw new Error('BrowserCity session did not return a ready page.');  }  await page.goto('https://app.example.com/dashboard');  await page.getByText('Account overview').waitFor();} finally {  await fetch(`${apiBase}/sessions/${id}`, {    method: 'DELETE',    headers: { Authorization: `Bearer ${apiKey}` },  });}

Operational guardrails

  • Only capture and reuse storage that your user or organization is authorized to use.
  • Treat storage-state.json like a password: encrypt it at rest, keep it out of source control, and rotate it when access changes.
  • Scope cookies to the target domain and keep lifetimes short when possible.
  • Delete capture and reuse sessions promptly after each phase finishes.
  • Prefer labels that identify the workflow, not the human user’s private data.

Cost and plan notes

Authenticated workflows often spend more time waiting on application pages than public extraction jobs. Estimate browser minutes for both the capture phase and each reuse run, then multiply by expected concurrent jobs in /pricing-calculator.

[ 06 / 06 ] — Get Started

Give your AI agents the web.

Start for free. No credit card required. Private sessions by default.