Skip to content

Latest commit

 

History

History
1038 lines (790 loc) · 36.8 KB

README.md

File metadata and controls

1038 lines (790 loc) · 36.8 KB

notte-sdk

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.

Contributing

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-run

npm 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     # production

Review 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.

Generated resource methods

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.ts

This 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:

  • tests runs unit tests, local HTTP integration tests, type checks, and package checks on Node.js 22 and 24, including for fork PRs.

  • integration-tests runs the complete live integration suite against staging on same-repository PRs. Fork PRs skip this credentialed job.

  • generated checks 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.

Releases

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.yml publishes the Python packages to PyPI as X.Y.Z.
  • node-sdk-publish.yml sets this package's version to X.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.

Features

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.

Server-side proxy security

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

Installation

npm install notte-sdk

Quick Start

Basic Session Usage

import { 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);
});

Core Classes

NotteClient

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();

Listing resources

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 });

Database preview branches

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.

Session

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
}

Browser control

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 access

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();
});

Replays and cookie files

// 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');

Session Methods

  • start() - Start the session (retries 5xx errors 3 times, waits 30 s on cluster overload)
  • stop(closeReason?) - Stop the session
  • status() - Get session status
  • use(callback) - Context manager pattern
  • getId() - Get session ID
  • isSessionActive() - Check if session is running
  • execute(action, options?) - Execute a typed action; throws ActionExecutionError on failure unless raiseOnFailure: false
  • observe(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 result
  • fetch(url, options?) - Issue an HTTP request from inside the page
  • setCookies(cookies) / setCookiesFromFile(path) / getCookies() - Manage cookies
  • cdpUrl() - Chrome DevTools Protocol websocket URL
  • page() - Playwright Page connected over CDP (requires playwright-core)
  • debugInfo() / debugTabInfo(tabIdx?) - Debug URLs and tab metadata
  • replay(options?) / downloadReplay(path?) - Session recording
  • getScript(options?) - Python code reproducing the session
  • viewer() - Open the live session viewer in the local default browser
  • storage - The RemoteFileStorage attached with the storage option
  • client.sessions.list(options?) - List sessions

Secret Vaults

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 vault

Vault Methods

  • addCredentials(url, credentials) - Store credentials for a URL (validated client-side like Python: root domain, exactly one of email/username, base32 mfa_secret)
  • addCredentialsFromEnv(url) - Read {DOMAIN}_EMAIL / {DOMAIN}_USERNAME / {DOMAIN}_PASSWORD / {DOMAIN}_MFA_SECRET from the environment
  • getCredentials(url) - Retrieve credentials for a URL; throws NotteAPIError when none exist
  • hasCredential(url) - Whether credentials exist for a URL
  • deleteCredentials(url) - Delete credentials for a URL
  • listCredentials() - List all stored credentials
  • generatePassword(length?, includeSpecialChars?) - Generate secure passwords
  • delete() - Delete the entire vault
  • stop() - Stop and delete the vault
  • client.vaults.list(options?) - List vaults

Security Features

  • 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)

Personas

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 data

Persona Methods

  • get() - Fetch (and initialize) the persona information
  • emails(options?) - Read emails sent to the persona
  • sms(options?) - Read SMS messages sent to the persona
  • addCredentials(url) - Add auto-generated credentials to the persona's vault
  • delete() - Delete the persona
  • stop() - Stop and delete the persona

Message Reading Options

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);

Persona Features

  • 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

Functions

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);

Selecting the runtime

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.

Create a run before execution

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.

Streaming options and migration

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.

Downloading the function code

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();

Creating and managing functions

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.

Function Methods

  • run(variables?, options?) - Stream logs and wait for completion; use stream: false for the backend's JSON response without live logs. Options: runtime, functionRunId, onLog, raiseOnFailure, timeoutMs
  • get(options?) - Function metadata, versions and download link
  • update(options) / updateMetadata(body) - Upload a new version / change name, description
  • delete() - Delete the function
  • setSchedule(body) / deleteSchedule() - Manage the cron schedule
  • rollback(options) - Roll back to a previous version
  • runs(options?) - List runs (all runs by default, only_active: true for active ones)
  • createRun() - Create a cloud run record without executing it
  • getRun(functionRunId) - Get current metadata/result for a specific run
  • retrieve(functionRunId) - Compatibility alias for getRun()
  • download(options?) - Download the function code, optionally writing it to options.path
  • getUrl(options?) - Get the (decrypted) download url of the function code
  • getFunctionId() - Get the function ID
  • functionId - The function ID (read-only property)
  • decryptionKey - The decryption key, if provided (read-only property)

File Storage

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 FileExistsError

notte.Files(sessionId) exposes the same operations returning Blobs instead of writing to disk.

Web Search

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.

Anything API

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);

Secrets

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);

Usage

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));

Advanced Usage

Session Configuration

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)
});

Error Handling

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;
  }
}

Timeouts and retries

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();

Legacy API Support

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;
});

Python SDK Equivalence

This TypeScript SDK closely mirrors the Python SDK patterns:

Python

from notte_sdk import NotteClient

notte = NotteClient()

# Session context manager
with notte.Session(idle_timeout_minutes=2) as session:
    status = session.status()
    print(status)

TypeScript

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);
});

Development

Building the SDK

# 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 typecheck

Testing

The 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.ts

Regenerating from API

The SDK is auto-generated from the Notte OpenAPI specification:

# Regenerate client code
npm run generate

This will fetch the latest API specification and update the generated client code.

TypeScript Support

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();
});

API Reference

Types

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';

License

MIT

Support

For issues and questions:

Managed authentication

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.