Docs
BlogHomeStart building

Browser Automation (totalum-sdk/browser)


import … from 'totalum-sdk/browser' — or the same namespace on totalum from 'totalum-sdk'. Install, configuration and errors: Totalum SDK.

A real Chrome in the cloud, driven over HTTPS from your server, so it works inside the published app: open pages, click, type, log in, fill forms, download files, take screenshots and PDFs, extract data. Use it when a page needs interaction; to just read a page, totalum.web.scrape is cheaper and faster. Billed in credits per started minute of an open session: always close() the session in finally. Plans allow a few sessions at once (one on Free); a session with no call for 60 s is closed.

ts
const s = await totalum.browser.session();
try {
  await s.goto('https://example.com');
  const title = await s.evaluate<string>('document.title');
} finally {
  await s.close();
}
Method or typeWhat it does
totalumBrowserCreates the browser namespace (the same object as totalum.browser, scope browser): remote browser sessions driven entirely over HTTPS, so every command works from a Worker.
browser.sessionOpens a session.
browser.sessions.listThe project's sessions, newest first — open ones and the history — filtered by status or metadata.
browser.sessions.getA handle on an existing session, to keep driving it from another request (the id was stored).
browser.programs.getA program's status and, once finished, each step's result and the final variables.
browser.programs.waitPolls a program until it leaves pending/running and returns it (the browser.program.completed webhook is the push alternative).
BrowserActionsOutputThe answer of a batch of commands.
BrowserCommandOne command for s.actions() or a program step: { type, …parameters } ({ type: "goto", url }, { type: "click", target }).
BrowserCommandResultThe result of one command in s.actions().
BrowserCommandsThe command methods of a session: one per command (goto, click, … tabs.new, network.har, …), named like the command; dotted commands are groups (s.tabs.new()).
BrowserProgramA program for s.program(): steps that run in the background on the session — data, never code.
BrowserProgramStatusA program's status and results, from browser.programs.get() and wait().
BrowserSessionAn open cloud-browser session, from browser.session() or browser.sessions.get(id): its id and live view, the methods below, and one method per command (the table below).
BrowserSessionCreatedA session just opened (what browser.session() reads to build its handle).
BrowserSessionInputThe options of browser.session(); all optional.
BrowserSessionStatusA session's state and usage, from s.status().

#totalumBrowser

ts
totalumBrowser(options?: TotalumClientOptions): TotalumBrowser

Creates the browser namespace (the same object as totalum.browser, scope browser): remote browser sessions driven entirely over HTTPS, so every command works from a Worker. A command's refusal throws TotalumError with its frozen code (ACTION_TIMEOUT, ACTION_FAILED, …); actions() returns every result instead.

#Things that still need a CDP connection

Nothing an app needs. Every capability is a command or a create-time option, so it works in a published app. The list keeps exactly two entries, neither of which an app needs:

  • Attaching a raw CDP domain listener for a continuous event stream — network.subscribe is the push channel for the app case.
  • Driving the session with a third-party Node library's own protocol client (connectOverCDP, puppeteer.connect).

#Not possible for anyone, over any transport

  • Seeding a saved profile from raw bytes (CONTEXT_SEED_UNSUPPORTED) — the browser provider's profile upload is deprecated; create a profile by running a session with context: { persist: true }, and replay cookies and web storage with cookies.set and storage.set (a service worker or an IndexedDB database cannot be replayed).

Parameters

NameTypeRequiredDescription
optionsTotalumClientOptionsnoThe project key, API URL and environment; each falls back to its environment variable.
options.keystringnoProject key tlm_pk_…; default TOTALUM_PROJECT_KEY. Server-side only.
options.apiUrlstringnoThe Totalum API's URL: default TOTALUM_SDK_API_URL, else https://sdk-api.totalum.app.
options.env'dev' | 'live'noSent as x-totalum-env on every call (the database it targets, and dev vs live usage); default TOTALUM_ENV, else live.

Returns TotalumBrowser — The browser namespace: the methods below.

Example

ts
import { totalumBrowser } from 'totalum-sdk/browser';

const browser = totalumBrowser();
const s = await browser.session();
try {
  await s.goto('https://example.com');
} finally {
  await s.close();
}

The namespace's type is exported as TotalumBrowser (import type { TotalumBrowser } from 'totalum-sdk/browser').

#browser.session

ts
browser.session(opts?: BrowserSessionInput): Promise<BrowserSession>

Opens a session. It returns the cloud browser's handle: call commands on it (s.goto(url), s.click(target), …) and close() it in finally. Billed per started minute while open. At most 3 sessions are open at once per project (1 on Free); one more waits in a queue up to 60 s. A session with no call for 60 s is closed; it lasts at most timeoutMinutes.

Totalum-synthesised options (Totalum applies them to every page, so the browser provider validates none of them): locale, timezone, geolocation, userAgent, extraHeaders, blockResources, device, captureDownloads, viewport. Mapped onto vendor fields: stealth ('off' | 'basic' | 'verified'; 'verified' needs a higher provider tier, else STEALTH_TIER_UNAVAILABLE, and ignores viewport) and region (four regions; 'eu' and 'us' are aliases). Every other option is passed to the browser provider as is.

Parameters

