docs
Use it from JavaScript
Install the TypeScript SDK, run a task, and keep secrets, authorization and output validation in your process.
fastbrowse.ai·all docs·source at the pinned commit·Raw Markdown·llms.txt
npm install fastbrowse gives a Node program the same agent, with no Python and no uv on the machine. The
package is a TypeScript SDK with no dependencies. With it npm installs one of five packages that hold the agent
as a native binary, the one for your platform: @fastbrowse/darwin-arm64, darwin-x64, linux-arm64,
linux-x64 or win32-x64. No install script runs and nothing is downloaded on first use, so it installs under
pnpm and bun with scripts blocked, and it starts offline. It needs Node.js 20 or newer. The npm packages carry
the PyPI package's version and are published by the same tag, starting with the release after 0.5.18.
The binary brings no browser. It finds local Chrome, starts a Browser Use Cloud browser, or drives one already
running, as the command line does. It reads the same keys from the environment of your process
(OPENROUTER_API_KEY, and BROWSER_USE_API_KEY for a cloud browser); env in Fastbrowse.start adds to them.
npm install fastbrowse zod
export OPENROUTER_API_KEY=...
import { Fastbrowse } from 'fastbrowse';
import { z } from 'zod';
const Release = z.object({ package: z.string(), version: z.string() });
const fb = await Fastbrowse.start({ local: true });
try {
const result = await fb.run('Find the httpx package and report its name and latest released version.', {
start: 'https://pypi.org/',
output: Release,
limits: { maxDollars: 0.1 },
onEvent: event => {
if (event.type === 'step') console.error(event.step.operation, event.step.target);
},
signal: AbortSignal.timeout(120_000),
});
console.log(result.status, result.answer);
if (result.status === 'complete') console.log(result.output.package, result.output.version);
for (const evidence of result.evidence) console.log(` "${evidence.quote}" from ${evidence.url}`);
} finally {
await fb.close();
}
Fastbrowse.start starts one fastbrowse process and run sends it a task. The process serves one run at a
time: a second run while one is active rejects with the busy error, and a program that wants runs side by
side starts more instances. close() shuts the process down and waits for it. The process also exits when
yours does, however yours ended.
run resolves with the result whatever status the run ended in, so needs_login or stuck is read from
status and is not an exception. It rejects when no run took place or none finished: with RpcError when the
server refuses the request before a browser opens (a bad option, a missing key, a busy server), with
AbortError when signal stopped the run, and with ProcessExitedError when the process is gone. A run that
fails after it has started resolves with status error.
The result is the RunResult the Python library returns, with the same statuses, citations and cost lines.
Its fields keep their Python names, such as final_url, since the types are generated from the Python models;
the options are camelCase. The structured data is in data, as in Python. The SDK adds
output: with a Zod (4.2 or newer) or ArkType schema, or a Valibot schema wrapped by
@valibot/to-json-schema, output is data after the schema's own validation and transforms, and has the
schema's output type. Data the schema refuses rejects with OutputValidationError, which carries the result.
A JSON Schema object is accepted too; output is then data, typed unknown.
What a run can fill today is narrower than what a schema can say. The server accepts nested objects, arrays,
enum, const and optional values, and refuses any other keyword by name before a browser opens. A run
fills a flat object of required string, number, integer and boolean fields, as the example has. With any
other field the run ends unverified with no data.
The callbacks that keep credentials and the last word in your code cross the process boundary:
await fb.run('Log in as standard_user with the saved password and add the backpack to the cart.', {
start: 'https://www.saucedemo.com/',
authorization: { irreversibleActions: true },
secrets: {
refs: [{ name: 'password', origins: ['https://www.saucedemo.com'] }],
resolve: name => vault.get(name),
},
until: url => url.endsWith('/cart.html'),
});
secrets.resolve is called each time a value is typed and never for an origin its ref does not cover, so the
value leaves your vault at that moment and a one-time code is fresh. until gets the address the run ended on,
and anything but true keeps the run from complete. onFrame receives JPEG frames of the active tab;
without it no frame is sent. A callback that throws ends the run with status error. The other options are
the command line's: inputs, attachments as bytes, downloads, record, and the browser choices
chrome, cloudProfile, cdpUrl, cdpPort, attach, targetMatch and proxyCountry, which
Fastbrowse.start also takes as defaults for every run. A run passes null for one of them to go without
that default.
There is no binary for Alpine or another musl system, and none for a platform outside the five. There
Fastbrowse.start rejects with an error that names the platform, and a binary of your own is named with
binaryPath or FASTBROWSE_BINARY. The macOS binaries carry an ad-hoc signature and are not notarized, which a Mac
that allows programs only by the team that signed them refuses. The Windows binary is not signed. Bun and Deno are
untested.
Use fastbrowse with your agent
Install the fastbrowse skill to delegate website tasks from an agent that supports Agent Skills. It uses the MCP tool when connected, or the CLI, and preserves the task's citations, status, authorization, and budget. The guide covers agent selection, configuration, and example prompts for Codex and Claude Code. Agents without skill support can use the CLI or MCP server directly.
Run fastbrowse through Nebula
Nebula embeds fastbrowse in its agent harness, with browser steps, results and permitted vault logins. Use Bitwarden or 1Password through Nebula with credentials scoped to authorized websites.