TypeScript SDK for Notte - Cloud-hosted browser sessions and automation
The official TypeScript SDK for Notte API, providing cloud-hosted browser sessions with precise control, serverless functions, and secure credential management.
This directory is a standalone npm package. The published SDK runs on Node.js 20 or
newer; developing it (the Vitest 5 test and coverage scripts) needs Node.js 22.12 or
newer, which npm warns about through devEngines. Node.js 22 and 24 are tested in CI;
Python tooling is not required to build or test it.
cd node-sdk
npm ci
npm run typecheck
npm run test:unit -- --run
npx vitest run test/function-stream.local.test.ts # also part of npm run test:unit
npm run build
node scripts/check-package.js
npm pack --dry-runnpm run build compiles the checked-in client without contacting the API.
npm run generate uses ../openapi/openapi.json, the committed snapshot shared
with Python, and ignores NOTTE_API_URL (which configures runtime API calls).
Run npm run docs:generate afterward and commit the generated changes.
npm run check:generated regenerates in a temporary directory and checks the
TypeScript reference without editing your files or contacting staging.
To deliberately refresh the snapshot and regenerate both SDKs and the TypeScript
reference, install dependencies with uvx uv@latest sync --all-packages --all-extras
at the repo root and npm ci here, then run from the repo root:
make openapi-refresh # staging
make openapi-refresh OPENAPI_URL=https://api.notte.cc # productionReview and commit the snapshot and all generated diffs together. If a generator
rejects the new contract, resolve the reported incompatibility before committing.
The scheduled openapi-bump.yml workflow proposes this same combined refresh as
one PR for manual review in nottelabs/notte; if a generator fails, the PR is a draft
with the error in its body and the run fails. It is repository-guarded and does not
run in nottelabs/notte-v2. There is no separate live Node generation check.
The SDK generates client-bound methods for sessions, functions,
vaults, personas, and profiles from the checked-in OpenAPI-derived types
and operations. No per-call client or throwOnError is needed:
const client = new NotteClient();
await client.sessions.stop(sessionId);
const vault = await client.vaults.create({ name: 'default' });
const copy = await client.functions.fork(functionId);Arguments are path IDs in URL order, then the request body (if any), query
parameters (if any), and optional { signal } cancellation options. For example,
client.sessions.stop(sessionId, { close_reason: 'manual' }, { signal }).
Operations with required request headers require a final options object, for
example client.sessions.createPayment(sessionId, body, undefined, { headers: { 'idempotency-key': key } }).
Keep the same idempotency key when retrying the same payment request. The SDK
binds x-notte-api-key automatically; callers cannot override it through typed headers.
Methods return the API response body and throw the client's typed API errors.
The four existing list() methods still return arrays for compatibility;
the newly added profiles.list() returns the API pagination response.
These are API operations, not lifecycle helpers: they do not poll for completion,
decrypt credentials, or manage cleanup. Continue using Session().use(),
Function().run() and Vault() for those behaviors. Existing
standalone generated exports remain supported.
npm run generate refreshes this layer too. Use npm run resources:generate
to regenerate it offline, npm run resources:check to detect stale output, and
npm run resources:test for generator regressions. Method-name aliases live in
the generator; request shapes and routes come from generated OpenAPI types.
For the isolated live test, set NOTTE_API_KEY and
NOTTE_API_URL=https://us-staging.notte.cc, then run:
npx vitest run test/function-create-run.integration.test.tsThis test creates its own function and deletes it afterward. Other live suites
may require pre-existing fixtures; see test documentation.
The node sdk cicd workflow runs on PRs touching node-sdk/, the OpenAPI snapshot,
or the generated TypeScript reference:
-
testsruns unit tests, local HTTP integration tests, type checks, and package checks on Node.js 22 and 24, including for fork PRs. -
integration-testsruns the complete live integration suite against staging on same-repository PRs. Fork PRs skip this credentialed job. -
generatedchecks the client, proxy patterns, resources and TypeScript reference against the committed snapshot, including on fork PRs.
These jobs also run on main pushes and manual workflow runs (live integration requires main for manual runs). Staging contract drift is handled only by the scheduled snapshot refresh PR, so it cannot break unrelated PR generation checks.
The npm package remains notte-sdk. Its MIT license is in LICENSE;
the repository root license does not replace this package's license.
The Node SDK is released together with the Python SDK, from the same tag and at
the same version. Cutting a release means creating a GitHub release tagged
vX.Y.Z on nottelabs/notte:
pypi-release.ymlpublishes the Python packages to PyPI asX.Y.Z.node-sdk-publish.ymlsets this package's version toX.Y.Z, runs the typecheck, unit tests, streaming test and export smoke test, publishes to npm with provenance, then installs the published version back from npm to verify it.
package.json stays at 0.0.0-dev on main; the version is only set in CI
from the tag, like the .dev placeholder in the Python pyproject.toml files.
Publishing uses npm trusted publishing (GitHub OIDC) bound to this repository,
workflow node-sdk-publish.yml and environment npm, so no npm token is stored
in the repository. To dry-run a release build locally, run make release X.Y.Z
in this directory; it never publishes.
Importing the SDK does not contact the npm registry. Constructing a NotteClient
performs a one-time, non-blocking check per process and warns when a newer
release exists, like the Python SDK. Set NOTTE_SDK_DISABLE_VERSION_CHECK=1 to
disable it. To explicitly check for an update, call await checkForLatestVersion()
from notte-sdk.
API URLs must use HTTPS, except HTTP loopback addresses for local development.
Since 1.10 the SDK has no high-level agent API, matching Python. Releases up to 1.9 keep agent support. Generated low-level agent endpoints and schemas remain tied to the API specification; they are not exposed through client resources.
createNotteProxy from notte-sdk/next requires an authenticate callback at
runtime. Without one, requests return 401 even when a server API key is configured.
The callback must verify caller identity and authorize access to requested
resources. Returning a user-specific Notte API key isolates accounts; returning
void uses the shared server key and requires application-level ownership checks.
An endpoint allowlist is not a substitute for authorization.
- 🌐 Cloud Browser Sessions - Access remote browsers with full control
- 🔒 Secret Vaults - Enterprise-grade credential management with end-to-end encryption
- 👤 Digital Personas - Complete identities with email, SMS, and 2FA for automated account creation
- 🎯 Session Management - Context managers for reliable resource cleanup
- 📝 Type Safety - Full TypeScript support with generated types
- ⚡ Serverless Functions - Run pre-built automation workflows with simple API calls
- 🔍 Web Search, Secrets and Usage - Search the web, manage workspace secrets and read billing usage
- 🔄 Auto-sync - Generated from OpenAPI spec, always up-to-date
npm install notte-sdkimport { NotteClient } from 'notte-sdk';
const notte = new NotteClient({
apiKey: 'your-api-key', // pragma: allowlist secret
baseUrl: 'https://api.notte.cc' // optional, defaults to https://api.notte.cc
});
// Use session with automatic cleanup (mirrors Python's context manager)
await notte.Session({ idle_timeout_minutes: 5 }).use(async (session) => {
const status = await session.status();
console.log('Session status:', status);
});The main client for interacting with the Notte API.
import { NotteClient } from 'notte-sdk';
const notte = new NotteClient({
apiKey: process.env.NOTTE_API_KEY, // defaults to NOTTE_API_KEY
baseUrl: 'https://api.notte.cc', // defaults to NOTTE_API_URL or https://api.notte.cc
timeoutMs: 60_000, // per-request timeout, default 60 s
verbose: false, // log every request
});
// Create sessions
const session = notte.Session({ idle_timeout_minutes: 5, max_duration_minutes: 30 });
// Create vaults
const vault = notte.Vault({ name: 'My Vault' });
// Create personas
const persona = notte.Persona({ create_vault: true });
// Check that the API is reachable (rejects with NotteAPIError otherwise)
await notte.healthCheck();Every resource has a list() namespace, mirroring client.sessions.list() and
friends in Python. Options are the generated query parameters of the matching
endpoint (SessionListOptions, VaultListOptions,
FunctionListOptions, PersonaListOptions).
const sessions = await notte.sessions.list({ only_active: true });
const vaults = await notte.vaults.list();
const functions = await notte.functions.list();
const personas = await notte.personas.list({ only_active: true });Set NOTTE_DB_PREVIEW_BRANCH (or pass dbPreview to the client) to route every
request and websocket handshake to a database preview branch. This is an internal
option used when testing API changes.
Manages browser sessions with automatic lifecycle management.
import { NotteClient } from 'notte-sdk';
const notte = new NotteClient();
// Context manager pattern (automatic start/stop)
await notte.Session({ idle_timeout_minutes: 5 }).use(async (session) => {
// Session is automatically started
console.log('Session ID:', session.getId());
const status = await session.status();
console.log('Session status:', status);
// Session is automatically stopped when done
});
// Manual session management
const session = notte.Session({ open_viewer: true });
await session.start();
try {
const status = await session.status();
console.log('Session status:', status);
} finally {
await session.stop();
}
// Async iterator pattern
for await (const session of notte.Session({ max_duration_minutes: 15 })) {
// Use session here
const status = await session.status();
console.log('Session active:', status);
// Session automatically closed after loop
}Drive the page with typed actions, exactly like session.execute(...) in Python.
Element ids (B1, I2, ...) must come from a live observe() call.
import { NotteClient, actions, ActionExecutionError } from 'notte-sdk';
const notte = new NotteClient();
await notte.Session().use(async (session) => {
await session.execute(actions.goto({ url: 'https://www.notte.cc' }));
const observation = await session.observe({ perception_type: 'deep' });
console.log(observation.space);
// Plain objects work too; the `type` field is a discriminated union
await session.execute({ type: 'click', id: 'B1' });
await session.execute(actions.fill({ selector: 'internal:text="Email"', value: 'me@example.com' }));
// A failed action throws ActionExecutionError with the server-side error type
try {
await session.execute(actions.click({ id: 'B999' }));
} catch (error) {
if (error instanceof ActionExecutionError) console.log(error.errorType, error.userMessage);
}
// Or inspect the result yourself
const result = await session.execute(actions.click({ id: 'B999' }), { raiseOnFailure: false });
console.log(result.success, result.message);
// Run JavaScript in the page and fetch through the page (cookies, proxy, fingerprint).
// Relative URLs resolve against the page; plaintext http targets are refused unless allowInsecure is set.
const title = await session.evaluateJs('document.title');
const summary = await session.fetch('/api/rest_v1/page/summary/Main_Page').then(r => r.json());
// Scrape the current page
const markdown = await session.scrape({ only_main_content: true });
// Cookies
const cookies = await session.getCookies();
await session.setCookies(cookies);
});playwright-core is an optional peer dependency (npm install playwright-core),
the counterpart of pip install notte-sdk[playwright]:
await notte.Session().use(async (session) => {
const page = await session.page(); // connected over CDP, cached
await page.goto('https://www.notte.cc');
await page.screenshot({ path: 'screenshot.png' });
// Or connect your own tooling
const cdpUrl = await session.cdpUrl();
});// Persist login state between runs: loaded on start, appended on stop
const session = notte.Session({ cookie_file: './cookies.json' });
await session.use(async () => { /* ... */ });
// Replays are generated after stop(); replay() polls until ready (240 s by default)
const replay = await session.replay();
await session.downloadReplay('./session.mp4');start()- Start the session (retries 5xx errors 3 times, waits 30 s on cluster overload)stop(closeReason?)- Stop the sessionstatus()- Get session statususe(callback)- Context manager patterngetId()- Get session IDisSessionActive()- Check if session is runningexecute(action, options?)- Execute a typed action; throwsActionExecutionErroron failure unlessraiseOnFailure: falseobserve(options?)- Observe the page (perception_type,min_nb_actions,max_nb_actions)scrape(options?)- Scrape the page as markdown, images, or structured data (see Scraping)evaluateJs(code, options?)- Evaluate JavaScript and return the stringified resultfetch(url, options?)- Issue an HTTP request from inside the pagesetCookies(cookies)/setCookiesFromFile(path)/getCookies()- Manage cookiescdpUrl()- Chrome DevTools Protocol websocket URLpage()- PlaywrightPageconnected over CDP (requiresplaywright-core)debugInfo()/debugTabInfo(tabIdx?)- Debug URLs and tab metadatareplay(options?)/downloadReplay(path?)- Session recordinggetScript(options?)- Python code reproducing the sessionviewer()- Open the live session viewer in the local default browserstorage- TheRemoteFileStorageattached with thestorageoptionclient.sessions.list(options?)- List sessions
Manage credentials securely with enterprise-grade encryption.
import { NotteClient } from 'notte-sdk';
const notte = new NotteClient();
// Create a new vault
const vault = notte.Vault({ name: 'My Secure Vault' });
// Add credentials for websites
await vault.addCredentials('https://github.com/', {
email: 'user@example.com',
password: 'secure-password', // pragma: allowlist secret
mfa_secret: 'PYNT7I67RFS2EPR5' // pragma: allowlist secret
});
// Generate secure passwords
const strongPassword = vault.generatePassword(20, true); // 20 chars with special chars
const simplePassword = vault.generatePassword(12, false); // 12 chars, no special chars
// Access existing vault
const existingVault = notte.Vault({ vault_id: 'vault-abc-123' });
const credentials = await existingVault.listCredentials();
// Cleanup
await vault.deleteCredentials('https://github.com/');
await vault.stop(); // Deletes entire vaultaddCredentials(url, credentials)- Store credentials for a URL (validated client-side like Python: root domain, exactly one ofemail/username, base32mfa_secret)addCredentialsFromEnv(url)- Read{DOMAIN}_EMAIL/{DOMAIN}_USERNAME/{DOMAIN}_PASSWORD/{DOMAIN}_MFA_SECRETfrom the environmentgetCredentials(url)- Retrieve credentials for a URL; throwsNotteAPIErrorwhen none existhasCredential(url)- Whether credentials exist for a URLdeleteCredentials(url)- Delete credentials for a URLlistCredentials()- List all stored credentialsgeneratePassword(length?, includeSpecialChars?)- Generate secure passwordsdelete()- Delete the entire vaultstop()- Stop and delete the vaultclient.vaults.list(options?)- List vaults
- End-to-End Encryption - All credentials encrypted at rest and in transit
- Zero Trust Architecture - Credentials never exposed to LLMs or external services
- Access Control - Strict access logging and permissions
- Two-Factor Authentication - Support for MFA secrets (TOTP)
Manage complete digital identities for automated account creation and 2FA.
import { NotteClient } from 'notte-sdk';
const notte = new NotteClient();
// Create a new persona with a vault
const persona = notte.Persona({ create_vault: true });
// Get persona information (the first call initializes the persona)
const info = await persona.get();
console.log(`Email: ${info.email}`);
// Read emails sent to the persona
const emails = await persona.emails({
limit: 10,
only_unread: true
});
console.log(`Received ${emails.length} new emails`);
// Read SMS messages for 2FA codes
const smsMessages = await persona.sms({
limit: 5,
only_unread: true
});
console.log(`Received ${smsMessages.length} SMS messages`);
// Access existing persona
const existingPersona = notte.Persona({ persona_id: 'persona-abc-123' });
const personaEmails = await existingPersona.emails();
// Add credentials to persona's vault
await persona.addCredentials('https://github.com/');
// This automatically generates a secure password and stores:
// - Email: persona's email
// - Password: generated secure password
// Cleanup
await persona.stop(); // Deletes persona and all associated dataget()- Fetch (and initialize) the persona informationemails(options?)- Read emails sent to the personasms(options?)- Read SMS messages sent to the personaaddCredentials(url)- Add auto-generated credentials to the persona's vaultdelete()- Delete the personastop()- Stop and delete the persona
const options = {
limit: 10, // Maximum number of messages (default: unlimited)
only_unread: true, // Only return unread messages (default: false)
timedelta: '1h' // Maximum age of messages (e.g., '1h', '30m', '24h')
};
const emails = await persona.emails(options);
const sms = await persona.sms(options);- Complete Digital Identity - Unique email address
- 2FA Support - Receive and read SMS verification codes automatically
- Vault Integration - Optional secure credential storage
- Email Management - Full email reading capabilities
- Message Tracking - Mark messages as read/unread automatically
Run pre-built automation workflows (Notte Functions) with a simple API call. Functions are serverless workflows that run on Notte's infrastructure — you provide the function_id and input variables, and get the result back.
import { NotteClient } from 'notte-sdk';
const notte = new NotteClient();
// Create a function instance
const fn = notte.NotteFunction({
function_id: 'your-function-id',
decryption_key: 'optional-decryption-key' // optional, for encrypted outputs
});
// Stream logs to console.log and wait for the final result (default)
const result = await fn.run({
url: 'https://example.com',
query: 'extract pricing information'
});
console.log('Run ID:', result.function_run_id);
console.log('Result:', result.result);
// Retrieve the run result / metadata later
const metadata = await fn.getRun(result.function_run_id);
console.log('Status:', metadata.status); // 'active' | 'closed' | 'failed'
console.log('Result:', metadata);run defaults to the standard Lambda runtime. Where the extended runtime pilot
is enabled, select AgentCore per invocation with the second argument:
const result = await fn.run(
{ url: 'https://example.com' },
{ runtime: 'extended' }
);
// The default is equivalent to an explicit standard selection.
await fn.run({ url: 'https://example.com' }, { runtime: 'standard' });The runtime option stays separate from script input variables. Extended requests fail explicitly if that runtime is unavailable or at capacity; they do not fall back to Lambda. Browser sessions keep their own duration limits. Existing schedules continue to use the standard runtime during the pilot.
createRun() is optional: use it when you need a run ID before execution starts.
It only creates the cloud run record. Pass the returned ID as functionRunId to
execute that record; omitting functionRunId makes run() create a new run.
const fn = notte.NotteFunction({ function_id: 'your-function-id' });
const { function_run_id } = await fn.createRun();
console.log('Run ID before execution:', function_run_id);
const pending = fn.run(
{ url: 'https://example.com' },
{ functionRunId: function_run_id },
);
// Do other work here, then wait for completion.
const result = await pending;
console.log(result.result);
const metadata = await fn.getRun(function_run_id);
console.log(metadata.status);getRun() fetches the current status and stored result without waiting for execution
to finish. retrieve() remains a compatible alias. Execution results contain the
function's structured output; the stored metadata.result is serialized JSON.
Keeping the promise lets your JavaScript continue while the HTTP request remains
open. Keep the process running until it completes. This is not a detached job.
With { stream: false }, the SDK returns JSON without live logs, but standard
execution still waits for completion.
Breaking change: run() now defaults to stream: true, matching the Python SDK.
It consumes the event stream, prints logs as they arrive, and resolves with the
final response, including status and result. Existing callers that require a
JSON response without live logs must add { stream: false } as the second argument.
Failed executions throw by default. Set raiseOnFailure: false to inspect the
failed response yourself. Transport and malformed-stream errors still throw.
Use onLog to route logs to your application (or onLog: () => {} to silence them):
const result = await fn.run({ url: 'https://example.com' }, {
onLog: message => console.log('[function]', message),
raiseOnFailure: false,
});Options belong in the second argument; a stream field in the first argument
is a script input and does not change streaming behavior.
const fn = notte.NotteFunction({ function_id: 'your-function-id' });
// Get the python source of the function
const code = await fn.download();
// Or write it straight to a file (the path must end with .py)
await fn.download({ path: './my-function.py' });
// A specific version, and the presigned url on its own
const url = await fn.getUrl({ version: 'v1.0.0' });Notte managed functions return an encrypted url. Pass the key on the function, or per call, and it is decrypted for you:
const fn = notte.NotteFunction({
function_id: 'your-function-id',
decryption_key: 'your-decryption-key',
});
const code = await fn.download();Upload a Python script to create a function, like NotteFunction(path=...) in Python.
Creation is lazy: the upload happens on the first call that needs the function id.
const fn = notte.NotteFunction({ path: './my_function.py', name: 'price-monitor', description: 'Checks prices' });
const details = await fn.get(); // metadata, versions and download link
await fn.update({ path: './my_function_v2.py' }); // upload a new version
await fn.updateMetadata({ name: 'price-monitor-v2' });
await fn.rollback({ version: details.latest_version });
await fn.setSchedule({ cron: '0 9 * * *', variables: { url: 'https://example.com' } });
await fn.deleteSchedule();
const runs = await fn.runs(); // all runs, including completed ones
const functions = await notte.functions.list();
await fn.delete();run() waits at most 300 s by default; pass timeoutMs to change it. A failed
run throws FailedToRunCloudFunctionError unless raiseOnFailure: false.
run(variables?, options?)- Stream logs and wait for completion; usestream: falsefor the backend's JSON response without live logs. Options:runtime,functionRunId,onLog,raiseOnFailure,timeoutMsget(options?)- Function metadata, versions and download linkupdate(options)/updateMetadata(body)- Upload a new version / change name, descriptiondelete()- Delete the functionsetSchedule(body)/deleteSchedule()- Manage the cron schedulerollback(options)- Roll back to a previous versionruns(options?)- List runs (all runs by default,only_active: truefor active ones)createRun()- Create a cloud run record without executing itgetRun(functionRunId)- Get current metadata/result for a specific runretrieve(functionRunId)- Compatibility alias forgetRun()download(options?)- Download the function code, optionally writing it tooptions.pathgetUrl(options?)- Get the (decrypted) download url of the function codegetFunctionId()- Get the function IDfunctionId- The function ID (read-only property)decryptionKey- The decryption key, if provided (read-only property)
Upload files for a session to use and download files the session produced,
the counterpart of client.FileStorage() in Python.
import { NotteClient, FileExistsError } from 'notte-sdk';
const notte = new NotteClient();
const storage = notte.FileStorage();
let sessionId = '';
let fileId = '';
await notte.Session({ storage }).use(async (session) => {
sessionId = session.getId()!;
// Upload from a path, a Blob or a Uint8Array
const uploaded = await storage.upload('./invoice.pdf');
fileId = uploaded.id;
// ... download a file through the session ...
const downloads = await storage.list({ source: 'session_download' });
for (const file of downloads.files) {
const path = await storage.download(file.id, './downloads');
console.log('saved', path);
}
const info = await storage.metadata(uploaded.id);
await storage.delete(uploaded.id);
});
// Files of a closed session
const files = notte.FileStorage(sessionId);
await files.download(fileId, './downloads', { force: true }); // overwrite instead of FileExistsErrornotte.Files(sessionId) exposes the same operations returning Blobs instead of writing to disk.
Search the public web with POST /search. The default outputType returns
ranked results; sourcedAnswer returns a written answer with its sources.
const { results } = await notte.search('notte browser automation', { depth: 'fast' });
results.forEach(item => console.log(item.name, item.url));
const { answer, sources } = await notte.search('what is notte?', { outputType: 'sourcedAnswer' });
console.log(answer, sources.map(s => s.url));Extra fields such as maxResults or includeDomains are forwarded to the search
provider. SearchOptions and the SearchResultsResponse /
SearchSourcedAnswerResponse shapes are exported.
notte.anything.start({ task }) (POST /anything/start) turns a plain-English
description of a web task into a deployed, reusable function. Building is slow and
billable; prefer notte.functions.list() when a ready-made function already fits.
The API response is not typed by the OpenAPI spec and is exposed as an opaque
AnythingStartResponse record (it references the built function, e.g. function_id).
const started = await notte.anything.start({ task: 'fetch the top 3 hacker news posts' });
console.log(started);Workspace secrets (/secrets) are scoped by namespace: llm_provider keys are
used by agents and scraping, function_env values are injected into function runs.
Listing never returns values, only metadata with a key_hint.
const meta = await notte.secrets.store({ namespace: 'function_env', name: 'API_TOKEN', value: 'xyz' });
const { value } = await notte.secrets.get('API_TOKEN', 'function_env');
const secrets = await notte.secrets.list({ namespace: 'function_env' });
await notte.secrets.delete(meta.id);Read billing usage for the current (or a given) monthly period, and page through per-request usage logs.
const usage = await notte.usage.get();
console.log(`${usage.session_count} sessions, $${usage.total_cost} this period`);
const page = await notte.usage.logs({ endpoint: 'sessions.start', page: 1, page_size: 50 });
page.items.forEach(log => console.log(log.created_at, log.endpoint, log.duration_ms));const session = notte.Session({
idle_timeout_minutes: 5, // Close the session after this much inactivity
max_duration_minutes: 45, // Hard limit on the session lifetime
// Add other session options as supported by the API (SessionOptions)
});Every error thrown by the SDK extends NotteError, mirroring notte_sdk.errors
in Python. Generated API calls reject with a NotteAPIError carrying the request
path, the HTTP statusCode and the parsed response body in error; the API can
flag failures as NotteAPIExecutionError. Client-side problems use dedicated
classes: AuthenticationError (missing key), InvalidRequestError,
NotteTimeoutError (request or wait deadline exceeded),
FailedToRunCloudFunctionError, ScrapeFailedError and ActionExecutionError.
import { NotteAPIError, NotteError, NotteTimeoutError } from 'notte-sdk';
try {
await notte.Session().use(async (session) => {
await session.execute({ type: "goto", url: "https://example.com" });
});
} catch (error) {
if (error instanceof NotteAPIError) {
console.error(`API ${error.statusCode} on ${error.path}:`, error.apiMessage ?? error.error);
} else if (error instanceof NotteTimeoutError) {
console.error('Timed out:', error.message);
} else if (error instanceof NotteError) {
console.error('SDK error:', error.message);
} else {
throw error;
}
}CAPTCHA solving has a separate captchaTimeoutSeconds budget (default 180).
session.execute() polls pending solves using requests of at most 10 seconds,
then resumes an ordinary action only when the backend confirms it did not execute.
Actions that already ran are never replayed. Navigation, closure, provider failure,
and deadline expiry preserve the normal raiseOnFailure behavior. Ordinary HTTP
transport failures are not automatically retried. Deploy backend CAPTCHA polling
support before upgrading the SDK.
The client applies a per-request timeout (timeoutMs, default 60 000 ms; 0
disables it) through response-body consumption and rejects with NotteTimeoutError
when it elapses. Explicit streams use the caller's signal for body cancellation;
function runs supply their own execution deadline. retry() wraps an async function so it is re-attempted on failure, like the Python
@retry decorator:
import { NotteClient, retry } from 'notte-sdk';
const notte = new NotteClient({ timeoutMs: 30_000 });
const start = retry(() => notte.Session().start(), {
maxTries: 3,
delayMs: 1_000,
shouldRetry: error => error instanceof NotteTimeoutError,
});
await start();For backward compatibility, you can still use the legacy client creation:
import { createClient, client } from 'notte-sdk';
// Legacy method 1
const legacyClient = createClient({
baseUrl: 'https://api.notte.cc',
token: 'your-api-key'
});
// Legacy method 2
import { client } from 'notte-sdk';
client.setConfig({ baseUrl: 'https://api.notte.cc' });
client.interceptors.request.use((request) => {
request.headers.set('Authorization', 'Bearer your-api-key');
return request;
});This TypeScript SDK closely mirrors the Python SDK patterns:
from notte_sdk import NotteClient
notte = NotteClient()
# Session context manager
with notte.Session(idle_timeout_minutes=2) as session:
status = session.status()
print(status)import { NotteClient } from 'notte-sdk';
const notte = new NotteClient();
// Session context manager equivalent
await notte.Session({ idle_timeout_minutes: 2 }).use(async (session) => {
const status = await session.status();
console.log(status);
});# Install dependencies
npm install
# Generate client from OpenAPI spec
npm run generate
# Build the SDK
npm run build
# Run tests
npm test
# Type checking
npm run typecheckThe SDK includes comprehensive tests:
# Run all tests
npm test
# Run tests with coverage
npm run test -- --coverage
# Run specific test file
npm run test test/client.test.tsThe SDK is auto-generated from the Notte OpenAPI specification:
# Regenerate client code
npm run generateThis will fetch the latest API specification and update the generated client code.
The SDK is built with full TypeScript support:
import { NotteClient, type SessionResponse } from 'notte-sdk';
const notte = new NotteClient();
// All types are properly inferred
await notte.Session().use(async (session) => {
const status: SessionResponse = await session.status();
});The SDK exports all generated types from the OpenAPI specification alongside the hand-written option types:
import type {
NotteClientConfig,
SessionOptions,
SessionResponse,
SessionListOptions,
VaultConstructor,
VaultListOptions,
CredentialsDictInput,
Credential,
PersonaConstructor,
MessageReadOptions,
PersonaListOptions,
FunctionConstructor,
FunctionListOptions,
RetryOptions,
NotteAPIErrorBody,
SearchOptions,
SearchResultsResponse,
AnythingStartResponse,
SecretMetadata,
UsageResponse,
} from 'notte-sdk';MIT
For issues and questions:
- GitHub Issues: Report issues
- Documentation: docs.notte.cc
- Python SDK: notte-sdk
Use existing connection IDs with a session. start() waits for authentication by
default, and .use() always invokes its callback only after the session is ready:
const session = client.Session({ auth_ids: ["<connection-id>"], auth_retry: 2 });
await session.use(async (ready) => {
await ready.execute({ type: "goto", url: "https://example.com" });
});For a nonblocking start, wait explicitly before browser actions or CDP access:
const abortController = new AbortController();
const session = client.Session({ auth_ids: ["<connection-id>"] });
await session.start({ wait_for_authentication: false });
try {
await session.waitForAuth({ timeoutMs: 615_000, signal: abortController.signal });
await session.execute({ type: "goto", url: "https://example.com" });
} finally {
await session.stop();
}Waiting polls session readiness through short requests. It never restarts the
session or submits another login. auth_retry controls server-side login retries
(0 to 2) and is omitted from requests without auth_ids. A failed or cancelled
startup wait closes the created session. A standalone waitForAuth() leaves
cleanup to the caller.
Connection operations are available through client.managedAuth:
const check = await client.managedAuth.checkConnection("<connection-id>");
// checkConnection only verifies; refreshConnection can log in if needed.
const operation = await client.managedAuth.refreshConnection("<connection-id>", {
wait: false,
auth_retry: 2,
});
const completed = await client.managedAuth.waitForAuth(operation);
// To explicitly request a new login:
await client.managedAuth.reauthenticateConnection("<connection-id>");Refresh and reauthentication wait by default. Use getOperation(operation.id)
for individual status reads. Cancelling an operation wait does not cancel the
server operation. An HTTP 409 remains an ownership conflict and is not retried.
These helpers require a backend with the managed-auth lifecycle endpoints.