NameTypeRequiredDescription
optsBrowserSessionInputnoProxy, CAPTCHA solving, stealth, region, a saved profile, emulation and recording options; all optional.
opts.proxy{ enabled, country?, state?, city?, routes? }noSend the traffic through a residential proxy (for sites that block data-centre traffic or vary by country). Proxy traffic is billed per GB.
opts.proxy.enabledbooleanyesTurn the proxy on.
opts.proxy.countrystringnoExit country, two letters (ES). Allowed: exactly 2 characters.
opts.proxy.statestringnoExit state, two letters (US). Allowed: exactly 2 characters.
opts.proxy.citystringnoExit city.
opts.proxy.routes({ domainPattern, mode, country?, state?, city?, server?, username?, password? })[]noPer-domain rules instead of one proxy for everything.
opts.proxy.routes.domainPatternstringyesThe domains the rule applies to (*.example.com).
opts.proxy.routes.mode'none' | 'totalum' | 'external'yestotalum (Totalum's proxy), none (direct) or external (your own proxy).
opts.proxy.routes.countrystringnoExit country for totalum. Allowed: exactly 2 characters.
opts.proxy.routes.statestringnoExit state for totalum. Allowed: exactly 2 characters.
opts.proxy.routes.citystringnoExit city for totalum.
opts.proxy.routes.serverstringnoYour proxy's address, for external.
opts.proxy.routes.usernamestringnoYour proxy's user, for external.
opts.proxy.routes.passwordstringnoYour proxy's password, for external.
opts.solveCaptchasbooleannoSolve CAPTCHAs automatically. Default true.
opts.captcha{ imageSelector?, inputSelector? }noWhere an image CAPTCHA is on the page, when it is not detected by itself.
opts.captcha.imageSelectorstringnoCSS selector of the CAPTCHA image.
opts.captcha.inputSelectorstringnoCSS selector of the field its answer goes in.
opts.stealth'basic' | 'off' | 'verified'nobasic (default): a normal fingerprint. verified: a verified browser for strict sites (when available). off.
opts.fingerprintOs'mobile' | 'windows' | 'mac' | 'linux' | 'tablet'noThe operating system the browser appears to run on.
opts.timeoutMinutesnumbernoThe longest the session may stay open. Default 15 (or the plan's maximum if lower). Allowed: > 0.
opts.keepAlivebooleannoAccepted; sessions always stay open between calls (until 60 s idle or timeoutMinutes).
opts.context{ id, persist? }noLoad a saved profile (cookies and storage): { id } from context.save; persist: true saves changes back.
opts.context.idstringyesThe profile's id (the contextId context.save returned).
opts.context.persistbooleannoSave the session's cookies back into the profile when it closes.
opts.contextIdstringnoSame as context: { id }.
opts.blockAdsbooleannoBlock ads and trackers.
opts.allowedDomainsstring[]noOnly let the browser open these domains (up to 50). Allowed: at most 50 items.
opts.ignoreCertificateErrorsbooleannoOpen sites with invalid HTTPS certificates.
opts.caCertificateIdsstring[]noExtra certificate authorities to trust.
opts.extensionIdstringnoA browser extension to load.
opts.metadataRecord<string, string>noYour own key/value strings, to find the session later (sessions.list({ metadata })); under 512 characters.
opts.logSessionbooleannoKeep the session's logs. Default true.
opts.recordSessionbooleannoRecord the session (replay with session.recording). Default true.
opts.region'eu' | 'us-west-2' | 'us-east-1' | 'eu-central-1' | 'ap-southeast-1' | 'us'noThe region the browser is in: us-west-2 (default), us-east-1, eu-central-1, ap-southeast-1, or eu / us.
opts.localestringnoThe browser language (es-ES).
opts.timezonestringnoThe browser time zone (Europe/Madrid).
opts.geolocation{ latitude, longitude, accuracy? }noThe location the browser reports.
opts.geolocation.latitudenumberyesLatitude.
opts.geolocation.longitudenumberyesLongitude.
opts.geolocation.accuracynumbernoAccuracy in metres.
opts.userAgentstringnoA custom user agent.
opts.extraHeadersRecord<string, string>noHeaders added to every request.
opts.blockResources('image' | 'media' | 'font')[]noResource types not to load, to go faster: media, font, image. Default ["media", "font"].
opts.devicestringnoEmulate a device: iPhone 15, iPhone 15 Pro Max, iPhone SE, iPad Pro 11, Pixel 8, Galaxy S24, Desktop Chrome, …
opts.captureDownloadsbooleannoKeep files the pages download (read them with downloads.get). Default true.
opts.viewport{ width, height }noThe window size.
opts.viewport.widthnumberyesWidth in pixels. Allowed: > 0.
opts.viewport.heightnumberyesHeight in pixels. Allowed: > 0.

Returns Promise<BrowserSession> — The session handle.

Errors — besides the errors every call can throw:

CodeHTTPWhen
SESSION_LIMIT_REACHED429timeoutMinutes is above the plan's longest session (15 min on Free, 60 on Starter, 180 on Builder, 360 on Professional and Enterprise).
CONCURRENCY_QUEUE_TIMEOUT429The plan's sessions at once were busy for 60 s; retry after details.retryAfter seconds, or open fewer sessions.
CONCURRENCY_QUEUE_FULL429Too many sessions waiting already.
RATE_LIMIT_EXCEEDED429The plan's new sessions per minute or monthly browser minutes are used up.
STEALTH_TIER_UNAVAILABLE409stealth: 'verified' is not available.
PROVIDER_UNAVAILABLE503The browser did not start; retry.

Example

ts
const s = await totalum.browser.session({ locale: 'es-ES', timezone: 'Europe/Madrid' });
try {
  await s.goto('https://example.com');
  const info = await s.pageInfo();
} finally {
  await s.close();
}

Result:

ts
{
  id: 'bs_a285ef5dfce1d3837b913a887a384298',
  liveViewUrl: 'https://sdk-api.totalum.app/v1/browser/live/eyJzIjoiYnNfYTI4NWVm…',
  expiresAt: '2026-10-11T17:56:24.666Z',
  goto: [Function], click: [Function], …, close: [Function]
}

#browser.sessions.list

ts
browser.sessions.list(q?: { status?, metadata?, limit?, cursor? }): Promise<{ items, nextCursor? }>

The project's sessions, newest first — open ones and the history — filtered by status or metadata. Free.

Parameters

NameTypeRequiredDescription
q{ status?, metadata?, limit?, cursor? }noFilters and paging; all optional.
q.statusstringnoOnly sessions in this state: running, ended, expired or failed.
q.metadatastringnoOnly sessions with this metadata entry, as key:value ('orderId:ord_42').
q.limitnumbernoSessions per page.
q.cursorstringnoThe previous page's nextCursor.

Returns Promise<{ items, nextCursor? }> — One page of sessions.

FieldTypeAlways presentDescription
items({ id, status, startedAt, minutes, proxyMb, captchas, credits, expiresAt, region, metadata })[]yesThe sessions, newest first: each one's status and usage, plus its region and metadata.
items.idstringyesThe session id.
items.status'expired' | 'running' | 'failed' | 'ended'yesrunning, ended (closed), expired (timed out or idle) or failed.
items.startedAtstringyesWhen it opened.
items.minutesnumberyesStarted minutes so far (billed per started minute).
items.proxyMbnumberyesProxy traffic so far, in MB.
items.captchasnumberyesCAPTCHAs solved so far.
items.creditsnumberyesCredits used so far.
items.expiresAtstringyesWhen it ends at the latest.
items.regionstringyesWhere it runs.
items.metadataRecord<string, string> | nullyesThe metadata it was opened with, or null.
nextCursorstringnoPass it as cursor for the next page; absent on the last.

Errors — besides the errors every call can throw:

CodeHTTPWhen
VALIDATION_ERROR400A filter is malformed.

Example

ts
const { items } = await totalum.browser.sessions.list({ status: 'running', limit: 2 });

Result:

json
{
  "items": [
    { "id": "bs_20b6813a9f10ce41b3ff002d80e911c4", "status": "ended", "region": "us-west-2", "metadata": null, "startedAt": "2026-09-28T13:18:46.849Z", "expiresAt": "2026-09-28T13:33:46.244Z", "minutes": 1, "proxyMb": 0, "captchas": 0, "credits": 0.056 }
  ],
  "nextCursor": "1790600898279:bs_8cbb1dd693454f4fd82fedbfb9671d0c"
}

#browser.sessions.get

ts
browser.sessions.get(id: string): BrowserSession

A handle on an existing session, to keep driving it from another request (the id was stored). No call is made; the first command fails with SESSION_EXPIRED if it has ended. Calling a command also keeps it from closing after 60 s idle.

Parameters

NameTypeRequiredDescription
idstringyesThe session's id.

Returns BrowserSession — The session handle (liveViewUrl and expiresAt are empty: read status()).

Example

ts
const s = totalum.browser.sessions.get(job.browserSessionId);
await s.click({ text: 'Next page' });

#browser.programs.get

ts
browser.programs.get(programId: string): Promise<BrowserProgramStatus>

A program's status and, once finished, each step's result and the final variables. Free.

Parameters

NameTypeRequiredDescription
programIdstringyesThe programId from s.program().

Returns Promise<BrowserProgramStatus> — The program's status and results.

FieldTypeAlways presentDescription
programIdstringyesThe program id.
sessionIdstringyesThe session it runs on.
status'completed' | 'running' | 'failed' | 'pending' | 'timed_out'yespending, running, then completed, failed or timed_out.
steps({ index, type, ok, data?, error? })[]yesEach executed step's result, in order.
steps.indexnumberyesThe step's position.
steps.typestringyesThe command (or step kind) it ran.
steps.okbooleanyesWhether it succeeded.
steps.dataunknownnoIts result.
steps.error{ code, message? }noWhy it failed.
steps.error.codeTotalumErrorCodeyesThe error code.
steps.error.messagestringnoWhat happened.
varsRecord<string, unknown>yesThe variables at the end.
startedAtstringyesWhen it started.
creditsnumberyesCredits charged for the program's own work (the session's minutes are billed on the session).
resultunknownnoThe program's result, when it set one.
endedAtstringnoWhen it ended.

Errors — besides the errors every call can throw:

CodeHTTPWhen
PROGRAM_NOT_FOUND404No such program in this project.

Example

ts
const p = await totalum.browser.programs.get('bp_a285ef5dfce1d3837b913a887a384298_3982732f');

Result:

ts
{
  "programId": "bp_a285ef5dfce1d3837b913a887a384298_3982732f",
  "sessionId": "bs_a285ef5dfce1d3837b913a887a384298",
  "status": "completed",
  "steps": [
    { "index": 0, "type": "goto", "ok": true, "data": { "url": "https://example.com/", "status": 200, "redirected": false } },
    { "index": 1, "type": "pageInfo", "ok": true, "data": { "url": "https://example.com/", "title": "Example Domain", … } }
  ],
  "vars": {},
  "startedAt": "2026-10-11T17:42:25.605Z",
  "endedAt": "2026-10-11T17:42:27.912Z",
  "credits": 0
}

#browser.programs.wait

ts
browser.programs.wait(programId: string, opts?: { intervalMs?, timeoutMs? }): Promise<BrowserProgramStatus>

Polls a program until it leaves pending/running and returns it (the browser.program.completed webhook is the push alternative). Keep it within a request's time: programs run at most 60 s.

Parameters

NameTypeRequiredDescription
programIdstringyesThe programId from s.program().
opts{ intervalMs?, timeoutMs? }noHow often and how long to poll.
opts.intervalMsnumbernoMilliseconds between polls. Default 2000.
opts.timeoutMsnumbernoStop polling after this long and return the program as it is. Default 960000.

Returns Promise<BrowserProgramStatus> — The finished program (or as it is when the timeout passed).

FieldTypeAlways presentDescription
programIdstringyesThe program id.
sessionIdstringyesThe session it runs on.
status'completed' | 'running' | 'failed' | 'pending' | 'timed_out'yespending, running, then completed, failed or timed_out.
steps({ index, type, ok, data?, error? })[]yesEach executed step's result, in order.
steps.indexnumberyesThe step's position.
steps.typestringyesThe command (or step kind) it ran.
steps.okbooleanyesWhether it succeeded.
steps.dataunknownnoIts result.
steps.error{ code, message? }noWhy it failed.
steps.error.codeTotalumErrorCodeyesThe error code.
steps.error.messagestringnoWhat happened.
varsRecord<string, unknown>yesThe variables at the end.
startedAtstringyesWhen it started.
creditsnumberyesCredits charged for the program's own work (the session's minutes are billed on the session).
resultunknownnoThe program's result, when it set one.
endedAtstringnoWhen it ended.

