@scalebrowser/sdk
Official Node / TypeScript SDK for the Scalebrowser
daemon: a typed REST client plus a direct-CDP driver plane.
The driver plane is deliberately direct-CDP (nodriver-style), not Playwright /
Puppeteer: anti-bot stacks block the Playwright control plane independently of
how good the browser patches are, so this SDK speaks the Chrome DevTools
Protocol over a raw websocket and never calls Runtime.enable.
Install
npm install @scalebrowser/sdk
Ships dual ESM + CJS with full type declarations, and needs Node ≥ 18. The SDK
is MIT-licensed, and its source is public at
blackdiamond-labs/scalebrowser-node:
npm builds every release there and publishes it with a provenance attestation.
The daemon it talks to is a separate, licensed product.
Quickstart
import { ScalebrowserClient } from '@scalebrowser/sdk';
const sb = new ScalebrowserClient({
baseUrl: 'http://127.0.0.1:8787',
token: process.env.SCALEBROWSER_TOKEN,
});
const profile = await sb.createProfile({ name: 'acct-01' });
await using session = await sb.launch(profile.id, { headless: true });
await session.cdp.navigate('https://example.com');
const title = await session.cdp.evaluate<string>('document.title');
await session.cdp.humanizeMove(120, 220);
await session.cdp.humanizeClick(120, 220);
Without await using (or for explicit control):
const { cdp_ws } = await sb.startProfile(profile.id, { headless: true });
const cdp = await sb.connectCdp(cdp_ws, profile.id);
try {
await cdp.navigate('https://example.com');
} finally {
cdp.close();
await sb.stopProfile(profile.id);
}
REST surface
Every /v1 endpoint is a typed method on ScalebrowserClient:
- Profiles:
listProfiles, listProfileIds, countProfiles, getProfile,
createProfile, updateProfile, deleteProfile, startProfile, stopProfile.
releaseProfile drops an agent's lease without its handle (the operator's way
out), and takeoverProfile asks for a profile that is open on another machine
of your account
- Bulk:
bulkCreateProfiles, bulkStart, bulkStop, bulkDelete,
bulkAssignProxy, bulkAssignExtensions
- Groups / Presets:
listGroups/createGroup/getGroup/updateGroup/deleteGroup,
listPresets/createPreset/getPreset/updatePreset/deletePreset,
getPersonaConstraints. A preset is config (what the profiles do:
geo_mode, proxy_id, …) plus constraints (what they are: country, which
pins the persona's language, timezone and voices). Both are typed and the daemon
refuses an unknown key with a 400, so read the valid regions from
getPersonaConstraints() rather than hardcoding them.
- Proxies:
listProxies, createProxy, getProxy, updateProxy,
deleteProxy, checkProxy, checkProxyConfig (probe a config before you save
it; pass id to reuse an existing proxy's stored credentials). Credentials
are write-only, never returned
- Extensions:
listExtensions, attachExtension, detachExtension, plus
the daemon-wide library (listLibraryExtensions, uploadExtension,
getLibraryExtension, deleteLibraryExtension, deleteLibraryExtensionVersion). An attached package IS loaded into the browser at
launch, under the canonical Web-Store id its own key derives
- Credentials:
listCredentials, putCredential, deleteCredential,
revealCredential (needs the vault password), exportCredentials,
importCredentials. The vault password itself: vaultStatus, setVaultPassword
- Secret layer:
getSecretLayer, setSecretLayer, listSecrets,
updateSecret, deleteSecret. scrubText filters a text you hold against a
profile's values, and runWithSecrets runs a command with named values in its
environment, so no value crosses a socket
- Cookies:
revealCookies, the one route a cookie VALUE leaves through,
behind the same vault password
- Recorded requests:
revealRequest, the one route what a request carried (query,
header values, body) leaves through, behind the same vault password
- Sessions:
exportSession, importSession
- Mailboxes:
listInboxes, createInbox, updateInbox, deleteInbox,
getInboxBindings, bindInbox, unbindInbox, which is where a profile's
confirmation codes arrive
- Passkeys:
listPasskeys, deletePasskey. Metadata only: the private key
has no field and no endpoint
- Agent runs:
listRuns, getRun, listRunSteps, getRunShot,
getActivity. Read-only, all of it
- Interruptions:
listInterruptionLocks, setInterruptionLock,
listInterruptionRules, setInterruptionRule, deleteInterruptionRule: who
may answer when the browser asks something
- Artifacts:
putArtifact (hand the daemon a file to upload later),
getArtifact (fetch a screenshot, download or saved PDF as bytes)
- Tasks and memory:
listTasks, createTasks, cancelTask, deleteTask,
plus a profile's standing instructions for agents, its PROFILE.md:
getProfileMemory, putProfileMemory, deleteProfileMemory
- Videos:
listVideos, getVideo, renameVideo, deleteVideo,
cancelVideo, rerenderVideo, videoAccess, downloadVideoFile,
videosStorage; recordings with listRecordings, getRecording,
deleteRecordingRaw, renderRecording; public links with share,
shareStatus, revokeShare
- Video presets:
listVideoPresets, getVideoPreset, createVideoPreset,
updateVideoPreset, duplicateVideoPreset, deleteVideoPreset, and a look
at an unsaved config with previewVideoPreset, previewVideoPresetClip,
downloadVideoPresetClip. The files a preset points at: listMedia,
uploadMedia, deleteMedia, mediaFile. Narration keys: listTtsProviders,
putTtsProvider, verifyTtsProvider, deleteTtsProvider. Encoders:
getCodecs, setCodecEnabled
- Fingerprint audit:
startAudit drives the profile's running browser through
the external detector pages, getAudit reads the state and the last report
- Exit addresses:
getExitStatus (which addresses a profile owns), and the
machine's rule that one address belongs to one profile: getExitRule,
setExitRule
- Machine settings:
getRemoteAccess, setRemoteAccess (may a chat service
reach this machine's browsers), getAgentTools, setAgentToolGroup (which
optional MCP tool groups agents see), getConnection (which installation
answered: its data directory and its binary)
- Input:
sendInput
- Metrics / Account / Health:
getMetrics, getAccount, health, ready.
The first two answer 404 on a daemon without them, which the SDK treats as
information rather than as an error: metrics are then derived from profile
state, and the account reads licensed: false, which is what "self-hosted, no
control plane" means
setToken swaps the bearer token on a live client.
Errors map to typed exceptions carrying the daemon's numeric code (4001–4013):
import { ApiError, ErrorCode } from '@scalebrowser/sdk';
try {
await sb.startProfile(id);
} catch (err) {
if (err instanceof ApiError && err.code === ErrorCode.Capacity) {
}
}
Lifecycle events (SSE)
const unsubscribe = sb.subscribeEvents({
onEvent: (e) => {
if (e.type === 'profile_crashed') console.warn(e.profile_id, e.reason);
},
});
for await (const e of sb.events()) {
if (e.type === 'profile_started') console.log(e.cdp_ws);
}
Direct-CDP driver
CdpSession (returned by connectCdp / launch) gives you:
send<T>(method, params?): raw CDP command, awaited by id (concurrent-safe)
navigate(url, { wait, timeout }), evaluate<T>(expr, { isolated }). The
wait for the load reads from an isolated world, so a page that watches its own
document.readyState sees no foreign read; isolated: true asks for the world
of the document that is loaded now, on every call
createIsolatedWorld(frameId?, worldName?)
attachToPage({ targetId?, create?, url? })
on(method, cb) / events(): CDP event subscription scoped to the session
humanizeMove / humanizeClick / humanizeType / humanizeScroll
close()
Security
- Bearer token is sent on every request and the SSE/CDP handshakes; it is
never logged (the client's inspect output masks it).
- Proxy credentials are injected by the daemon and never travel over CDP.
- The CDP endpoint is guarded by the OS user, not by that token. The engine's
DevTools port has no authentication of its own: it answers a bogus bearer with
200, measured, so the engine drops any connection whose peer process runs as
a different user. Nothing to configure and nothing to pass: your process is the
one that started the profile, so it is on the allowed side. A helper running as
another account will not get in, by design.
Develop
npm install
npm run typecheck
npm run build
npm test
The env-gated end-to-end test runs the full path against a real daemon + mock
engine:
SCALEBROWSER_E2E=1 SCALEBROWSER_BASE_URL=http://127.0.0.1:8787 \
SCALEBROWSER_TOKEN=… npm test