Errors — besides the errors every call can throw:

CodeHTTPWhen
PROGRAM_NOT_FOUND404No such program in this project.

Example

ts
const done = await totalum.browser.programs.wait(programId, { intervalMs: 1000 });

Result:

ts
{ "programId": "bp_a285ef5dfce1d3837b913a887a384298_3982732f", "status": "completed", "steps": [ … ], "vars": {}, "credits": 0, … }

#BrowserActionsOutput

The answer of a batch of commands.

FieldTypeAlways presentDescription
resultsBrowserCommandResult[]yesOne result per command, in order.
durationMsnumberyesHow long the batch took.

#BrowserCommand

One command for s.actions() or a program step: { type, …parameters } ({ type: "goto", url }, { type: "click", target }). The parameters are the command's (see the session commands table).

ts
type BrowserCommand = Record<string, unknown>

#BrowserCommandResult

The result of one command in s.actions().

FieldTypeAlways presentDescription
okbooleanyesWhether it succeeded.
dataunknownnoIts result (as the command's method would return it), when ok.
error{ code, message? }noWhy it failed, when not ok.
error.codeTotalumErrorCodeyesThe error code (ACTION_FAILED, ACTION_TIMEOUT, SKIPPED after an earlier failure, …).
error.messagestringnoWhat happened.

#BrowserCommands

The command methods of a session: one per command (goto, click, … tabs.new, network.har, …), named like the command; dotted commands are groups (s.tabs.new()). See the session commands table under BrowserSession.

#BrowserProgram

A program for s.program(): steps that run in the background on the session — data, never code.

FieldTypeAlways presentDescription
steps({ do } | { repeat } | { if } | { try } | { set })[]yesThe steps, in order: { do: command }, { repeat: { times | until, max, body } }, { if: { condition, then, else } }, { try: { body, catch } }, { set: { name, from } }. At most 200 steps, nested 5 deep.
varsRecord<string, unknown>noStarting variables, readable by the steps.
timeoutSecnumbernoThe longest it may run, at most 60 s. Default 60.
webhookUrlstringnoAn https:// URL to POST the result to when it ends (also sent as the browser.program.completed webhook).

#BrowserProgramStatus

A program's status and results, from browser.programs.get() and wait().

FieldTypeAlways presentDescription
programIdstringyesThe program id.
sessionIdstringyesThe session it runs on.
status'completed' | 'running' | 'failed' | 'pending' | 'timed_out'yespending, running, then completed, failed or timed_out.
steps({ index, type, ok, data?, error? })[]yesEach executed step's result, in order.
steps.indexnumberyesThe step's position.
steps.typestringyesThe command (or step kind) it ran.
steps.okbooleanyesWhether it succeeded.
steps.dataunknownnoIts result.
steps.error{ code, message? }noWhy it failed.
steps.error.codeTotalumErrorCodeyesThe error code.
steps.error.messagestringnoWhat happened.
varsRecord<string, unknown>yesThe variables at the end.
startedAtstringyesWhen it started.
creditsnumberyesCredits charged for the program's own work (the session's minutes are billed on the session).
resultunknownnoThe program's result, when it set one.
endedAtstringnoWhen it ended.

#BrowserSession type

An open cloud-browser session, from browser.session() or browser.sessions.get(id): its id and live view, the methods below, and one method per command (the table below). Each method is one HTTPS call. Always close() it in finally: an open session is billed per minute until it closes (or until 60 s pass with no call).

FieldTypeAlways presentDescription
idstringyesThe session id (bs_…): pass it to browser.sessions.get(id) to drive the same session from another request.
liveViewUrlstringyesA signed link to watch the session live (and take over: a user can solve a login step), valid a few minutes. '' on a handle from sessions.get.
expiresAtstringyesWhen the session ends at the latest (its timeoutMinutes). '' on a handle from sessions.get.

#s.actions

ts
s.actions(batch: BrowserCommand[], opts?: { stopOnError? }): Promise<BrowserCommandResult[]>

Runs up to 20 commands in one round trip, in order, under the session's lock, and returns every result instead of throwing: faster than one call per command. With stopOnError (the default), the commands after a failure are not run and answer SKIPPED.

Parameters

NameTypeRequiredDescription
batchBrowserCommand[]yesThe commands: { type, …parameters } each ({ type: 'goto', url }, { type: 'click', target }).
opts{ stopOnError? }nostopOnError.
opts.stopOnErrorbooleannoStop at the first failing command. Default true.

Returns Promise<BrowserCommandResult[]> — One result per command, in order: { ok: true, data } or { ok: false, error: { code, message } }.

Each item:

FieldTypeAlways presentDescription
okbooleanyesWhether it succeeded.
dataunknownnoIts result (as the command's method would return it), when ok.
error{ code, message? }noWhy it failed, when not ok.
error.codeTotalumErrorCodeyesThe error code (ACTION_FAILED, ACTION_TIMEOUT, SKIPPED after an earlier failure, …).
error.messagestringnoWhat happened.

Errors — besides the errors every call can throw:

CodeHTTPWhen
BATCH_TOO_LARGE400More than 20 commands.
SESSION_BUSY409Another command or batch is running on this session.
SESSION_EXPIRED410The session has ended: open a new one.
UNKNOWN_COMMAND400A type is not one of the commands.

Example

ts
const results = await s.actions([
  { type: 'goto', url: 'https://example.com' },
  { type: 'evaluate', expression: '1 + 1' },
]);

Result:

json
[
  { "ok": true, "data": { "url": "https://example.com/", "status": 200, "redirected": false } },
  { "ok": true, "data": 2 }
]

#s.program

ts
s.program(program: BrowserProgram): Promise<{ programId, status, pollUrl }>

Starts a program on the session: a list of steps (commands, plus bounded repeat, if, try and set) that runs in the background — data, never code — so a long task outlives the request. Read it with browser.programs.get(programId) / programs.wait, or receive the browser.program.completed webhook.

Parameters

NameTypeRequiredDescription
programBrowserProgramyesThe steps, variables, time limit and webhook.
program.steps({ do } | { repeat } | { if } | { try } | { set })[]yesThe steps, in order: { do: command }, { repeat: { times | until, max, body } }, { if: { condition, then, else } }, { try: { body, catch } }, { set: { name, from } }. At most 200 steps, nested 5 deep.
program.varsRecord<string, unknown>noStarting variables, readable by the steps.
program.timeoutSecnumbernoThe longest it may run, at most 60 s. Default 60.
program.webhookUrlstringnoAn https:// URL to POST the result to when it ends (also sent as the browser.program.completed webhook).

Returns Promise<{ programId, status, pollUrl }> — The program id and where to poll it.

FieldTypeAlways presentDescription
programIdstringyesThe program id: pass it to browser.programs.get() / wait().
status'pending'yesAlways pending.
pollUrlstringyesThe API URL of its status.

Errors — besides the errors every call can throw:

CodeHTTPWhen
PROGRAM_TOO_LARGE400More than 200 steps or nesting deeper than 5.
PROGRAM_UNBOUNDED400A repeat without max (≤ 100) and exactly one of times / until.
UNKNOWN_STEP400A step that is not do, repeat, if, try or set.
PROGRAM_IN_FLIGHT409A program is already running on this session.
PROGRAM_LIMIT_REACHED429The plan's programs at once (or per hour) are reached.

Example

ts
const { programId } = await s.program({
  steps: [
    { do: { type: 'goto', url: 'https://example.com' } },
    { do: { type: 'pageInfo' } },
  ],
});
const done = await totalum.browser.programs.wait(programId);

Result:

json
{ "programId": "bp_a285ef5dfce1d3837b913a887a384298_3982732f", "status": "pending", "pollUrl": "https://sdk-api.totalum.app/v1/browser/programs/bp_a285ef5dfce1d3837b913a887a384298_3982732f" }

#s.status

ts
s.status(): Promise<BrowserSessionStatus>

The session's state and what it has used so far (minutes, proxy MB, CAPTCHAs, credits). Also keeps an idle session open: a session with no call for 60 s is closed.

Returns Promise<BrowserSessionStatus> — The session's status.

FieldTypeAlways presentDescription
idstringyesThe session id.
status'expired' | 'running' | 'failed' | 'ended'yesrunning, ended (closed), expired (timed out or idle) or failed.
startedAtstringyesWhen it opened.
minutesnumberyesStarted minutes so far (billed per started minute).
proxyMbnumberyesProxy traffic so far, in MB.
captchasnumberyesCAPTCHAs solved so far.
creditsnumberyesCredits used so far.
expiresAtstringyesWhen it ends at the latest.

Errors — besides the errors every call can throw:

CodeHTTPWhen
SESSION_NOT_FOUND404No such session in this project.

Example

ts
const status = await s.status();

Result:

json
{ "id": "bs_a285ef5dfce1d3837b913a887a384298", "status": "running", "startedAt": "2026-10-11T17:41:24.666Z", "minutes": 1, "proxyMb": 0, "captchas": 0, "credits": 0.1647, "expiresAt": "2026-10-11T17:56:24.666Z" }

#s.close

ts
s.close(): Promise<{ minutes, proxyMb, captchas, creditsUsed, recordingUrl? }>

Ends the session and settles its charge. Always call it in finally: an open session is billed per started minute.

Returns Promise<{ minutes, proxyMb, captchas, creditsUsed, recordingUrl? }> — What the session used and cost.

FieldTypeAlways presentDescription
minutesnumberyesStarted minutes billed.
proxyMbnumberyesProxy traffic in MB.
captchasnumberyesCAPTCHAs solved.
creditsUsednumberyesCredits charged for the session.
recordingUrlstringnoA link to the recording, when there is one.

Errors — besides the errors every call can throw:

CodeHTTPWhen
SESSION_NOT_FOUND404No such session in this project.

Example

ts
const closed = await s.close();

Result:

json
{ "minutes": 2, "proxyMb": 0, "captchas": 0, "creditsUsed": 0.279 }

#Session commands

Every command is a method of the session, named like the command (dotted names are groups: s.tabs.new()). The leading arguments are the command's main parameters; the last argument is an object with the rest of them. Every command also takes frame (a frame id from frames.list), tabId (a tab id from tabs.list) and timeoutMs (default 30 s; waitFor, pdf and extract up to 60 s, captcha.solve up to 120 s) in that last object. Each resolves to the command's result; a refusal throws TotalumError with the command's code. target is a CSS selector ('#email'), { text: 'Sign in' }, { role: 'button', name: 'Search' } or { xpath: '//button' }. Files a command produces (screenshots, PDFs, HAR files, downloads) are private files of the project: url is a signed link valid for 1 hour; keep the key and read it later with totalum.files.getSignedUrl(key).

CommandWhat it doesParameters (last argument)Result
s.goto(url, opts?)Opens a URL in the current tab and waits for it to load.waitUntil: load (default), domcontentloaded or networkidle; referer.{ url, status, redirected } — the final URL, its HTTP status, whether it redirected.
s.back(opts?)Goes back one page in the tab's history.waitUntil.{ url, status }.
s.forward(opts?)Goes forward one page.waitUntil.{ url, status }.
s.reload(opts?)Reloads the page.waitUntil.{ url, status }.
s.pageInfo(opts?)Describes the current page.—{ url, title, viewport: { width, height }, userAgent, frames, tabId }.
s.click(target, opts?)Clicks an element (moves the mouse to its centre and clicks).button (left, right, middle), clickCount, modifiers (["Shift"]), position: { x, y } instead of a target.{ clicked: true }.
s.dblclick(target, opts?)Double-clicks an element.As click.{ clicked: true }.
s.type(target, text, opts?)Focuses a field and types text into it.clear (empty it first), delayMs (between keys, to look human), pressEnter.{ typed: true, value } — the field's value afterwards.
s.press(key, opts?)Presses a key (Enter, Tab, Escape, ArrowDown, a), optionally on an element.target (focus it first), modifiers, repeat.{ pressed: true }.
s.hover(target, opts?)Moves the mouse over an element (menus that open on hover).—{ hovered: true }.
s.scroll(opts?)Scrolls the page, or an element into view.target (scroll it into view), to: "top" | "bottom", by (pixels, default 600), direction: "y" | "x".{ scrollX, scrollY, atBottom } — use atBottom to stop an infinite-scroll loop.
s.dragAndDrop(from, to, opts?)Drags one element onto another.steps (mouse moves in between, default 10).{ dropped: true }.
s.select(target, value, opts?)Chooses an option of a <select>: by value, a list of values, { label } or { index }.—{ selected } — the selected values.
s.upload(target, files, opts?)Puts files into a file input (1–10): each { fileKey } (a project file) or { url }.viaChooser: true clicks target and fills the file chooser it opens; name (with one file).{ uploaded: [{ name, bytes }] }.
s.frames.list(opts?)Lists the page's frames (iframes).—{ frames: [{ frameId, url, name, parentFrameId }] }.
s.frames.use(opts?)Makes later commands run inside a frame (or back in the main page).frameId, selector (the iframe element), url (part of it), name, or main: true.{ frameId } (null for the main page).
s.tabs.list(opts?)Lists the open tabs.—{ tabs: [{ tabId, url, title, active }] }.
s.tabs.new(opts?)Opens a new tab (at most 10) and makes it the current one.url, newWindow.{ tabId, url }.
s.tabs.use(opts?)Makes another tab the current one.tabId, index or urlContains.{ tabId }.
s.tabs.close(opts?)Closes a tab (default the current one).tabId.{ closed: true, activeTabId }.
s.waitFor(opts)Waits for something before going on: an element, a text, a JavaScript condition, a navigation, network idle or a fixed time (up to 60 s).One of selector (with state: visible default, hidden, attached, detached), text, expression, navigation: true, networkIdle: true, ms.{ matched, waitedMs } — what matched and how long it took.
s.evaluate(expression, opts?)Runs JavaScript in the page and returns its value (a promise is awaited). Script ≤ 16 KB, result ≤ 1 MB.args (makes expression a function called with them), awaitPromise (default true).The expression's value (JSON), or null.
s.content(opts?)Reads the page (or an element) as text, Markdown or HTML.format: markdown (default), text or html; target; onlyMainContent.A string; over 5 MB, a stored file { key, url, truncated: true } instead.
s.screenshot(opts?)Takes a screenshot of the viewport, the full page or an element, stored as a private project file.scope: viewport (default), fullPage or element (with target); format: png (default) or jpeg (with quality); omitBackground.{ key, url, bytes, width, height } — url is a signed link valid 1 hour.
s.pdf(opts?)Prints the page to a PDF, stored as a private project file.format: A4 (default) or Letter; landscape; printBackground; scale; margin: { top, right, bottom, left } (inches).{ key, url, bytes, pages }.
s.extract(opts)Reads the page and extracts data with your JSON Schema, using AI (billed as an AI call).schema (required), prompt, model, scope: "target" with target to read one element.The extracted object.
s.cookies.get(opts?)Reads the browser's cookies.urls (only cookies sent to them), names.{ cookies: [{ name, value, domain, path, expires, httpOnly, secure, sameSite, … }] }.
s.cookies.set(cookies, opts?)Sets cookies (for example a saved login).Each cookie: name, value, and url or domain/path, expires, httpOnly, secure, sameSite.{ set } — how many.
s.cookies.clear(opts?)Deletes cookies.names, domain.{ cleared } — how many.
s.storage.get(opts?)Reads the current page's localStorage (or sessionStorage).area: local (default) or session; keys; origin (must be the current page's).{ items } — key/value pairs.
s.storage.set(opts)Writes items to the current page's storage.items (key/value strings), area, origin.{ set } — how many.
s.storage.clear(opts?)Empties the current page's storage.area, origin.{ cleared } — how many items.
s.context.save(opts?)Saves the browser's cookies and this page's localStorage as a profile (a private project file), to log in again later with session({ context: { id } }).name.{ contextId, name } — pass contextId as context.id.
s.downloads.list(opts?)Lists the files the pages downloaded (with captureDownloads, the default).—{ files: [{ name, bytes, contentType, completedAt }] }.
s.downloads.get(opts?)Stores a downloaded file as a private project file, waiting for it if needed.name or index (default the first); waitMs (up to 60 s).{ name, key, bytes, contentType } (and url, a signed link).
s.network.capture(opts)Starts (or stops) recording the network requests of the session.enabled, urlPattern (glob), maxEntries (≤ 5,000, default 1,000), includeBodies, includeResponseBodies.{ capturing, entries }.
s.network.log(opts?)Returns the captured requests.urlPattern, resourceTypes (xhr, fetch, document, …), statusMin, since, limit (≤ 500).{ entries: [{ id, method, url, status, resourceType, requestHeaders, responseHeaders, timings, requestBytes, responseBytes, fromCache }], truncated }.
s.network.har(opts?)Saves the captured requests as a HAR file (a private project file).includeBodies.{ key, url, entries, truncated }.
s.network.intercept(opts)Blocks, answers or changes matching requests (at most 50 rules).rules: each { urlPattern, method?, resourceTypes?, action: "block" | "fulfill" | "modify" | "continue", status?, contentType?, body?, setHeaders?, removeHeaders? }; replace: false adds to the current rules.{ rules } — how many are active.
s.network.setHeaders(opts)Adds headers to every request (or to matching URLs).headers, urlPattern.{ headers } — how many.
s.emulate.viewport(opts)Changes the window size.width, height, deviceScaleFactor, isMobile, hasTouch.{ viewport }.
s.emulate.device(opts)Emulates a device's screen and user agent.device: iPhone 15, iPhone 15 Pro Max, iPhone SE, iPad Pro 11, Pixel 8, Galaxy S24, Desktop Chrome, ….{ device, viewport, userAgent }.
s.emulate.geolocation(opts)Sets the location the browser reports (and grants the permission).latitude, longitude, accuracy (default 10), permission: "deny".{ geolocation }.
s.emulate.timezone(opts)Sets the time zone.timezoneId (Europe/Madrid).{ timezoneId }.
s.emulate.locale(opts)Sets the language and Accept-Language.locale (es-ES), languages.{ locale, languages }.
s.emulate.userAgent(opts)Sets the user agent.userAgent, platform, acceptLanguage.{ userAgent }.
s.captcha.solve(opts?)Waits (up to 120 s) for the CAPTCHA on the page to be solved (sessions solve them automatically unless solveCaptchas: false).—{ solved, durationMs }; throws CAPTCHA_UNSOLVED when it failed.
s.captcha.events(opts?)Lists the session's CAPTCHA events.since.{ events: [{ type, at, … }] } (captcha_started, captcha_solved, captcha_failed).
s.proxy.usage(opts?)The proxy traffic so far.—{ enabled, country, bytes, requests }.
s.stealth.get(opts?)Shows the fingerprint websites see.—{ mode, fingerprint: { userAgent, platform, languages, hardwareConcurrency, webdriver, webglVendor, webglRenderer } }.
s.session.status(opts?)The session's status and usage (as s.status()).—{ id, status, startedAt, minutes, proxyMb, captchas, credits, expiresAt }.
s.session.liveView(opts?)A new signed link to watch the session (or a tab) live.ttlSeconds (≤ 300), tabId.{ url, expiresAt }.
s.session.recording(opts?)A signed link to the session's recording.ttlSeconds (default 600, ≤ 3,600).The recording's link and details.
s.dialog.handle(opts?)Accepts or dismisses the open alert/confirm/prompt, or arms the answer for the next one.action: accept (default) or dismiss; promptText; persist (every future dialog).{ armed: false, handled: { type, message } }, or { armed: true }.
s.page.addInitScript(opts)Runs a script in every page before its own scripts (at most 10, ≤ 16 KB each).expression, persist (default true).{ scriptId }.
s.page.exposeFunction(opts)Adds window.<name>(…) to pages; each call is POSTed to your webhook URL (at most 5).name (a JavaScript identifier), webhookUrl.{ name, bound: true }.
s.emulate.media(opts)Emulates CSS media features.media (print, screen), colorScheme (dark, light), reducedMotion.The applied settings.
s.emulate.network(opts)Simulates a slow or offline network.offline, downloadKbps, uploadKbps, latencyMs.The applied settings.
s.emulate.cpu(opts)Slows the CPU down, to test slow phones.rate (2 = twice as slow).{ rate }.
s.permissions.grant(opts)Grants browser permissions (geolocation, notifications, camera, microphone, clipboard, …).permissions, origin.{ granted }.
s.clipboard.read(opts?)Reads the clipboard of the current page.format: text (default) or html.The clipboard's text.
s.clipboard.write(opts)Writes to the clipboard.text, html.{ written: true }.
s.act(instruction, opts?)Does something described in plain words (click the cookie banner's accept button), using AI.variables (values to fill in, kept out of the AI prompt), frame.{ done, action, actions } — whether it succeeded and what it did.
s.observe(instruction, opts?)Finds the elements that match a description in plain words, using AI, without acting.frame.{ candidates } — the matching elements and how to act on them.
s.session.release(opts?)Releases the remote browser while keeping the session record (use close() to end and settle it).—{ released: true }.
s.network.subscribe(opts)POSTs the session's events to your webhook URL as they happen.events: any of request, response, console, download, popup, dialog; webhookUrl; urlPattern.{ subscribed } — the events.
s.accessibility.snapshot(opts?)The page's accessibility tree (roles, names, values): a compact view of what is on screen, good for AI.target (one element), interestingOnly (default true).The tree { role, name, value, children }; over 2 MB, a stored file { key, url, truncated: true }.

#BrowserSessionCreated

A session just opened (what browser.session() reads to build its handle).

FieldTypeAlways presentDescription
idstringyesThe session id.
status'running'yesAlways running.
liveViewUrlstringyesA signed link to watch it live.
expiresAtstringyesWhen it ends at the latest.
proxyCountrystringnoThe proxy's exit country, with a proxy.

#BrowserSessionInput

The options of browser.session(); all optional.

NameTypeRequiredDescription
proxy{ enabled, country?, state?, city?, routes? }noSend the traffic through a residential proxy (for sites that block data-centre traffic or vary by country). Proxy traffic is billed per GB.
proxy.enabledbooleanyesTurn the proxy on.
proxy.countrystringnoExit country, two letters (ES). Allowed: exactly 2 characters.
proxy.statestringnoExit state, two letters (US). Allowed: exactly 2 characters.
proxy.citystringnoExit city.
proxy.routes({ domainPattern, mode, country?, state?, city?, server?, username?, password? })[]noPer-domain rules instead of one proxy for everything.
proxy.routes.domainPatternstringyesThe domains the rule applies to (*.example.com).
proxy.routes.mode'none' | 'totalum' | 'external'yestotalum (Totalum's proxy), none (direct) or external (your own proxy).
proxy.routes.countrystringnoExit country for totalum. Allowed: exactly 2 characters.
proxy.routes.statestringnoExit state for totalum. Allowed: exactly 2 characters.
proxy.routes.citystringnoExit city for totalum.
proxy.routes.serverstringnoYour proxy's address, for external.
proxy.routes.usernamestringnoYour proxy's user, for external.
proxy.routes.passwordstringnoYour proxy's password, for external.
solveCaptchasbooleannoSolve CAPTCHAs automatically. Default true.
captcha{ imageSelector?, inputSelector? }noWhere an image CAPTCHA is on the page, when it is not detected by itself.
captcha.imageSelectorstringnoCSS selector of the CAPTCHA image.
captcha.inputSelectorstringnoCSS selector of the field its answer goes in.
stealth'basic' | 'off' | 'verified'nobasic (default): a normal fingerprint. verified: a verified browser for strict sites (when available). off.
fingerprintOs'mobile' | 'windows' | 'mac' | 'linux' | 'tablet'noThe operating system the browser appears to run on.
timeoutMinutesnumbernoThe longest the session may stay open. Default 15 (or the plan's maximum if lower). Allowed: > 0.
keepAlivebooleannoAccepted; sessions always stay open between calls (until 60 s idle or timeoutMinutes).
context{ id, persist? }noLoad a saved profile (cookies and storage): { id } from context.save; persist: true saves changes back.
context.idstringyesThe profile's id (the contextId context.save returned).
context.persistbooleannoSave the session's cookies back into the profile when it closes.
contextIdstringnoSame as context: { id }.
blockAdsbooleannoBlock ads and trackers.
allowedDomainsstring[]noOnly let the browser open these domains (up to 50). Allowed: at most 50 items.
ignoreCertificateErrorsbooleannoOpen sites with invalid HTTPS certificates.
caCertificateIdsstring[]noExtra certificate authorities to trust.
extensionIdstringnoA browser extension to load.
metadataRecord<string, string>noYour own key/value strings, to find the session later (sessions.list({ metadata })); under 512 characters.
logSessionbooleannoKeep the session's logs. Default true.
recordSessionbooleannoRecord the session (replay with session.recording). Default true.
region'eu' | 'us-west-2' | 'us-east-1' | 'eu-central-1' | 'ap-southeast-1' | 'us'noThe region the browser is in: us-west-2 (default), us-east-1, eu-central-1, ap-southeast-1, or eu / us.
localestringnoThe browser language (es-ES).
timezonestringnoThe browser time zone (Europe/Madrid).
geolocation{ latitude, longitude, accuracy? }noThe location the browser reports.
geolocation.latitudenumberyesLatitude.
geolocation.longitudenumberyesLongitude.
geolocation.accuracynumbernoAccuracy in metres.
userAgentstringnoA custom user agent.
extraHeadersRecord<string, string>noHeaders added to every request.
blockResources('image' | 'media' | 'font')[]noResource types not to load, to go faster: media, font, image. Default ["media", "font"].
devicestringnoEmulate a device: iPhone 15, iPhone 15 Pro Max, iPhone SE, iPad Pro 11, Pixel 8, Galaxy S24, Desktop Chrome, …
captureDownloadsbooleannoKeep files the pages download (read them with downloads.get). Default true.
viewport{ width, height }noThe window size.
viewport.widthnumberyesWidth in pixels. Allowed: > 0.
viewport.heightnumberyesHeight in pixels. Allowed: > 0.

#BrowserSessionStatus

A session's state and usage, from s.status().

FieldTypeAlways presentDescription
idstringyesThe session id.
status'expired' | 'running' | 'failed' | 'ended'yesrunning, ended (closed), expired (timed out or idle) or failed.
startedAtstringyesWhen it opened.
minutesnumberyesStarted minutes so far (billed per started minute).
proxyMbnumberyesProxy traffic so far, in MB.
captchasnumberyesCAPTCHAs solved so far.
creditsnumberyesCredits used so far.
expiresAtstringyesWhen it ends at the latest.