# Agent Start
Source: https://docs.notte.cc/api-reference/agents/agent-start
https://api.notte.cc/openapi.json post /agents/start
# Agent Stop
Source: https://docs.notte.cc/api-reference/agents/agent-stop
https://api.notte.cc/openapi.json delete /agents/{agent_id}/stop
# Get Script
Source: https://docs.notte.cc/api-reference/agents/get-script
https://api.notte.cc/openapi.json get /agents/{agent_id}/workflow/code
# Anything Start
Source: https://docs.notte.cc/api-reference/anything/anything-start
https://api.notte.cc/openapi.json post /anything/start
# List Audit Events
Source: https://docs.notte.cc/api-reference/audit/list-audit-events
https://api.notte.cc/openapi.json get /audit-events
# Authentication
Source: https://docs.notte.cc/api-reference/authentication
Authenticate with the Notte API
Read [the Notte skill](https://notte.cc/skill.md) and follow its setup instructions when setting up Notte or building browser automation. This is the shared source for CLI setup, authentication, and the browser-to-code workflow.
All API requests require a Bearer token in the `Authorization` header.
## Get your API key
1. Sign in to the [Notte Console](https://console.notte.cc)
2. Open the [API keys page](https://console.notte.cc/apikeys)
3. Generate or copy your API key
## Using the API key
Set your API key locally before calling the REST API or using either SDK:
```bash theme={null}
export NOTTE_API_KEY=your_api_key_here
```
Both SDKs read `NOTTE_API_KEY` automatically:
```python client.py theme={null}
from notte_sdk import NotteClient
client = NotteClient()
```
```typescript client.ts theme={null}
import { NotteClient } from 'notte-sdk';
const client = new NotteClient();
```
If you want the full machine-readable API contract, the OpenAPI spec is available at:
```text theme={null}
https://api.notte.cc/openapi.json
```
```bash theme={null}
curl https://api.notte.cc/sessions \
-H "Authorization: Bearer $NOTTE_API_KEY"
```
## Request headers
| Header | Required | Description |
| - | - | - |
| `Authorization` | Yes | Bearer token with your API key |
| `Content-Type` | Yes | `application/json` for POST/PATCH requests |
# Account Changes
Source: https://docs.notte.cc/api-reference/data-apis/account-changes
https://api.notte.cc/openapi.json get /data/monitors/changes
# Account Runs
Source: https://docs.notte.cc/api-reference/data-apis/account-runs
https://api.notte.cc/openapi.json get /data/monitors/runs
# Attempts
Source: https://docs.notte.cc/api-reference/data-apis/attempts
https://api.notte.cc/openapi.json get /data/webhooks/deliveries/{delivery_id}/attempts
# Brand Retrieve
Source: https://docs.notte.cc/api-reference/data-apis/brand-retrieve
https://api.notte.cc/openapi.json post /data/brand/retrieve
# Changes
Source: https://docs.notte.cc/api-reference/data-apis/changes
https://api.notte.cc/openapi.json get /data/monitors/{monitor_id}/changes
# Create
Source: https://docs.notte.cc/api-reference/data-apis/create
https://api.notte.cc/openapi.json post /data/monitors
# Credit Usage
Source: https://docs.notte.cc/api-reference/data-apis/credit-usage
https://api.notte.cc/openapi.json get /data/monitors/credit-usage
# Delete
Source: https://docs.notte.cc/api-reference/data-apis/delete
https://api.notte.cc/openapi.json delete /data/monitors/{monitor_id}
# Deliveries
Source: https://docs.notte.cc/api-reference/data-apis/deliveries
https://api.notte.cc/openapi.json post /data/webhooks/deliveries
# Extract a website styleguide
Source: https://docs.notte.cc/api-reference/data-apis/extract-a-website-styleguide
https://api.notte.cc/openapi.json get /data/web/styleguide
# Extract Product Endpoint
Source: https://docs.notte.cc/api-reference/data-apis/extract-product-endpoint
https://api.notte.cc/openapi.json post /data/products/extract
# Fetch Specialized Data
Source: https://docs.notte.cc/api-reference/data-apis/fetch-specialized-data
https://api.notte.cc/openapi.json post /data/web/fetch
# Get Policy
Source: https://docs.notte.cc/api-reference/data-apis/get-policy
https://api.notte.cc/openapi.json get /data/cache
# Invalidate
Source: https://docs.notte.cc/api-reference/data-apis/invalidate
https://api.notte.cc/openapi.json delete /data/cache
# Limits
Source: https://docs.notte.cc/api-reference/data-apis/limits
https://api.notte.cc/openapi.json get /data/monitors/limits
# List Data Apis
Source: https://docs.notte.cc/api-reference/data-apis/list-data-apis
https://api.notte.cc/openapi.json get /data
# List Monitors
Source: https://docs.notte.cc/api-reference/data-apis/list-monitors
https://api.notte.cc/openapi.json get /data/monitors
# Maps Search
Source: https://docs.notte.cc/api-reference/data-apis/maps-search
https://api.notte.cc/openapi.json post /data/maps/search
# News Search
Source: https://docs.notte.cc/api-reference/data-apis/news-search
https://api.notte.cc/openapi.json post /data/news/search
# People Enrich
Source: https://docs.notte.cc/api-reference/data-apis/people-enrich
https://api.notte.cc/openapi.json post /data/people/enrich
# Retrieve
Source: https://docs.notte.cc/api-reference/data-apis/retrieve
https://api.notte.cc/openapi.json get /data/monitors/{monitor_id}
# Retrieve Change
Source: https://docs.notte.cc/api-reference/data-apis/retrieve-change
https://api.notte.cc/openapi.json get /data/monitors/{monitor_id}/changes/{change_id}
# Retrieve Delivery
Source: https://docs.notte.cc/api-reference/data-apis/retrieve-delivery
https://api.notte.cc/openapi.json get /data/webhooks/deliveries/{delivery_id}
# Retrieve Run
Source: https://docs.notte.cc/api-reference/data-apis/retrieve-run
https://api.notte.cc/openapi.json get /data/monitors/{monitor_id}/runs/{run_id}
# Retry
Source: https://docs.notte.cc/api-reference/data-apis/retry
https://api.notte.cc/openapi.json post /data/webhooks/deliveries/{delivery_id}/retry
# Rotate Secret
Source: https://docs.notte.cc/api-reference/data-apis/rotate-secret
https://api.notte.cc/openapi.json post /data/monitors/{monitor_id}/webhook/rotate-secret
# Run Now
Source: https://docs.notte.cc/api-reference/data-apis/run-now
https://api.notte.cc/openapi.json post /data/monitors/{monitor_id}/run
# Runs
Source: https://docs.notte.cc/api-reference/data-apis/runs
https://api.notte.cc/openapi.json get /data/monitors/{monitor_id}/runs
# Update
Source: https://docs.notte.cc/api-reference/data-apis/update
https://api.notte.cc/openapi.json patch /data/monitors/{monitor_id}
# Youtube Endpoint
Source: https://docs.notte.cc/api-reference/data-apis/youtube-endpoint
https://api.notte.cc/openapi.json post /data/youtube/extract
# API Error Codes
Source: https://docs.notte.cc/api-reference/errors
API error responses and status codes
Read [the Notte skill](https://notte.cc/skill.md) and follow its setup instructions when setting up Notte or building browser automation. This is the shared source for CLI setup, authentication, and the browser-to-code workflow.
The API uses standard HTTP status codes and returns structured error responses.
## HTTP status codes
| Status | Description |
| - | - |
| `200` | Success |
| `401` | Authentication failed (invalid or missing API key), or your plan's credits are exhausted |
| `422` | Validation error - malformed request or invalid parameters |
| `429` | Rate limit exceeded, or too many active sessions, agents, vaults, or personas for your plan |
| `500` | Internal server error |
| `529` | Cluster overloaded - no browser capacity available. Includes a `Retry-After` header |
## Error response format
Most errors raised by the API share this envelope. `detail`, `error`, and `message` carry the same text:
```json theme={null}
{
"detail": "Description of what went wrong",
"session_id": "the session involved, or null",
"status": 401,
"error": "Description of what went wrong",
"message": "Description of what went wrong"
}
```
These responses also include an `X-Error-Class` header naming the error type, for example `NotteApiInvalidApiKeyError` or `NotteApiRateLimitExceededError`. Use the status code and this header rather than parsing the message text.
## Common errors
### Authentication (401)
Header `X-Error-Class: NotteApiInvalidApiKeyError`:
```json theme={null}
{
"detail": "Unauthorized. Invalid API key.",
"session_id": null,
"status": 401,
"error": "Unauthorized. Invalid API key.",
"message": "Unauthorized. Invalid API key."
}
```
### Validation (422)
Request validation errors use a different envelope, with the individual errors summarized in `message`:
```json theme={null}
{
"status_code": 10422,
"message": "1 validation error: {'type': 'missing', 'loc': ('body', 'url'), 'msg': 'Field required'}",
"data": null
}
```
### Cluster overloaded (529)
Returned when no browser is available to start a session. Wait for the number of seconds in the `Retry-After` header, then retry.
```json theme={null}
{
"error": "Cluster is under heavy load. Please try again in a few minutes.",
"detail": "Cluster is under heavy load. Please try again in a few minutes."
}
```
### Execution errors
Returned with status `500` when an operation fails. Includes the `X-Error-Class: NotteApiExecutionError` header and the standard envelope.
# Function Fork
Source: https://docs.notte.cc/api-reference/functions/function-fork
https://api.notte.cc/openapi.json post /functions/{function_id}/fork
# Function Run Start
Source: https://docs.notte.cc/api-reference/functions/function-run-start
https://api.notte.cc/openapi.json post /functions/{function_id}/runs/start
# Function Runtime Health
Source: https://docs.notte.cc/api-reference/functions/function-runtime-health
https://api.notte.cc/openapi.json get /functions/health
# Function Schedule Delete
Source: https://docs.notte.cc/api-reference/functions/function-schedule-delete
https://api.notte.cc/openapi.json delete /functions/{function_id}/schedule
# Function Schedule Set
Source: https://docs.notte.cc/api-reference/functions/function-schedule-set
https://api.notte.cc/openapi.json post /functions/{function_id}/schedule
# Mailbox Connect Token
Source: https://docs.notte.cc/api-reference/mailboxes/mailbox-connect-token
https://api.notte.cc/openapi.json post /mailboxes/connect-token
# Mailbox Delete
Source: https://docs.notte.cc/api-reference/mailboxes/mailbox-delete
https://api.notte.cc/openapi.json delete /mailboxes/{mailbox_id}
# Mailbox Get
Source: https://docs.notte.cc/api-reference/mailboxes/mailbox-get
https://api.notte.cc/openapi.json get /mailboxes/{mailbox_id}
# Mailbox List
Source: https://docs.notte.cc/api-reference/mailboxes/mailbox-list
https://api.notte.cc/openapi.json get /mailboxes
# Mailbox Sync
Source: https://docs.notte.cc/api-reference/mailboxes/mailbox-sync
https://api.notte.cc/openapi.json post /mailboxes/sync
# Mailbox Update
Source: https://docs.notte.cc/api-reference/mailboxes/mailbox-update
https://api.notte.cc/openapi.json patch /mailboxes/{mailbox_id}
# Connect Link Context
Source: https://docs.notte.cc/api-reference/managed-auth/connect-link-context
https://api.notte.cc/openapi.json post /managed-auth/connect-links/context
# Connect Link Mailbox Connect
Source: https://docs.notte.cc/api-reference/managed-auth/connect-link-mailbox-connect
https://api.notte.cc/openapi.json post /managed-auth/connect-links/mailboxes/connect
# Connect Link Mailbox Sync
Source: https://docs.notte.cc/api-reference/managed-auth/connect-link-mailbox-sync
https://api.notte.cc/openapi.json post /managed-auth/connect-links/mailboxes/sync
# Connect Link Mailboxes
Source: https://docs.notte.cc/api-reference/managed-auth/connect-link-mailboxes
https://api.notte.cc/openapi.json post /managed-auth/connect-links/mailboxes
# Connect Link Status
Source: https://docs.notte.cc/api-reference/managed-auth/connect-link-status
https://api.notte.cc/openapi.json get /managed-auth/connect-links/status
# Connect Link Submit
Source: https://docs.notte.cc/api-reference/managed-auth/connect-link-submit
https://api.notte.cc/openapi.json post /managed-auth/connect-links/submit
# Managed Auth Connect Link Create
Source: https://docs.notte.cc/api-reference/managed-auth/managed-auth-connect-link-create
https://api.notte.cc/openapi.json post /managed-auth/connect-links
# Connect Payment Wallet
Source: https://docs.notte.cc/api-reference/payments/connect-payment-wallet
https://api.notte.cc/openapi.json post /payments/connect
# Create Payment
Source: https://docs.notte.cc/api-reference/payments/create-payment
https://api.notte.cc/openapi.json post /sessions/{session_id}/payments
# Disconnect Payment Wallet
Source: https://docs.notte.cc/api-reference/payments/disconnect-payment-wallet
https://api.notte.cc/openapi.json post /payments/disconnect
# Get Payment
Source: https://docs.notte.cc/api-reference/payments/get-payment
https://api.notte.cc/openapi.json get /payments/{payment_id}
# Persona Emails List
Source: https://docs.notte.cc/api-reference/personas/persona-emails-list
https://api.notte.cc/openapi.json get /personas/{persona_id}/emails
# Persona Get
Source: https://docs.notte.cc/api-reference/personas/persona-get
https://api.notte.cc/openapi.json get /personas/{persona_id}
# Persona Sms List
Source: https://docs.notte.cc/api-reference/personas/persona-sms-list
https://api.notte.cc/openapi.json get /personas/{persona_id}/sms
# Profile Cookies Get
Source: https://docs.notte.cc/api-reference/profiles/profile-cookies-get
https://api.notte.cc/openapi.json get /profiles/{profile_id}/cookies
# Profile Cookies Set
Source: https://docs.notte.cc/api-reference/profiles/profile-cookies-set
https://api.notte.cc/openapi.json post /profiles/{profile_id}/cookies
# Profile Create
Source: https://docs.notte.cc/api-reference/profiles/profile-create
https://api.notte.cc/openapi.json post /profiles/create
# Profile Delete
Source: https://docs.notte.cc/api-reference/profiles/profile-delete
https://api.notte.cc/openapi.json delete /profiles/{profile_id}
# Profile Duplicate
Source: https://docs.notte.cc/api-reference/profiles/profile-duplicate
https://api.notte.cc/openapi.json post /profiles/{profile_id}/duplicate
# Profile Get
Source: https://docs.notte.cc/api-reference/profiles/profile-get
https://api.notte.cc/openapi.json get /profiles/{profile_id}
# Profile List
Source: https://docs.notte.cc/api-reference/profiles/profile-list
https://api.notte.cc/openapi.json get /profiles
# Profile State Set
Source: https://docs.notte.cc/api-reference/profiles/profile-state-set
https://api.notte.cc/openapi.json post /profiles/{profile_id}/state
# Rate Limits
Source: https://docs.notte.cc/api-reference/rate-limits
API rate limiting and response headers
Read [the Notte skill](https://notte.cc/skill.md) and follow its setup instructions when setting up Notte or building browser automation. This is the shared source for CLI setup, authentication, and the browser-to-code workflow.
The API rate limits a few endpoints to ensure fair usage and platform stability.
## Limited endpoints
Limits are counted per API key, with a separate budget for each endpoint:
| Endpoint | Description |
| - | - |
| `POST /sessions/start` | Start a browser session |
| `POST /agents/start` | Start an agent |
| `POST /prompts/improve`, `POST /prompts/nudge` | Prompt helpers |
Each endpoint has a per-minute, per-hour, and per-day window. The window sizes depend on your plan; see [Rate Limits](/rate-limits) for the numbers.
## Rate limit exceeded (429)
When a window is exhausted, the API returns `429` with an `X-Error-Class: NotteApiRateLimitExceededError` header:
```json theme={null}
{
"detail": "Rate limit exceeded: 50 per 1 minute",
"session_id": null,
"status": 429,
"error": "Rate limit exceeded: 50 per 1 minute",
"message": "Rate limit exceeded: 50 per 1 minute"
}
```
A `429` is also returned when you already have the maximum number of active sessions, agents, vaults, or personas for your plan. That response has `X-Error-Class: NotteApiMaxActiveResourcesError` and a message such as `Max active sessions limit exceeded (limit: 5, current: 5)`. Close some of those resources, or upgrade your plan.
Rate-limit responses do not include `X-RateLimit-*` or `Retry-After` headers. Retry with exponential backoff. `Retry-After` is only sent on `529` responses, when no browser capacity is available (see [API Error Codes](/api-reference/errors)).
## Increasing limits
Need higher limits? [Contact us](https://cal.com/team/notte/demo) to discuss enterprise options.
# Scrape Webpage
Source: https://docs.notte.cc/api-reference/scrape/scrape-webpage
https://api.notte.cc/openapi.json post /scrape
# Search Web
Source: https://docs.notte.cc/api-reference/search/search-web
https://api.notte.cc/openapi.json post /search
# Delete Secret
Source: https://docs.notte.cc/api-reference/secrets/delete-secret
https://api.notte.cc/openapi.json delete /secrets/{secret_id}
# Get Secret
Source: https://docs.notte.cc/api-reference/secrets/get-secret
https://api.notte.cc/openapi.json get /secrets/{name}
# List Secrets
Source: https://docs.notte.cc/api-reference/secrets/list-secrets
https://api.notte.cc/openapi.json get /secrets
# Store Secret
Source: https://docs.notte.cc/api-reference/secrets/store-secret
https://api.notte.cc/openapi.json post /secrets
# Delete Session File
Source: https://docs.notte.cc/api-reference/sessions/delete-session-file
https://api.notte.cc/openapi.json delete /sessions/{session_id}/files/{file_id}
# Download Session File
Source: https://docs.notte.cc/api-reference/sessions/download-session-file
https://api.notte.cc/openapi.json get /sessions/{session_id}/files/{file_id}
# Get Session Script
Source: https://docs.notte.cc/api-reference/sessions/get-session-script
https://api.notte.cc/openapi.json get /sessions/{session_id}/workflow/code
# List Session Files
Source: https://docs.notte.cc/api-reference/sessions/list-session-files
https://api.notte.cc/openapi.json get /sessions/{session_id}/files
# Page Execute
Source: https://docs.notte.cc/api-reference/sessions/page-execute
https://api.notte.cc/openapi.json post /sessions/{session_id}/page/execute
# Page Observe
Source: https://docs.notte.cc/api-reference/sessions/page-observe
https://api.notte.cc/openapi.json post /sessions/{session_id}/page/observe
# Page Scrape
Source: https://docs.notte.cc/api-reference/sessions/page-scrape
https://api.notte.cc/openapi.json post /sessions/{session_id}/page/scrape
# Page Screenshot
Source: https://docs.notte.cc/api-reference/sessions/page-screenshot
https://api.notte.cc/openapi.json post /sessions/{session_id}/page/screenshot
# Session Auth Readiness
Source: https://docs.notte.cc/api-reference/sessions/session-auth-readiness
https://api.notte.cc/openapi.json get /sessions/{session_id}/auth
# Session Cookies Get
Source: https://docs.notte.cc/api-reference/sessions/session-cookies-get
https://api.notte.cc/openapi.json get /sessions/{session_id}/cookies
# Session Cookies Set
Source: https://docs.notte.cc/api-reference/sessions/session-cookies-set
https://api.notte.cc/openapi.json post /sessions/{session_id}/cookies
# Session Mouse Replay
Source: https://docs.notte.cc/api-reference/sessions/session-mouse-replay
https://api.notte.cc/openapi.json get /sessions/{session_id}/replay/mouse
# Session Network Logs
Source: https://docs.notte.cc/api-reference/sessions/session-network-logs
https://api.notte.cc/openapi.json get /sessions/{session_id}/network/logs
# Session Profile Save
Source: https://docs.notte.cc/api-reference/sessions/session-profile-save
https://api.notte.cc/openapi.json post /sessions/{session_id}/profile/save
# Session Replay
Source: https://docs.notte.cc/api-reference/sessions/session-replay
https://api.notte.cc/openapi.json get /sessions/{session_id}/replay
# Session Start
Source: https://docs.notte.cc/api-reference/sessions/session-start
https://api.notte.cc/openapi.json post /sessions/start
# Session Stop
Source: https://docs.notte.cc/api-reference/sessions/session-stop
https://api.notte.cc/openapi.json delete /sessions/{session_id}/stop
# Upload Session File
Source: https://docs.notte.cc/api-reference/sessions/upload-session-file
https://api.notte.cc/openapi.json post /sessions/{session_id}/files
# Vault Create
Source: https://docs.notte.cc/api-reference/vaults/vault-create
https://api.notte.cc/openapi.json post /vaults/create
# Vault Credentials Get
Source: https://docs.notte.cc/api-reference/vaults/vault-credentials-get
https://api.notte.cc/openapi.json get /vaults/{vault_id}/credentials
# Auth
Source: https://docs.notte.cc/cli-reference/auth
Manage authentication
Read [the Notte skill](https://notte.cc/skill.md) and follow its setup instructions when setting up Notte or building browser automation. This is the shared source for CLI setup, authentication, and the browser-to-code workflow.
Login, logout, and check authentication status.
## `notte auth login`
Open browser to authenticate with Notte.
You can authenticate in two ways:
```bash theme={null}
1. Sign in with Notte Console (recommended) - automatically fetches your API key
2. Enter API key manually - paste your key from console.notte.cc/apikeys
```
The API key is stored in your system credential store: the macOS Keychain, the Windows Credential Manager, or a Secret Service, KWallet or pass on Linux. Without one (headless hosts, containers), it goes to an encrypted file in the config directory whose password is fixed, so it is protected no better than a file only your user can read.
## `notte auth logout`
Remove stored credentials
## `notte auth status`
Show current authentication status
# Files
Source: https://docs.notte.cc/cli-reference/files
Manage stored files
Read [the Notte skill](https://notte.cc/skill.md) and follow its setup instructions when setting up Notte or building browser automation. This is the shared source for CLI setup, authentication, and the browser-to-code workflow.
Upload, list, and download files owned by a browser session.
## `notte files download `
Download a user upload or browser download by its immutable file ID.
| Flag | Description |
| - | - |
| `--path ` | Output file path (defaults to current directory) |
| `--session-id ` | Session ID (uses current session if not specified) |
## `notte files list`
List all files owned by a browser session. Use `--from` uploads or `--from` session to filter the source.
| Flag | Description |
| - | - |
| `--from ` | File source: uploads or session (default all) |
| `--session-id ` | Session ID (uses current session if not specified) |
## `notte files upload `
Upload a file to a browser session.
| Flag | Description |
| - | - |
| `--session-id ` | Session ID (uses current session if not specified) |
# Functions
Source: https://docs.notte.cc/cli-reference/functions
Manage functions
Read [the Notte skill](https://notte.cc/skill.md) and follow its setup instructions when setting up Notte or building browser automation. This is the shared source for CLI setup, authentication, and the browser-to-code workflow.
List, create, and operate on functions.
## `notte functions configure`
Update a function's metadata.
Pass any of `--name`, `--description`, `--domain`, `--run-instructions`, `--default-runtime`, `--response-format`, or `--self-healing`. Only the flags you pass are sent; omitted fields are left unchanged.
`--run-instructions` is documentation for whoever calls the function - how long a run takes, what each variable is for, which sites it trips over. It is not input to the self-healing agent.
`--default-runtime` is the runtime every run of this function uses unless that run passes `functions run --runtime`.
`--response-format` is the JSON Schema of what run() returns (inline JSON, @file, or - for stdin).
| Flag | Description |
| - | - |
| `--default-runtime ` | Runtime this function's runs use unless a run overrides it: standard or extended |
| `--description ` | description |
| `--domain ` | domain |
| `--function-id ` | Function ID (uses current function if not specified) |
| `--name ` | name |
| `--response-format ` | JSON Schema of run()'s return value, computed by the caller from its pydantic model, as inline JSON, @file, or - for stdin |
| `--run-instructions ` | Notes for whoever calls this function: how long a run takes, what the variables mean, what it trips over |
| `--self-healing` | Let an agent repair the function when a run fails |
## `notte functions create`
Create a new function
| Flag | Description |
| - | - |
| `--description ` | Function description |
| `--file ` | Path to function file (required) |
| `--name ` | Function name |
| `--response-format ` | JSON Schema of run()'s return value, computed by the caller from its pydantic model, as inline JSON, @file, or - for stdin |
| `--shared` | Make function public (API default: false) |
## `notte functions delete`
Delete function
| Flag | Description |
| - | - |
| `--function-id ` | Function ID (uses current function if not specified) |
## `notte functions download `
Download function code to disk
| Flag | Description |
| - | - |
| `--function-id ` | Function ID (uses current function if not specified) |
| `--version ` | Function version (defaults to latest) |
## `notte functions fork`
Fork/duplicate the function
| Flag | Description |
| - | - |
| `--function-id ` | Function ID (uses current function if not specified) |
## `notte functions health`
Show the function runtime's health and available packages
## `notte functions list`
List functions
| Flag | Description |
| - | - |
| `--include-deleted` | Include deleted functions |
| `--page ` | Page number (1-indexed) |
| `--page-size ` | Number of items per page |
## `notte functions rollback`
Restore an earlier version of the function
| Flag | Description |
| - | - |
| `--function-id ` | Function ID (uses current function if not specified) |
| `--version ` | A version from the function's versions history to restore. Required. |
## `notte functions run`
Start the function, print its run ID, and poll until it finishes. Use `--no-wait` to return after startup, then run-metadata `--wait` to wait separately. `--timeout` limits each API request, not the total execution time. Interrupting the CLI does not stop the function; use run-stop to cancel it.
| Flag | Description |
| - | - |
| `--function-id ` | Function ID (uses current function if not specified) |
| `--no-stream` | Suppress log output while waiting for the final run metadata |
| `--no-wait` | Return the run ID after startup without waiting for completion |
| `--runtime ` | Run on standard or extended for this run only, overriding the function's default-runtime |
| `--var ` | Variable as key=value pair (can be used multiple times) |
| `--vars ` | Variables as JSON object string |
| `--wait-timeout ` | Maximum time to wait for completion (e.g. 30m; 0 waits indefinitely) |
## `notte functions run-metadata`
Get function run metadata
| Flag | Description |
| - | - |
| `--function-id ` | Function ID (uses current function if not specified) |
| `--no-stream` | Suppress log output while waiting for the final run metadata |
| `--run-id ` | Run ID (required) |
| `--wait` | Poll until the run finishes; `--timeout` applies to each request |
| `--wait-timeout ` | Maximum time to wait for completion (e.g. 30m; 0 waits indefinitely) |
## `notte functions run-stop`
Stop a function run
| Flag | Description |
| - | - |
| `--function-id ` | Function ID (uses current function if not specified) |
| `--run-id ` | Run ID (required) |
## `notte functions runs`
List function runs
| Flag | Description |
| - | - |
| `--function-id ` | Function ID (uses current function if not specified) |
| `--page ` | Page number (1-indexed) |
| `--page-size ` | Number of items per page |
| `--running` | Only show runs that are still executing |
## `notte functions schedule`
Set a cron schedule for the function
| Flag | Description |
| - | - |
| `--cron ` | AWS EventBridge schedule expression. Six space-separated fields, minutes first: minutes hours day-of-month month day-of-week year. This is neither five-field Unix crontab nor the seconds-first six-field dialect used by Quartz and cron-parser. Exactly one of day-of-month and day-of-week must be `?`, the other must be a real value. Schedules fire in UTC. Example: `0 12 ? * * *` runs daily at noon UTC. `rate(...)` expressions such as `rate(1 hour)` are accepted too. Required. |
| `--function-id ` | Function ID (uses current function if not specified) |
## `notte functions secrets delete `
Delete a function environment secret
Aliases: `rm`
## `notte functions secrets get `
Get a function environment secret value
## `notte functions secrets list`
List function environment secrets
## `notte functions secrets set [value]`
Set a function environment secret
| Flag | Description |
| - | - |
| `--value ` | Secret value |
## `notte functions show`
Show function details
| Flag | Description |
| - | - |
| `--function-id ` | Function ID (uses current function if not specified) |
## `notte functions unschedule`
Remove the schedule from the function
| Flag | Description |
| - | - |
| `--function-id ` | Function ID (uses current function if not specified) |
## `notte functions update`
Update function
| Flag | Description |
| - | - |
| `--file ` | Path to updated function file (required) |
| `--function-id ` | Function ID (uses current function if not specified) |
| `--response-format ` | JSON Schema of run()'s return value, computed by the caller from its pydantic model, as inline JSON, @file, or - for stdin |
# CLI
Source: https://docs.notte.cc/cli-reference/index
Drive Notte browser sessions, functions, and vaults from your terminal
Read [the Notte skill](https://notte.cc/skill.md) and follow its setup instructions when setting up Notte or building browser automation. This is the shared source for CLI setup, authentication, and the browser-to-code workflow.
The [Notte CLI](https://github.com/nottelabs/notte-cli) gives you every Notte resource from the command line. Pair it with shell scripts, CI pipelines, or coding agents.
## Installation
Install with one of:
```bash Install script theme={null}
# macOS and Linux
curl -fsSL https://notte.cc/install-cli.sh | sh
```
```bash Homebrew theme={null}
brew tap nottelabs/notte-cli https://github.com/nottelabs/notte-cli.git
brew install notte
```
```bash Go theme={null}
go install github.com/nottelabs/notte-cli/cmd/notte@latest
```
Verify the installation:
```bash theme={null}
notte version
```
Upgrade to the latest release with `notte upgrade`. It uses Homebrew when notte was installed with it, and otherwise re-runs the install script.
## Authentication
The CLI reads your API key from, in order: the `NOTTE_API_KEY` environment variable, the system keyring (set by `notte auth login`), then `~/.notte/cli/config.json`. See [Authentication](/api-reference/authentication) to get a key.
```bash theme={null}
export NOTTE_API_KEY=your_api_key_here
# or, for local development
notte auth login
```
## Commands
Manage authentication
Manage stored files
Manage functions
Execute page actions (syntactic sugar for sessions execute)
Request a temporary card for a browser session
Manage personas
Manage browser profiles
Manage browser sessions
Manage Notte skills for AI coding assistants
Route sessions through this machine's internet connection
Show API usage statistics
Manage vaults
`clear`, `health`, `search`, `upgrade`, `version`
## Global flags
These flags work with every command.
| Flag | Description |
| - | - |
| `--no-color` | Disable color output |
| `--output `, `-o` | Output format (text, json) (default: `text`) |
| `--timeout ` | API request timeout in seconds (default: `60`) |
| `--verbose`, `-v` | Verbose output |
| `--yes`, `-y` | Skip confirmation prompts |
## JSON output
Commands that return data accept `--output json` (or `-o json`) for machine-readable output. Commands that wrap another tool, such as `skill add`, print that tool's output as is:
```bash theme={null}
notte sessions start -o json
notte functions list -o json
```
# Other commands
Source: https://docs.notte.cc/cli-reference/other
Standalone Notte CLI commands
Read [the Notte skill](https://notte.cc/skill.md) and follow its setup instructions when setting up Notte or building browser automation. This is the shared source for CLI setup, authentication, and the browser-to-code workflow.
## `notte clear`
Clear all locally stored state including current session, viewer URL, and function. This does not affect credentials or settings.
## `notte health`
Check if the Notte API is healthy and responding.
## `notte search `
Search the web via the Notte search API.
Returns either a list of search results, or an LLM-generated answer with sources, depending on `--output-type`.
Examples:
```bash theme={null}
notte search "what is anthropic"
notte search what is anthropic
notte search "latest llm releases" --depth deep
notte search "what is anthropic" --output-type sourcedAnswer
```
| Flag | Description |
| - | - |
| `--depth ` | Search depth: standard, fast, or deep |
| `--output-type ` | Output type: searchResults, sourcedAnswer, or structured |
## `notte upgrade`
Upgrade the CLI to the latest release.
Uses Homebrew when the CLI was installed with Homebrew. Otherwise it re-runs the install script ([https://notte.cc/install-cli.sh](https://notte.cc/install-cli.sh)), replacing the binary that is running. On Windows it prints the manual steps instead.
## `notte version`
Print version information
# Page
Source: https://docs.notte.cc/cli-reference/page
Execute page actions (syntactic sugar for sessions execute)
Read [the Notte skill](https://notte.cc/skill.md) and follow its setup instructions when setting up Notte or building browser automation. This is the shared source for CLI setup, authentication, and the browser-to-code workflow.
Execute page actions with a simplified command interface.
Use:
```bash theme={null}
notte page click "#btn"
notte page click B3 # element ID (auto-detected)
notte page click @B3 # @-prefix also works (legacy)
notte page fill I1 "hello"
notte page goto "https://example.com"
```
## `notte page back`
Go back in browser history
| Flag | Description |
| - | - |
| `--session-id ` | Session ID (uses current session if not specified) |
## `notte page captcha-solve `
Solve a CAPTCHA (e.g., recaptcha\_v2, hcaptcha)
| Flag | Description |
| - | - |
| `--session-id ` | Session ID (uses current session if not specified) |
## `notte page check `
Check or uncheck a checkbox
| Flag | Description |
| - | - |
| `--session-id ` | Session ID (uses current session if not specified) |
| `--value` | Check (true) or uncheck (false) (default: `true`) |
## `notte page click `
Click an element
| Flag | Description |
| - | - |
| `--enter` | Press Enter after clicking |
| `--session-id ` | Session ID (uses current session if not specified) |
| `--timeout ` | Timeout in milliseconds |
## `notte page close-tab`
Close the current tab
| Flag | Description |
| - | - |
| `--session-id ` | Session ID (uses current session if not specified) |
## `notte page complete `
Mark task as complete with an answer
| Flag | Description |
| - | - |
| `--session-id ` | Session ID (uses current session if not specified) |
| `--success` | Whether the completion was successful (default: `true`) |
## `notte page download `
Download a file by clicking an element
| Flag | Description |
| - | - |
| `--session-id ` | Session ID (uses current session if not specified) |
## `notte page eval-js `
Evaluate JavaScript code on the current page and return the result.
The JavaScript code is executed in the context of the page's main frame.
The evaluated value is printed alone on stdout (objects and arrays as JSON, a JS null as "null"), so it pipes straight into jq or a shell variable; the status line goes to stderr. Use -o json for the full execution result.
Examples:
```bash theme={null}
notte page eval-js "document.title"
notte page eval-js "window.location.href"
notte page eval-js "document.querySelectorAll('a').length"
notte page eval-js "JSON.stringify([...document.links].map(a => a.href))" | jq length
```
| Flag | Description |
| - | - |
| `--session-id ` | Session ID (uses current session if not specified) |
## `notte page fill [value]`
Fill an input field with a literal value or a credential from the vault attached to the current session.
Use `--vault-field` instead of a value to keep credentials out of the command:
```bash theme={null}
notte page fill "input[name=email]" --vault-field email
notte page fill "input[name=password]" --vault-field password
notte page fill "input[autocomplete=cc-number]" --vault-field card_number
```
| Flag | Description |
| - | - |
| `--clear` | Clear the field before filling |
| `--enter` | Press Enter after filling |
| `--session-id ` | Session ID (uses current session if not specified) |
| `--vault-field ` | Fill from the session vault (email, username, password, mfa, card\_number, card\_holder\_name, card\_expiration, card\_cvv) |
## `notte page form-fill --data `
Fill a form with JSON data
| Flag | Description |
| - | - |
| `--data ` | JSON object with form field values (required) |
| `--session-id ` | Session ID (uses current session if not specified) |
## `notte page forward`
Go forward in browser history
| Flag | Description |
| - | - |
| `--session-id ` | Session ID (uses current session if not specified) |
## `notte page goto `
Navigate to a URL
| Flag | Description |
| - | - |
| `--session-id ` | Session ID (uses current session if not specified) |
## `notte page new-tab `
Open a URL in a new tab
| Flag | Description |
| - | - |
| `--session-id ` | Session ID (uses current session if not specified) |
## `notte page observe`
Observe the current page state
| Flag | Description |
| - | - |
| `--session-id ` | Session ID (uses current session if not specified) |
## `notte page press `
Press a key (e.g., Enter, Escape, Tab)
| Flag | Description |
| - | - |
| `--session-id ` | Session ID (uses current session if not specified) |
## `notte page reload`
Reload the current page
| Flag | Description |
| - | - |
| `--session-id ` | Session ID (uses current session if not specified) |
## `notte page scrape`
Scrape content from the page
| Flag | Description |
| - | - |
| `--instructions ` | Extraction instructions |
| `--only-main-content` | Only scrape main content |
| `--session-id ` | Session ID (uses current session if not specified) |
## `notte page screenshot [output]`
Take a screenshot of the current page and save it as a JPEG file.
By default, saves to a temporary directory. Optionally provide a path to save to a specific location.
Examples:
```bash theme={null}
notte page screenshot # saves to tmp directory
notte page screenshot screenshot.jpg # saves to specified path
notte page screenshot --path out.jpg # saves to specified path (alt syntax)
```
| Flag | Description |
| - | - |
| `--path ` | Output path for the screenshot (defaults to temp directory) |
| `--session-id ` | Session ID (uses current session if not specified) |
## `notte page scroll-down [amount]`
Scroll down the page
| Flag | Description |
| - | - |
| `--session-id ` | Session ID (uses current session if not specified) |
## `notte page scroll-up [amount]`
Scroll up the page
| Flag | Description |
| - | - |
| `--session-id ` | Session ID (uses current session if not specified) |
## `notte page select `
Select a dropdown option
| Flag | Description |
| - | - |
| `--session-id ` | Session ID (uses current session if not specified) |
## `notte page switch-tab `
Switch to a tab by index (0-based)
| Flag | Description |
| - | - |
| `--session-id ` | Session ID (uses current session if not specified) |
## `notte page upload --file `
Upload a file to an input element
| Flag | Description |
| - | - |
| `--file ` | Path to the file to upload (required) |
| `--session-id ` | Session ID (uses current session if not specified) |
## `notte page wait `
Wait for a specified duration
| Flag | Description |
| - | - |
| `--session-id ` | Session ID (uses current session if not specified) |
# Payment
Source: https://docs.notte.cc/cli-reference/payment
Request a temporary card for a browser session
Read [the Notte skill](https://notte.cc/skill.md) and follow its setup instructions when setting up Notte or building browser automation. This is the shared source for CLI setup, authentication, and the browser-to-code workflow.
## `notte payment connect`
Return a Link wallet connection URL and phrase. Open the URL to authorize Notte. Completion is detected in the background; this command does not wait. Run it again to retrieve the pending link or confirm connected. No browser session is required.
| Flag | Description |
| - | - |
| `--mode ` | Wallet mode: test or live (defaults to the API setting) |
## `notte payment disconnect`
Revoke and forget the selected wallet connection, then use payment connect to choose another account. The other mode is unaffected. Active payments and card cleanup must finish first.
| Flag | Description |
| - | - |
| `--mode ` | Wallet mode: test or live (defaults to the API setting) |
## `notte payment request`
Request spending from a connected wallet and receive approval instructions
| Flag | Description |
| - | - |
| `--amount ` | Amount in currency units (100.91 = USD 100.91). Required. |
| `--currency ` | Three-letter lowercase currency (default: `usd`) |
| `--description ` | Purchase description (100 to 4000 characters). Required. |
| `--idempotency-key ` | Reuse this key with the same request to recover a previous attempt |
| `--merchant-name ` | Merchant name. Required. |
| `--merchant-url ` | HTTPS merchant URL. Required. |
| `--mode ` | Payment mode: test or live (defaults to the API setting) |
| `--session-id ` | Existing browser session ID. Required. |
## `notte payment status PAYMENT_ID`
Retrieve payment status and current approval instructions
## `notte payment wait PAYMENT_ID`
Wait until the temporary card is installed in the session vault. Ready does not mean a purchase succeeded. Connection, approval, and verification instructions appear on stderr. Exiting does not cancel provisioning.
| Flag | Description |
| - | - |
| `--wait-timeout ` | Maximum time to wait (e.g. 10m); zero waits until completion |
# Personas
Source: https://docs.notte.cc/cli-reference/personas
Manage personas
Read [the Notte skill](https://notte.cc/skill.md) and follow its setup instructions when setting up Notte or building browser automation. This is the shared source for CLI setup, authentication, and the browser-to-code workflow.
List, create, and operate on personas.
## `notte personas create`
Create a new persona
| Flag | Description |
| - | - |
| `--create-phone-number` | Whether to create a phone number for the persona (API default: false) |
| `--create-vault` | Whether to create a vault for the persona (API default: false) |
| `--email-provider ` | email-provider (API default: notte) (notte, agentmail, resend) |
| `--name ` | name |
## `notte personas delete`
Delete the persona
| Flag | Description |
| - | - |
| `--persona-id ` | Persona ID (required) |
## `notte personas emails`
List emails for the persona
| Flag | Description |
| - | - |
| `--persona-id ` | Persona ID (required) |
## `notte personas list`
List all personas
| Flag | Description |
| - | - |
| `--include-deleted` | Include deleted personas |
| `--page ` | Page number (1-indexed) |
| `--page-size ` | Number of items per page |
## `notte personas show`
Show persona details
| Flag | Description |
| - | - |
| `--persona-id ` | Persona ID (required) |
## `notte personas sms`
List SMS messages for the persona
| Flag | Description |
| - | - |
| `--persona-id ` | Persona ID (required) |
## `notte personas update`
Rename a persona
| Flag | Description |
| - | - |
| `--name ` | New name for the persona. Required. |
| `--persona-id ` | Persona ID (required) |
# Profiles
Source: https://docs.notte.cc/cli-reference/profiles
Manage browser profiles
Read [the Notte skill](https://notte.cc/skill.md) and follow its setup instructions when setting up Notte or building browser automation. This is the shared source for CLI setup, authentication, and the browser-to-code workflow.
List, create, and operate on browser profiles.
## `notte profiles cookies`
Get the cookies stored in a profile
| Flag | Description |
| - | - |
| `--profile-id ` | Profile ID (required) |
## `notte profiles cookies-set`
Import cookies into a profile from a JSON file.
The file may be a bare array of cookies, which is what Playwright and the Chrome extensions export, or an object with a `cookies` key.
| Flag | Description |
| - | - |
| `--file ` | Path to a cookies JSON file (required) |
| `--mode ` | replace the profile's cookies, or append to them |
| `--profile-id ` | Profile ID (required) |
| `--source-format ` | Format the cookies were exported in (playwright, chrome) |
## `notte profiles create`
Create a new profile
| Flag | Description |
| - | - |
| `--name ` | Name of the profile |
## `notte profiles delete`
Delete profile
| Flag | Description |
| - | - |
| `--profile-id ` | Profile ID (required) |
## `notte profiles list`
List profiles
| Flag | Description |
| - | - |
| `--include-deleted` | Include deleted profiles |
| `--name ` | Filter profiles by name |
| `--page ` | Page number (1-indexed) |
| `--page-size ` | Number of items per page |
## `notte profiles show`
Show profile details
| Flag | Description |
| - | - |
| `--profile-id ` | Profile ID (required) |
## `notte profiles sync`
Read the cookies from a local Firefox, Chrome, Brave, Edge, Chromium, Arc or Helium profile and upload them into a Notte cloud profile, so remote sessions and agents start already logged in. Arc and Helium are supported on macOS only.
Cookies are read from a copy of the browser's own files on this machine and decrypted locally. Nothing leaves your computer until you confirm the upload, and with `--domain` you choose exactly which sites are included.
With `--profile-id` the cookies go into an existing profile; without it a new profile is created and its id is printed for you to reuse next time.
| Flag | Description |
| - | - |
| `--browser ` | Browser to read from (firefox, chrome, brave, edge, chromium, arc, helium) |
| `--domain ` | Only sync cookies for these domains and their subdomains (repeatable) |
| `--local-profile ` | Local browser profile, by name or directory (prompts if there is more than one) |
| `--mode ` | How to apply the cookies: replace the profile's cookies, or append to them (default: `replace`) |
| `--name ` | Name for the new profile when `--profile-id` is omitted |
| `--profile-id ` | Existing Notte profile to sync into (creates a new one if omitted) |
# Sessions
Source: https://docs.notte.cc/cli-reference/sessions
Manage browser sessions
Read [the Notte skill](https://notte.cc/skill.md) and follow its setup instructions when setting up Notte or building browser automation. This is the shared source for CLI setup, authentication, and the browser-to-code workflow.
Hosted sessions use Notte-managed Chromium. Browser-type flags and their legacy aliases are accepted for compatibility; they do not select a different hosted browser build. See [Session configuration](/sdk-reference/manual/session) for supported settings.
List, create, and operate on browser sessions.
## `notte sessions code`
Get Python script for session steps
| Flag | Description |
| - | - |
| `--session-id ` | Session ID (uses current session if not specified) |
## `notte sessions cookies`
Get all cookies for the session
| Flag | Description |
| - | - |
| `--session-id ` | Session ID (uses current session if not specified) |
## `notte sessions cookies-set`
Set cookies from a JSON file
| Flag | Description |
| - | - |
| `--file ` | JSON file containing cookies array (required) |
| `--session-id ` | Session ID (uses current session if not specified) |
## `notte sessions list`
List active sessions
| Flag | Description |
| - | - |
| `--all`, `-a` | Include stopped sessions |
| `--page ` | Page number (1-indexed) |
| `--page-size ` | Number of items per page |
## `notte sessions network`
Get network logs for the session
| Flag | Description |
| - | - |
| `--path ` | Output directory for downloaded files (defaults to temp directory) |
| `--session-id ` | Session ID (uses current session if not specified) |
| `--urls-only` | Only show download URLs without downloading |
## `notte sessions offset`
Get session offset info
| Flag | Description |
| - | - |
| `--session-id ` | Session ID (uses current session if not specified) |
## `notte sessions replay`
Download the replay video (MP4) for a session.
Examples:
```bash theme={null}
notte sessions replay # saves to temp directory
notte sessions replay --path replay.mp4 # saves to specified path
```
| Flag | Description |
| - | - |
| `--path ` | Output path for the replay video (defaults to temp directory) |
| `--session-id ` | Session ID (uses current session if not specified) |
## `notte sessions save-profile`
Save the running session's browser state (cookies, localStorage, sessionStorage) into a browser profile.
The session keeps running. Pass `--profile-id` to overwrite an existing profile, or `--name` to create a new one. With neither, the state is saved into the profile the session was started with.
| Flag | Description |
| - | - |
| `--name ` | Create a new profile with this name and save into it |
| `--profile-id ` | Existing profile to overwrite |
| `--session-id ` | Session ID (uses current session if not specified) |
## `notte sessions start`
Start a new browser session
| Flag | Description |
| - | - |
| `--advanced-stealth` | Enable Notte's highest-fidelity browser environment for sites with sophisticated bot detection. Available to approved workspaces. (API default: false) |
| `--aspect-ratio ` | Viewport shape preset. When set, the backend fits the largest rectangle of this aspect ratio inside the sampled available screen area. Cannot be combined with explicit viewport\_width/viewport\_height. |
| `--auth-ids ` | Managed Auth connection IDs to verify and, when necessary, authenticate inside this session. Initial verification runs before returning; when login is needed, the session returns as authenticating. Poll /sessions/\{id}/auth for readiness. (repeatable) |
| `--auth-retry ` | auth-retry (API default: 0) |
| `--block-ads` | Block ads and trackers in managed browsers using Basic filtering. Set false to disable. Does not configure externally supplied CDP browsers. (API default: true) |
| `--browser-type ` | The browser type to use. Supported values are chromium and chrome. chrome-nightly and chrome-turbo are legacy aliases for chrome. (API default: chromium) (chromium, chrome, chrome-nightly, chrome-turbo) |
| `--cdp-url ` | The CDP URL of another remote session provider. |
| `--chrome-args ` | Overwrite the chrome instance arguments (repeatable) |
| `--demonstrate` | Whether to enable the Notte recorder extension for this session. The extension is installed but remains inactive when this is false. (API default: false) |
| `--extra-http-headers ` | Extra HTTP headers as JSON (e.g. '\{"Authorization": "Bearer xxx"}') |
| `--idle-timeout-minutes ` | Idle timeout in minutes. Session closes after this period of inactivity (resets on each operation). (API default: 3) |
| `--max-duration-minutes ` | Maximum session lifetime in minutes (absolute maximum, not affected by activity). (API default: 15) |
| `--no-solve-captchas` | Do not attempt to solve captchas automatically |
| `--profile-id ` | Profile ID to use for this session |
| `--profile-persist` | Whether to save browser state to profile on session close (API default: false) |
| `--proxy` | Use default proxies |
| `--proxy-country ` | Proxy country code (e.g. us, gb, fr). Selects a proxy on its own; do not combine with `--proxy` |
| `--proxy-external-password ` | External proxy password |
| `--proxy-external-server ` | External proxy server URL (e.g. [http://proxy:8080](http://proxy:8080)). Enables external proxy |
| `--proxy-external-username ` | External proxy username |
| `--proxy-tailnet-client-id ` | Tailnet OAuth client ID. Enables Tailscale proxy |
| `--proxy-tailnet-client-secret ` | Tailnet OAuth client secret |
| `--screenshot-type ` | The type of screenshot to use for the session. (API default: last\_action) (raw, full, last\_action) |
| `--solve-captchas` | Whether to try to automatically solve captchas (API default: true) |
| `--tunnel ` | Route public traffic through a saved tunnel (see notte tunnel up) |
| `--user-agent ` | The user agent to use for the session |
| `--vault-id ` | The vault to use for the session |
| `--viewport-height ` | Viewport height in pixels (minimum 500). Set together with viewport\_width. |
| `--viewport-width ` | Viewport width in pixels (minimum 500). Set together with viewport\_height. |
| `--wait-for-authentication` | SDK waiting preference. The API always verifies inline and returns authenticating when background login is needed; SDKs implement waiting through readiness polling. (API default: true) |
| `--web-bot-auth` | Whether to use web bot authentication. (API default: false) |
## `notte sessions status`
Get session status
| Flag | Description |
| - | - |
| `--auth` | Show managed authentication readiness and operations |
| `--session-id ` | Session ID (uses current session if not specified) |
## `notte sessions stop`
Stop the session
| Flag | Description |
| - | - |
| `--session-id ` | Session ID (uses current session if not specified) |
## `notte sessions viewer`
Open session viewer in browser
| Flag | Description |
| - | - |
| `--session-id ` | Session ID (uses current session if not specified) |
## `notte sessions workflow-code`
Export session steps as code
| Flag | Description |
| - | - |
| `--session-id ` | Session ID (uses current session if not specified) |
# Skill
Source: https://docs.notte.cc/cli-reference/skill
Manage Notte skills for AI coding assistants
Read [the Notte skill](https://notte.cc/skill.md) and follow its setup instructions when setting up Notte or building browser automation. This is the shared source for CLI setup, authentication, and the browser-to-code workflow.
Install and manage Notte skills for AI coding assistants.
Skills provide AI assistants with browser automation capabilities through natural language commands.
## `notte skill add`
Install the Notte browser automation skill using npx.
This command runs: npx skills add nottelabs/notte-skills
With `--upgrade` (or -f), it runs: npx skills update notte-browser to refresh an already-installed skill to the latest version.
npx skills prompts interactively to pick which AI assistants to install to. Pass `--yes` (-y) to skip that prompt and install to all detected assistants — required when running non-interactively (CI, scripts).
The skill enables AI coding assistants (like Cursor, Claude Code, etc.) to control browser sessions through natural language commands.
| Flag | Description |
| - | - |
| `--upgrade`, `-f` | Force a reinstall by updating an already-installed skill to the latest version |
## `notte skill remove`
Remove the Notte browser automation skill using npx.
This command runs: npx skills remove `--skill` notte-browser
Aliases: `rm`
# Tunnel
Source: https://docs.notte.cc/cli-reference/tunnel
Route sessions through this machine's internet connection
Read [the Notte skill](https://notte.cc/skill.md) and follow its setup instructions when setting up Notte or building browser automation. This is the shared source for CLI setup, authentication, and the browser-to-code workflow.
Route session traffic through this machine's internet connection using Tailscale. "notte tunnel up" offers this machine as a Tailscale exit node and saves it as a named tunnel; "notte tunnel check" verifies it end to end; and "notte sessions start `--tunnel` \" then sends the session's public traffic out through it.
Requires Tailscale running and signed in on this machine, and a Tailscale OAuth client (auth\_keys write scope, tagged tag:notte only). Connect the client once in the Notte console (Settings > Integrations), or pass it to "tunnel up" to keep it on this machine. Your tailnet policy must let tag:notte reach autogroup:internet, and this machine must be approved as an exit node.
## `notte tunnel check`
Verify that sessions leave through a saved tunnel.
The check starts a short session through the tunnel, opens an IP lookup inside it, and compares the session's public IP with this machine's. The session is billed like any other (usually well under a minute) and is closed when the check ends.
If the Notte workspace has no Tailscale connection, connect one in the console (Settings > Integrations) first.
| Flag | Description |
| - | - |
| `--name ` | Tunnel to check (default: this machine's MagicDNS host name) |
```bash theme={null}
notte tunnel check
notte tunnel check --name home-mac
```
## `notte tunnel down`
Stop offering this machine as a Tailscale exit node. Saved tunnels are kept, so "notte tunnel up" can bring it back without credentials.
## `notte tunnel list`
List saved tunnels
## `notte tunnel remove `
Delete a saved tunnel and its stored credentials
## `notte tunnel up`
Offer this machine as a Tailscale exit node and save it as a named tunnel.
Without credentials, the tunnel uses the Tailscale OAuth client connected to your Notte workspace in the console (Settings > Integrations), and nothing secret is stored on this machine.
To keep the client on this machine instead, pass `--oauth-client-id` and `--oauth-client-secret` (or NOTTE\_TAILNET\_OAUTH\_CLIENT\_SECRET). The secret is stored in the keyring, never in the config file. Re-running "up" for a saved tunnel reuses its stored credentials.
| Flag | Description |
| - | - |
| `--name ` | Tunnel name (default: this machine's MagicDNS host name) |
| `--oauth-client-id ` | Tailscale OAuth client ID (default: the workspace's console connection) |
| `--oauth-client-secret ` | Tailscale OAuth client secret (or set NOTTE\_TAILNET\_OAUTH\_CLIENT\_SECRET) |
```bash theme={null}
notte tunnel up --name home-mac
notte tunnel up --name home-mac --oauth-client-id --oauth-client-secret
notte sessions start --tunnel home-mac
```
# Usage
Source: https://docs.notte.cc/cli-reference/usage
Show API usage statistics
Read [the Notte skill](https://notte.cc/skill.md) and follow its setup instructions when setting up Notte or building browser automation. This is the shared source for CLI setup, authentication, and the browser-to-code workflow.
Display usage statistics including credits, costs, and quotas.
## `notte usage`
Display usage statistics including credits, costs, and quotas.
| Flag | Description |
| - | - |
| `--period ` | Monthly period to get usage for (e.g., 'May 2025') |
## `notte usage logs`
List the API requests made with this workspace's credentials, newest first.
| Flag | Description |
| - | - |
| `--endpoint ` | Only show requests to this endpoint, e.g. /sessions/start |
| `--include-system` | Include Notte's own internal requests |
| `--only-current-token` | Only show requests made with the API key in use now |
| `--page ` | Page number (1-indexed) |
| `--page-size ` | Number of items per page |
# Vaults
Source: https://docs.notte.cc/cli-reference/vaults
Manage vaults
Read [the Notte skill](https://notte.cc/skill.md) and follow its setup instructions when setting up Notte or building browser automation. This is the shared source for CLI setup, authentication, and the browser-to-code workflow.
List, create, and operate on vaults for storing credentials.
## `notte vaults create`
Create a new vault
| Flag | Description |
| - | - |
| `--name ` | Name of the vault (API default: default) |
## `notte vaults credentials add`
Add credentials (password and optional MFA secret) to a vault.
Security note: values passed via `--password` and `--mfa-secret` are read from argv, which makes them visible in 'ps' output and process snapshots while the command runs. Expanding them from an environment variable keeps the literal value out of your shell history, but the shell still passes it to notte as an argument:
```bash theme={null}
notte vaults credentials add --vault-id --url \
--email me@example.com --password "$MY_PASSWORD"
```
Example values shown in the docs (e.g. EXAMPLEMFASECRET) are placeholders, not real secrets.
| Flag | Description |
| - | - |
| `--email ` | email |
| `--mfa-secret ` | mfa-secret |
| `--password ` | password. Required. |
| `--url ` | url. Required. |
| `--username ` | username |
| `--vault-id ` | Vault ID (required) |
## `notte vaults credentials delete`
Delete credentials for a specific URL
| Flag | Description |
| - | - |
| `--url ` | URL to delete credentials for (required) |
| `--vault-id ` | Vault ID (required) |
## `notte vaults credentials get`
Get credentials for a specific URL
| Flag | Description |
| - | - |
| `--url ` | URL to get credentials for (required) |
| `--vault-id ` | Vault ID (required) |
## `notte vaults credentials list`
List all credentials in the vault
| Flag | Description |
| - | - |
| `--vault-id ` | Vault ID (required) |
## `notte vaults delete`
Delete the vault
| Flag | Description |
| - | - |
| `--vault-id ` | Vault ID (required) |
## `notte vaults list`
List all vaults
| Flag | Description |
| - | - |
| `--include-deleted` | Include deleted vaults |
| `--page ` | Page number (1-indexed) |
| `--page-size ` | Number of items per page |
## `notte vaults update`
Update vault details
| Flag | Description |
| - | - |
| `--name ` | New name for the vault. Required. |
| `--vault-id ` | Vault ID (required) |
# GitHub
Source: https://docs.notte.cc/community/github
# LinkedIn
Source: https://docs.notte.cc/community/linkedin
# Product Hunt
Source: https://docs.notte.cc/community/producthunt
# Slack
Source: https://docs.notte.cc/community/slack
# Trustpilot
Source: https://docs.notte.cc/community/trustpilot
# X (Twitter)
Source: https://docs.notte.cc/community/x
# Y Combinator
Source: https://docs.notte.cc/community/yc
# YouTube
Source: https://docs.notte.cc/community/youtube
# Anything API
Source: https://docs.notte.cc/concepts/anything
Build and run web automations from a single API call
Read [the Notte skill](https://notte.cc/skill.md) and follow its setup instructions when setting up Notte or building browser automation. This is the shared source for CLI setup, authentication, and the browser-to-code workflow.
The Anything API lets you describe a task in plain English, and Notte will build, deploy, and run a web automation function for you -- all through a single HTTP request. The response streams the agent's progress as an [AI SDK UI message stream](https://ai-sdk.dev/docs/ai-sdk-ui/stream-protocol), or you can detach and let the run continue in the background.
Open the Anything API web app.
Create an account on the Console to get started.
## Quick start
```bash theme={null}
curl -N -X POST https://anything.notte.cc/api/anything/start \
-H "Content-Type: application/json" \
-H "Authorization: Bearer $NOTTE_API_KEY" \
-d '{"query": "fetch the top 3 hacker news posts"}'
```
The API streams the agent's work back as an AI SDK UI message stream. The `x-thread-id` response header identifies the thread handling your run, so you can send follow-up turns into the same thread later.
## Request
A natural-language description of the task you want automated.
Send a follow-up turn into an existing thread. The sandbox state and conversation carry over. Omit to start a new thread.
Start the run and return immediately instead of streaming. See [Detached mode](#detached-mode).
Optional model override for the agent.
Optional reasoning effort override for the agent.
API contract version. `"v2"` is the only supported value and the default, so you can omit this field. Explicit `"v1"` returns `400` -- see [Migrating from v1](#migrating-from-v1).
```json POST /api/anything/start theme={null}
{
"query": "fetch the top 3 hacker news posts"
}
```
**Headers**
| Header | Required | Description |
| - | - | - |
| `Authorization` | Yes | `Bearer ` |
| `Content-Type` | Yes | `application/json` |
## Response
### Streamed (default)
By default, the response is an [AI SDK UI message stream](https://ai-sdk.dev/docs/ai-sdk-ui/stream-protocol) -- the same format the Vercel AI SDK's `useChat` hook consumes, so you can back a chat UI with it or read the stream yourself. Call it from your server, not from the browser: see [Calling from a browser](#calling-from-a-browser).
**Response headers**
| Header | Description |
| - | - |
| `x-thread-id` | ID of the thread handling this run. Use it as `thread_id` in follow-up requests. |
| `x-vercel-ai-ui-message-stream` | `v1` -- the AI SDK's stream-format version. Unrelated to the retired v1 backend. |
### Detached mode
Pass `detach: true` to start the run and return immediately with `202 Accepted`:
```bash cURL theme={null}
curl -X POST https://anything.notte.cc/api/anything/start \
-H "Content-Type: application/json" \
-H "Authorization: Bearer $NOTTE_API_KEY" \
-d '{"query": "fetch the top 3 hacker news posts", "detach": true}'
```
```python start_detached.py theme={null}
import os
import requests
NOTTE_API_KEY = os.environ["NOTTE_API_KEY"]
response = requests.post(
"https://anything.notte.cc/api/anything/start",
headers={
"Authorization": f"Bearer {NOTTE_API_KEY}",
"Content-Type": "application/json",
},
json={"query": "fetch the top 3 hacker news posts", "detach": True},
timeout=30,
)
response.raise_for_status() # 202 Accepted
run = response.json()
print("Thread ID:", run["thread_id"])
print("Status:", run["status"]) # "started"
print("Follow along at:", run["url"])
```
The response body contains:
| Field | Description |
| - | - |
| `thread_id` | ID of the thread handling the run (also sent as the `x-thread-id` header). |
| `url` | Link to the thread page in the app, where you can follow the run. |
| `status` | `"started"` |
## Follow-up turns
Pass the `thread_id` from a previous response to continue the conversation in the same thread. The sandbox state and conversation history carry over, so the agent can build on its earlier work:
```bash theme={null}
curl -N -X POST https://anything.notte.cc/api/anything/start \
-H "Content-Type: application/json" \
-H "Authorization: Bearer $NOTTE_API_KEY" \
-d '{
"query": "now also include each post'\''s comment count",
"thread_id": "820a7cff-613b-4529-9ba4-52c7a6777713"
}'
```
Follow-up turns work in both streamed and detached mode.
## Consuming the stream
### Python
```python consume_stream.py theme={null}
import json
import os
import requests
NOTTE_API_KEY = os.environ["NOTTE_API_KEY"]
response = requests.post(
"https://anything.notte.cc/api/anything/start",
headers={
"Authorization": f"Bearer {NOTTE_API_KEY}",
"Content-Type": "application/json",
},
json={"query": "fetch the top 3 hacker news posts"},
stream=True,
timeout=(10, 600),
)
response.raise_for_status()
# The thread ID lets you send follow-up turns later
thread_id = response.headers["x-thread-id"]
print("Thread ID:", thread_id)
for line in response.iter_lines():
if not line:
continue
decoded = line.decode("utf-8")
if not decoded.startswith("data: "):
continue
payload = decoded[len("data: ") :]
if payload == "[DONE]":
break
chunk = json.loads(payload)
print(chunk.get("type"), chunk)
```
### TypeScript
```typescript theme={null}
const response = await fetch("https://anything.notte.cc/api/anything/start", {
method: "POST",
headers: {
Authorization: `Bearer ${NOTTE_API_KEY}`,
"Content-Type": "application/json",
},
body: JSON.stringify({ query: "fetch the top 3 hacker news posts" }),
});
if (!response.ok) {
throw new Error(`HTTP ${response.status}`);
}
if (!response.body) {
throw new Error("ReadableStream not available");
}
// The thread ID lets you send follow-up turns later
const threadId = response.headers.get("x-thread-id");
console.log("Thread ID:", threadId);
const reader = response.body.getReader();
const decoder = new TextDecoder();
let buffer = "";
let finished = false;
while (!finished) {
const { done, value } = await reader.read();
if (done) break;
buffer += decoder.decode(value, { stream: true });
const lines = buffer.split("\n");
// The last element is a partial line: keep it for the next chunk
buffer = lines.pop() ?? "";
for (const rawLine of lines) {
// Lines may be terminated with CRLF, so drop any trailing carriage return
const line = rawLine.replace(/\r$/, "");
if (!line.startsWith("data: ")) continue;
const payload = line.slice("data: ".length);
if (payload === "[DONE]") {
finished = true;
break;
}
const chunk = JSON.parse(payload);
console.log(chunk.type, chunk);
}
}
```
If you are building a chat interface, you can skip the manual parsing: the stream is directly compatible with the [AI SDK's `useChat` hook](https://ai-sdk.dev/docs/ai-sdk-ui/chatbot). Point it at [your own proxy route](#calling-from-a-browser), not at `anything.notte.cc`.
## Using the Node SDK
The Node SDK wraps this endpoint as [`client.anything.start()`](/typescript-sdk-reference/notteanything/start). It posts to `api.notte.cc/anything/start`, which proxies to the same v2 backend and pipes the stream back, so you get the parsed events without writing the reader yourself:
```typescript theme={null}
import { NotteClient } from 'notte-sdk';
const client = new NotteClient();
const run = await client.anything.start({ task: 'fetch the top 3 hacker news posts' });
console.log('Thread ID:', run.threadId);
for await (const chunk of run.chunks) {
console.log(chunk.type, chunk);
}
```
Two differences from calling the endpoint directly:
* The SDK field is `task`, not `query` -- the proxy maps it before forwarding.
* The proxy forwards only that field, so `thread_id`, `detach`, `model`, and `reasoningEffort` are not available through it. `run.threadId` still tells you which thread ran, but sending a follow-up turn into it means calling `anything.notte.cc` directly, as above.
The Python SDK does not wrap the Anything API yet -- use the HTTP example above.
## Calling from a browser
Your `NOTTE_API_KEY` is a server-side secret. A browser client that sends `Authorization: Bearer ` ships the key to every visitor, who can then spend your credits. There are no browser-scoped keys for this endpoint.
Put a route on your own server between the browser and the Anything API. The route holds the key, forwards the request, and pipes the stream back unchanged -- so `useChat` still sees the AI SDK UI message stream it expects:
```typescript app/api/anything/route.ts theme={null}
export async function POST(request: Request) {
const upstream = await fetch("https://anything.notte.cc/api/anything/start", {
method: "POST",
headers: {
// Read server-side; never sent to the browser
Authorization: `Bearer ${process.env.NOTTE_API_KEY}`,
"Content-Type": "application/json",
},
body: JSON.stringify(await request.json()),
});
// Forward the stream-format header, or useChat will not recognise the stream,
// and the thread ID, so the client can send follow-up turns
const headers = new Headers();
for (const name of [
"content-type",
"x-vercel-ai-ui-message-stream",
"x-thread-id",
]) {
const value = upstream.headers.get(name);
if (value) headers.set(name, value);
}
return new Response(upstream.body, { status: upstream.status, headers });
}
```
Then point the browser at your route -- `useChat({ api: "/api/anything" })`. Authenticate and authorize your own users in that route: it is now the thing standing between them and your Notte credits.
## Re-running a created function
When a run creates a reusable **Notte Function**, you can run it again via the [Notte CLI](https://github.com/nottelabs/notte-cli) or [SDK](/quickstart):
```bash CLI theme={null}
notte functions run \
--function-id d3c31289-f28b-49bd-a340-95e071cfef7e \
--vars '{"count": "3"}' \
-o json
```
```python run_function.py theme={null}
from notte_sdk import NotteClient
client = NotteClient()
function = client.Function(function_id="d3c31289-f28b-49bd-a340-95e071cfef7e")
result = function.run(count="3")
print(result)
```
## Migrating from v1
The v1 backend is retired. Requests with `version: "v1"` return `400 {"error": "unsupported_version"}` with a retirement message.
If you were calling the v1 API:
* **Omit `version`** (or send `"v2"`). It now defaults to `"v2"`.
* **Responses are an AI SDK UI message stream** instead of the v1 SSE event feed. The v1 event types (`status`, `thinking_delta`, `done`, ...) no longer exist.
* **Follow-ups use `thread_id`.** The v1 concepts `resume_strategy`, snapshot semantics, and `claude_code_session_id` are gone: pass the `thread_id` from the `x-thread-id` response header to continue a thread.
* The `x-vercel-ai-ui-message-stream: v1` response header refers to the AI SDK's own stream-format version, not the retired v1 backend.
## Error handling
| Status | Meaning |
| - | - |
| `400` | Missing `query` field, invalid JSON, or `version: "v1"` (`unsupported_version`) |
| `401` | Missing or invalid API key |
# File Storage
Source: https://docs.notte.cc/concepts/file-storage
Upload and download files for your automations
Read [the Notte skill](https://notte.cc/skill.md) and follow its setup instructions when setting up Notte or building browser automation. This is the shared source for CLI setup, authentication, and the browser-to-code workflow.
File Storage manages session-owned uploads and browser downloads. Upload files for your automation to attach to forms, and retrieve files the browser saves.
## Quick Start
```python quickstart.py theme={null}
from notte_sdk import NotteClient
client = NotteClient()
with client.Session() as session:
# Every file belongs to this session.
session.storage.upload("document.pdf")
session.execute(type="goto", url="https://example.com/upload")
session.execute(type="upload_file", selector='input[type="file"]', file_path="document.pdf")
session.execute(type="download_file", selector="a.receipt")
# Download the files the session retrieved
for file in session.storage.list("session_download").files:
session.storage.download(file.id, local_dir="./downloads")
```
## How It Works
Files have two sources:
| Source | Purpose | API |
| - | - | - |
| **Uploads** | Files you upload for sessions to use | `storage.upload()` |
| **Downloads** | Files sessions download from websites | `storage.download()` |
```
┌─────────────┐ ┌──────────────┐
│ Local Files │ ── upload() ─────> │ Uploads │
└─────────────┘ └──────┬───────┘
│
v
┌──────────────┐
│ Session │
│ │
└──────┬───────┘
│
v
┌─────────────┐ ┌──────────────┐
│ Local Files │ <── download() ─── │ Downloads │
└─────────────┘ └──────────────┘
```
## Uploading Files
Upload files from your local machine to make them available in sessions:
```python uploading_files.py theme={null}
from notte_sdk import NotteClient
client = NotteClient()
with client.Session() as session:
session.storage.upload("report.pdf")
session.storage.upload("report.pdf", upload_file_name="quarterly_report.pdf")
print(session.storage.list(source="user_upload").files)
```
```typescript uploading_files.ts theme={null}
const client = new NotteClient();
const storage = client.FileStorage();
await client.Session({ storage }).use(async session => {
await storage.upload('report.pdf');
await storage.upload('report.pdf', 'quarterly_report.pdf');
console.log((await storage.list({ source: 'user_upload' })).files);
});
```
Each file can be up to **1 GiB**, with up to **10 GiB** stored per session. For larger files, contact the
team at [support@notte.cc](mailto:support@notte.cc).
### Attaching a URL directly to a form
In a cloud session, pass a public HTTP(S) file URL, including a signed URL, to
`session.execute(type="upload_file", file_path=...)` along with the upload element's
selector or observed ID. Notte downloads and attaches the file without navigating
away from the form or requiring a separate `session.storage.upload()` call.
```python url_upload.py theme={null}
from notte_sdk import NotteClient
client = NotteClient()
with client.Session() as session:
session.execute(type="goto", url="https://test-resources-lovat.vercel.app/upload_fixture.html")
session.execute(
type="upload_file",
selector='input[type="file"]',
file_path="https://test-resources-lovat.vercel.app/text1.txt",
)
```
```typescript url_upload.ts theme={null}
const { NotteClient } = await import('notte-sdk');
const client = new NotteClient();
await client.Session().use(async session => {
await session.execute({
type: 'goto', url: 'https://test-resources-lovat.vercel.app/upload_fixture.html',
});
await session.execute({
type: 'upload_file',
selector: 'input[type="file"]',
file_path: 'https://test-resources-lovat.vercel.app/text1.txt',
});
});
```
The download runs on Notte's servers and does not use browser cookies, browser proxy
settings, or custom authentication headers. URLs must resolve to public addresses,
including after redirects. Files fetched this way are temporary: they are removed
when the session is cleaned up and are not added to the session's file list.
`storage.upload()` only accepts local files; URL support belongs to the browser's
`upload_file` action.
## Downloading Files
Download files that your session retrieved from websites with the `download_file` action:
```python downloading_files.py theme={null}
from notte_sdk import NotteClient
client = NotteClient()
with client.Session() as session:
session.execute(type="goto", url="https://example.com/account")
session.execute(type="download_file", selector="a.invoice")
# List downloaded files
downloaded = session.storage.list("session_download").files
print(f"Downloaded: {downloaded}")
# Download to local directory
for file in downloaded:
session.storage.download(file.id, local_dir="./invoices")
```
### Force Overwrite
Overwrite existing local files:
Before running this example, set `session_id` (Python) or `sessionId` (Node) to
your storage's session ID, and `file_id` or `fileId` to the ID returned by uploading
or listing the file. The Node example uses the `client` initialized above.
```python force_overwrite.py theme={null}
from notte_sdk import NotteClient
client = NotteClient()
storage = client.FileStorage(session_id)
storage.download(file_id, local_dir="./downloads", force=True)
```
```typescript force_overwrite.ts theme={null}
const storage = client.FileStorage(sessionId);
await storage.download(fileId, './downloads', { force: true });
```
## Using with Sessions
Use file storage with scripted automation:
```python using_with_sessions.py theme={null}
from notte_sdk import NotteClient
client = NotteClient()
with client.Session() as session:
session.storage.upload("data.csv")
session.execute(type="goto", url="https://example.com/import")
# Upload using the upload_file action
session.execute(type="upload_file", selector='input[type="file"]', file_path="data.csv")
session.execute(type="click", selector="button.submit")
# Download any files
for file in session.storage.list(source="session_download").files:
session.storage.download(file.id, local_dir="./results")
```
## API Reference
### FileStorage Methods
| Method | Description |
| - | - |
| `upload(file_path, upload_file_name=None)` | Upload a local file to storage |
| `download(file_name, local_dir, force=False)` | Download a file to local directory |
| `list_uploaded_files()` | List files you've uploaded |
| `list_downloaded_files()` | List files downloaded by sessions |
### Parameters
**upload()**
* `file_path` - Path to the local file to upload
* `upload_file_name` - Optional custom name for the uploaded file
**download()**
* `file_id` - ID of the file, from `upload()` or `list()` (`SessionFile.id`)
* `local_dir` - Local directory to save the file (default: current directory)
* `force` - Overwrite an existing local file (default: False)
## Supported File Types
File storage supports all common file types:
* **Documents**: PDF, DOCX, XLSX, TXT, CSV
* **Images**: PNG, JPG, JPEG, GIF, WEBP
* **Archives**: ZIP, RAR, TAR, GZ
* **Media**: MP4, MP3
* **Data**: JSON, XML
## Best Practices
### 1. Use Descriptive Filenames
```python descriptive_filenames.py theme={null}
from datetime import datetime
from notte_sdk import NotteClient
client = NotteClient()
timestamp = datetime.now().strftime("%Y%m%d_%H%M%S")
with client.Session() as session:
session.storage.upload("report.pdf", upload_file_name=f"report_{timestamp}.pdf")
```
```typescript descriptive_filenames.ts theme={null}
const { NotteClient } = await import('notte-sdk');
const client = new NotteClient();
const now = new Date();
const date = [now.getFullYear(), now.getMonth() + 1, now.getDate()].map(n => String(n).padStart(2, '0')).join('');
const time = [now.getHours(), now.getMinutes(), now.getSeconds()].map(n => String(n).padStart(2, '0')).join('');
const timestamp = `${date}_${time}`;
const storage = client.FileStorage();
await client.Session({ storage }).use(async session => {
await storage.upload('report.pdf', `report_${timestamp}.pdf`);
});
```
### 2. Check Downloads After Session
Always check for downloaded files after the session ends:
```python check_downloads.py theme={null}
from notte_sdk import NotteClient
client = NotteClient()
with client.Session() as session:
session.execute(type="goto", url="https://example.com/account")
session.execute(type="download_file", selector="a.invoice")
# Check what was downloaded
files = session.storage.list("session_download").files
if not files:
print("No files were downloaded")
else:
for f in files:
_ = session.storage.download(f.id, local_dir="./invoices")
```
## Next Steps
Learn about browser sessions
Store credentials securely
Create browser identities
# Functions
Source: https://docs.notte.cc/concepts/functions
Deploy browser automations as serverless API endpoints
Read [the Notte skill](https://notte.cc/skill.md) and follow its setup instructions when setting up Notte or building browser automation. This is the shared source for CLI setup, authentication, and the browser-to-code workflow.
Functions are serverless deployments of your browser automations that can be invoked via API, scheduled to run automatically, or triggered by events.
## What are Functions?
Functions turn your automation scripts into:
* **API endpoints** you can call with HTTP requests
* **Scheduled jobs** that run on a cron schedule
* **Reusable workflows** accessible from anywhere
* **Shareable automations** for your team
Unlike running scripts locally, Functions:
* ✅ Run on Notte's infrastructure (no servers to manage)
* ✅ Scale automatically based on demand
* ✅ Provide built-in logging and monitoring
* ✅ Can be invoked from any platform (Python, JavaScript, cURL, etc.)
* ✅ Support scheduling and automation
## How Functions Work
### 1. Write Your Script
Create a Python file with a `run()` function:
```python basic_function.py theme={null}
from notte_sdk import NotteClient
client = NotteClient()
def run(url: str, search_query: str):
"""Search a website url and extract results."""
with client.Session() as session:
# Navigate to site
session.execute(type="goto", url=url)
# Search
session.execute(type="fill", selector="input[name='search']", value=search_query)
session.execute(type="press_key", key="Enter")
# Extract results
results = session.scrape(instructions="Extract search results")
return results
```
### 2. Deploy to Notte
Upload your script to create a Function:
```python deploy_function.py theme={null}
from notte_sdk import NotteClient
client = NotteClient()
# Deploy function
function = client.Function(
path="my_automation.py", name="Search Automation", description="Searches a website and extracts results"
)
print(f"Function deployed: {function.function_id}")
print(f"Version: {function.response.latest_version}")
```
```typescript deploy_function.ts theme={null}
import { NotteClient } from 'notte-sdk';
const client = new NotteClient();
// Deploy function
const fn = client.NotteFunction({
path: 'my_automation.py',
name: 'Search Automation',
description: 'Searches a website and extracts results',
});
const response = await fn.get();
console.log(`Function deployed: ${response.function_id}`);
console.log(`Version: ${response.latest_version}`);
```
### 3. Invoke the Function
Call your Function using either SDK. Set `NOTTE_FUNCTION_ID` to the deployed function's ID first:
```bash theme={null}
export NOTTE_FUNCTION_ID=your_function_id
```
```python invoke_sdk.py theme={null}
import os
from notte_sdk import NotteClient
client = NotteClient()
function = client.Function(function_id=os.environ["NOTTE_FUNCTION_ID"])
# Via SDK
result = function.run(url="https://example.com", search_query="laptop")
print(result.result)
```
```typescript invoke_sdk.ts theme={null}
import { NotteClient } from 'notte-sdk';
const client = new NotteClient();
const fn = client.NotteFunction({ function_id: process.env.NOTTE_FUNCTION_ID! });
const result = await fn.run({ url: 'https://example.com', search_query: 'laptop' });
console.log(result.result);
```
```bash theme={null}
# Via cURL
curl -X POST https://api.notte.cc/functions/{function_id}/runs/start \
-H "Authorization: Bearer YOUR_API_KEY" \
-H "x-notte-api-key: YOUR_API_KEY" \
-H "Content-Type: application/json" \
-d '{
"function_id": "workflow_123",
"variables": {
"url": "https://example.com",
"search_query": "laptop"
}
}'
```
Raw HTTP calls to `runs/start` must send your API key twice: as `Authorization: Bearer` and as the `x-notte-api-key` header. The SDKs do this for you.
## Large run outputs
The Python and TypeScript SDKs upload individual run results, logs, and variables
larger than 1 MiB to private storage. Python `functions.run_update_metadata()` and TypeScript
`fn.updateRun(runId, fields)` save a small reference. Python `functions.run_get_metadata()`
and TypeScript `fn.getRun()` download and verify the full value automatically.
Each stored field can be up to 256 MiB. Small fields continue to use inline updates.
Upgrade your SDK to use this behavior. An inline run update above 8 MiB is
rejected. Reading a run with externally stored fields through an older client
returns an explicit error rather than a partial result.
For HTTP integrations, request a run with `?payload_mode=references`. The response
includes `payloads` descriptors and short-lived `payload_urls` for the external
fields. Download these URLs without your API authorization header, verify each
field's `size_bytes` and base64 SHA-256 checksum, and decode its JSON value.
## Function Structure
### The Handler Function
Functions must have a `run()` function that serves as the entry point:
```python handler_function.py theme={null}
def run(param1: str, param2: int = 10):
"""
Function docstring explains what it does.
Args:
param1: Description of param1
param2: Description of param2 (optional)
Returns:
Description of return value
"""
# Your automation code
result = perform_automation(param1, param2)
return result
```
**Key points:**
* Named `run()` - this is the entry point
* Can accept parameters (passed as `variables` when invoked)
* Should have type hints for clarity
* Should include docstring documentation
* Returns a value (any JSON-serializable type)
### Parameters
Define parameters as arguments to the `run` function:
```python parameters_example.py theme={null}
from notte_sdk import NotteClient
def run(email: str, password: str, product_id: str):
client = NotteClient()
with client.Session() as session:
session.execute(type="fill", id="email", value=email)
session.execute(type="fill", id="password", value=password)
session.execute(type="goto", url=f"https://example.com/product/{product_id}")
```
Pass values when invoking the function:
```python pass_values.py theme={null}
function.run(email="user@example.com", password="secret", product_id="123")
```
### Return Values
Functions can return any JSON-serializable data:
```python return_values.py theme={null}
from pydantic import BaseModel
# Return string
def run_string():
return "Task completed successfully"
# Return dict
def run_dict():
extracted_data = ["item1", "item2"]
return {"status": "success", "data": extracted_data, "count": len(extracted_data)}
# Return list
def run_list():
return ["item1", "item2", "item3"]
# Return Pydantic model (serialized)
class Result(BaseModel):
success: bool
data: list[str]
def run():
return Result(success=True, data=["a", "b"]).model_dump()
```
## Use Cases
### 1. Scheduled Scraping
Extract data on a schedule:
```python price_monitor.py theme={null}
from notte_sdk import NotteClient
def run(product_urls: list[str]):
client = NotteClient()
prices = []
for url in product_urls:
with client.Session() as session:
session.execute(type="goto", url=url)
price = session.scrape(instructions="Extract product price")
prices.append({"url": url, "price": price})
return prices
# Deploy this file with client.Function(path="price_monitor.py"), then schedule it from
# the Console Schedule tab or POST /functions/{id}/schedule with the cron expression
# 0 9 ? * * * (every day at 9:00 UTC)
```
**Schedule:** `0 9 ? * * *` (every day at 9:00 UTC, see [Schedules](/features/functions/schedules))
***
### 2. API Endpoints
Expose automation as an API:
```python contact_extractor.py theme={null}
from notte_sdk import NotteClient
# contact_extractor.py
def run(company_url: str):
client = NotteClient()
with client.Session() as session:
session.execute(type="goto", url=company_url)
contact_info = session.scrape(instructions="Extract contact email and phone")
return contact_info
# Now callable from any service
# POST /functions/{id}/runs/start
# body: {"function_id": "...", "variables": {"company_url": "..."}}
```
***
### 3. Webhooks
Trigger automations from external events:
```python order_processor.py theme={null}
from notte_sdk import NotteClient
# order_processor.py
def run(order_id: str, action: str):
client = NotteClient()
with client.Session() as session:
# Login to admin panel
session.execute(type="goto", url="https://admin.example.com")
# Process order
if action == "fulfill":
session.execute(type="click", selector=f"button[data-order='{order_id}'].fulfill")
elif action == "refund":
session.execute(type="click", selector=f"button[data-order='{order_id}'].refund")
return {"order_id": order_id, "action": action, "status": "completed"}
# Called via webhook from your e-commerce platform
```
***
### 4. Batch Processing
Process multiple items in parallel:
```python bulk_data_extract.py theme={null}
from concurrent.futures import ThreadPoolExecutor
from notte_sdk import NotteClient
# bulk_data_extract.py
def run(urls: list[str], max_workers: int = 5):
client = NotteClient()
def extract_from_url(url):
with client.Session() as session:
session.execute(type="goto", url=url)
data = session.scrape()
return {"url": url, "data": data}
with ThreadPoolExecutor(max_workers=max_workers) as executor:
results = list(executor.map(extract_from_url, urls))
return results
```
## How Functions Fit In
Functions are a **deployment layer** - they turn any automation into a reusable API.
```
┌─────────────────────────────────────────────────────┐
│ Function │
│ (Deployment & Scheduling) │
├─────────────────────────────────────────────────────┤
│ │
│ ┌─────────────────┐ │
│ │ Your code │ │
│ │ (actions, CDP) │ │
│ └────────┬────────┘ │
│ │ │
│ ┌────────▼────────┐ │
│ │ Session │ │
│ │ (Cloud Browser) │ │
│ └─────────────────┘ │
│ │
└─────────────────────────────────────────────────────┘
```
* **Session** - The cloud browser that runs everything
* **Your code** - How you control the session (Notte actions, scraping, Playwright over CDP)
* **Function** - Deploys your automation as an API with scheduling, versioning, and sharing
**Use Functions when:**
* You need to run automation repeatedly
* You want to expose automation as an API
* You need scheduling capabilities
* You want to share automation with team or customers
## Function Lifecycle
```mermaid theme={null}
graph LR
A[Write Script] --> B[Deploy Function]
B --> C[Get Function ID]
C --> D{Invoke}
D -->|API Call| E[Execute]
D -->|Schedule| E
D -->|Webhook| E
E --> F[Return Result]
F --> G[Access Logs/Replay]
```
1. **Write** - Create Python script with `run()` function
2. **Deploy** - Upload to Notte (creates function ID)
3. **Version** - Notte tracks versions automatically
4. **Invoke** - Call via API, schedule, or webhook
5. **Execute** - Runs on Notte infrastructure
6. **Monitor** - View logs, replays, and results
## Best Practices
### 1. Use Clear Parameters
Define parameters with type hints and descriptions:
```python bp_clear_parameters.py theme={null}
def run(target_url: str, max_results: int = 10, timeout_seconds: int = 30):
"""
Extract data from a website.
Args:
target_url: The URL to scrape
max_results: Maximum number of results to extract
timeout_seconds: Session timeout
"""
pass
```
### 2. Return Structured Data
Always return JSON-serializable data:
```python bp_return_structured.py theme={null}
from datetime import datetime
def my_function():
results = [] # your data
# Good - return JSON-serializable dict
return {"success": True, "data": results, "count": len(results), "timestamp": datetime.now().isoformat()}
# Bad - not JSON serializable
# return datetime.now() # Can't serialize datetime directly
```
### 3. Handle Errors Gracefully
Catch exceptions and return meaningful errors:
```python bp_handle_errors.py theme={null}
from notte_sdk import NotteClient
def run(url: str):
try:
client = NotteClient()
with client.Session() as session:
session.execute(type="goto", url=url)
data = session.scrape()
return {"success": True, "data": data}
except Exception as e:
return {"success": False, "error": str(e), "error_type": type(e).__name__}
```
### 4. Add Logging
Log important steps for debugging:
```python bp_add_logging.py theme={null}
from loguru import logger
from notte_sdk import NotteClient
def run(url: str):
logger.info(f"Starting automation for {url}")
client = NotteClient()
with client.Session() as session:
logger.info("Session started")
session.execute(type="goto", url=url)
logger.info("Navigation complete")
data = session.scrape()
logger.info(f"Extracted {len(data)} characters of markdown")
return data
```
## Next Steps
Learn how to write and deploy Functions
Call Functions via API, SDK, or cURL
Schedule Functions with cron
Update, version, and monitor Functions
# Persona Identities
Source: https://docs.notte.cc/concepts/personas
Self-service identities for web automation (account creation, 2FA,etc.)
Read [the Notte skill](https://notte.cc/skill.md) and follow its setup instructions when setting up Notte or building browser automation. This is the shared source for CLI setup, authentication, and the browser-to-code workflow.
## Overview
Notte Personas provide automated identity management for AI agents, enabling them to create accounts, handle two-factor authentication, and interact with web platforms without manual intervention.
`Notte Personas` come with complete digital identities:
* **Unique Email Address**: Dedicated mailbox for each persona with full email management
* **Phone Number**: SMS-capable phone number for verification and 2FA
* **Credential Vault**: Optional secure storage for passwords and authentication tokens
* **Automated Communication**: Built-in email and SMS reading capabilities
* **2FA Support**: Seamless handling of two-factor authentication flows
### Persona Attributes
| Field | Example Value |
| - | - |
| **Name** | David Mason |
| Email | [david.smith@mail-sand.com](mailto:david.smith@mail-sand.com) |
| Phone Number | +1234567890 |
| Notte Secret Vault ID | `21e75bga-1e3f-4389-a034-9aaa62d1f355` |
| Persona ID | `ab9ec497-8e49-49ab-9af7-cc04a95a0613` |
## How it works
Personas act as complete digital identities that your AI agents can assume during execution. When an agent needs to create an account or authenticate with a service, it uses the persona's credentials and communication channels to complete the entire flow autonomously.
The persona system integrates with both the browser session and the secure vault, allowing agents to:
1. Fill registration forms with persona details
2. Receive and read verification emails automatically
3. Handle SMS-based 2FA codes
4. Store generated passwords and tokens securely
This creates a seamless experience where agents can navigate complex authentication flows without breaking execution to wait for human intervention.
## Persona Management
### Python SDK
The following snippet shows how to create and manage personas using the Notte Python SDK.
> ⚠️ You need to have an active Notte account to create personas. Each persona incurs additional charges for the email and phone services.
```python personas.py theme={null}
import datetime as dt
from notte_sdk.client import NotteClient
client = NotteClient()
persona = client.Persona(create_vault=True)
print(f"Persona email: {persona.info.email}")
# add a credential to the persona: password is generated automatically and email is the persona's email
persona.add_credentials(url="https://github.com/")
# read recent emails
recent_emails = persona.emails(only_unread=True, limit=10, timedelta=dt.timedelta(minutes=5))
print(f"Recent emails: {recent_emails}")
# get your persona in subsequent scripts
same_persona = client.Persona(persona.info.persona_id)
assert same_persona.info == persona.info
# delete the persona when you don't need it anymore
persona.delete()
```
### Choosing an email provider
Each persona gets an inbox from an email provider, chosen with `email_provider` when you create it:
| `email_provider` | Inbox |
| - | - |
| `notte` (default) | Notte mailbox, the same inbox personas have always had |
| `agentmail` | An inbox from [AgentMail](https://agentmail.to), an email inbox API for AI agents, on an `@agentmail.to` address |
| `resend` | An address on Notte's [Resend](https://resend.com) receiving domain |
```python agentmail_persona.py theme={null}
from notte_sdk import NotteClient
client = NotteClient()
# email_provider defaults to "notte"; "agentmail" gives the persona an AgentMail inbox
persona = client.Persona(email_provider="agentmail")
print(f"Persona email: {persona.info.email}") # an @agentmail.to address
# read emails exactly as with the default provider
emails = persona.emails(limit=10)
for email in emails:
print(email.subject)
# deleting the persona also deletes its AgentMail inbox
persona.delete()
```
With the REST API, pass `email_provider` to `POST /personas/create`. Persona responses include the `email_provider` field.
```bash cURL theme={null}
# create a persona with an AgentMail inbox
curl -X POST https://api.notte.cc/personas/create \
-H "Authorization: Bearer $NOTTE_API_KEY" \
-H "Content-Type: application/json" \
-d '{"email_provider": "agentmail"}'
# read the persona's emails
curl "https://api.notte.cc/personas/YOUR_PERSONA_ID/emails?limit=10" \
-H "Authorization: Bearer $NOTTE_API_KEY"
```
Only the inbox backend and the email address change. Everything else works the same way with either provider:
* Read emails with `persona.emails()` or `GET /personas/{persona_id}/emails`.
* Agents that use the persona read verification emails automatically.
* Vaults, credentials and phone numbers are unaffected.
* Deleting the persona also deletes its AgentMail inbox.
AgentMail and Resend inboxes are receive-only: personas cannot send email. See the [AgentMail integration guide](/integrations/agentmail) for a full sign-up example.
## Use Cases
### Account Creation Workflows
Personas excel at automating account registration across multiple platforms:
* **SaaS Signups**: Automatically create accounts on software platforms
* **E-commerce**: Register for shopping sites and marketplaces
* **Social Media**: Set up profiles across social networks
* **Financial Services**: Complete KYC and account opening processes
* **QA Testing**: Generate test accounts for quality assurance
## Security & Privacy
* 🔒 Each persona operates in complete isolation
* 🔒 Communication channels are encrypted and secure
* 🔒 Optional integration with Notte's secure vault system
* 🔒 Automatic cleanup and data retention policies
* 🔒 Full audit trail of persona activities
Personas are designed to work seamlessly with Notte's existing vault system. You can create a persona with an associated vault to store any credentials generated during account creation, ensuring they're available for future agent sessions.
Personas should be used responsibly and in compliance with the terms of service of the platforms your agents interact with. Ensure your use cases align with platform policies and applicable regulations.
# Fetch
Source: https://docs.notte.cc/concepts/scraping
Extract content and structured data from web pages
Read [the Notte skill](https://notte.cc/skill.md) and follow its setup instructions when setting up Notte or building browser automation. This is the shared source for CLI setup, authentication, and the browser-to-code workflow.
Fetch extracts web page content as markdown or structured data using LLM-powered extraction. In the SDK this is `scrape`. In the console it is [Fetch](https://console.notte.cc).
## Quick Start
Extract structured data from a page:
```python Python theme={null}
from notte_sdk import NotteClient
from pydantic import BaseModel
class HackerNewsPost(BaseModel):
title: str
url: str
points: int
author: str
class HackerNewsFeed(BaseModel):
posts: list[HackerNewsPost]
client = NotteClient()
result = client.scrape(
url="https://news.ycombinator.com",
response_format=HackerNewsFeed,
instructions="Extract the top 5 posts from the front page",
)
for i, post in enumerate(result.posts, 1):
print(f"{i}. {post.points} - {post.title}")
```
```typescript TypeScript theme={null}
import { z } from 'zod';
import { NotteClient } from 'notte-sdk';
const HackerNewsPost = z.object({
title: z.string(),
url: z.string(),
points: z.number(),
author: z.string(),
});
const HackerNewsFeed = z.object({
posts: z.array(HackerNewsPost),
});
const notte = new NotteClient({
apiKey: process.env.NOTTE_API_KEY,
});
const result = await notte.scrape('https://news.ycombinator.com', {
instructions: 'Extract the top 5 posts from the front page',
response_format: HackerNewsFeed,
});
result.posts.forEach((post, index) => {
console.log(`${index + 1}. ${post.points} - ${post.title}`);
});
```
## Scraping Methods
Notte provides two ways to scrape:
| Method | Use Case |
| - | - |
| `client.scrape(url)` | Quick, one-off scrapes |
| `session.scrape()` | Scraping after navigation or authentication |
### Quick Scrape
For simple scraping without session management:
```python Python theme={null}
from notte_sdk import NotteClient
client = NotteClient()
# Returns markdown content
markdown = client.scrape("https://example.com")
```
```typescript TypeScript theme={null}
import { NotteClient } from 'notte-sdk';
const client = new NotteClient();
// Returns markdown content
const markdown = await client.scrape('https://example.com');
```
### Session-Based Scrape
For scraping after authentication or navigation:
```python Python theme={null}
from notte_sdk import NotteClient
client = NotteClient()
with client.Session() as session:
# Navigate and authenticate
session.execute(type="goto", url="https://example.com/login")
session.execute(type="fill", selector="input[name='email']", value="user@example.com")
session.execute(type="fill", selector="input[name='password']", value="password")
session.execute(type="click", selector="button[type='submit']")
# Navigate to protected page
session.execute(type="goto", url="https://example.com/dashboard")
# Scrape the page
content = session.scrape()
```
```typescript TypeScript theme={null}
import { NotteClient } from 'notte-sdk';
const client = new NotteClient({
apiKey: process.env.NOTTE_API_KEY,
});
await client.Session().use(async (session) => {
// Navigate and authenticate
await session.execute({ type: 'goto', url: 'https://example.com/login' });
await session.execute({ type: 'fill', selector: "input[name='email']", value: 'user@example.com' });
await session.execute({ type: 'fill', selector: "input[name='password']", value: 'password' });
await session.execute({ type: 'click', selector: "button[type='submit']" });
// Navigate to protected page
await session.execute({ type: 'goto', url: 'https://example.com/dashboard' });
// Scrape the page
const content = await session.scrape();
});
```
## Structured Extraction
Extract data into typed Python objects using Pydantic models. The extraction is powered by an LLM that understands the page content and extracts the specified fields.
### Using Pydantic Models
Define a schema and extract matching data:
```python Python theme={null}
from notte_sdk import NotteClient
from pydantic import BaseModel
class Product(BaseModel):
name: str
price: float
description: str
client = NotteClient()
product = client.scrape(
"https://example.com/product", response_format=Product, instructions="Extract the product details"
)
print(f"Name: {product.name}, Price: {product.price}")
```
```typescript TypeScript theme={null}
import { z } from 'zod';
import { NotteClient } from 'notte-sdk';
const Product = z.object({
name: z.string(),
price: z.number(),
description: z.string(),
});
const client = new NotteClient({
apiKey: process.env.NOTTE_API_KEY,
});
const product = await client.scrape('https://example.com/product', {
response_format: Product,
instructions: 'Extract the product details',
});
console.log(`Name: ${product.name}, Price: ${product.price}`);
```
### Using Instructions Only
For flexible extraction without a strict schema:
```python Python theme={null}
from notte_sdk import NotteClient
client = NotteClient()
result = client.scrape(
"https://example.com/article", instructions="Extract the article title, author, and publication date"
)
print(result)
```
```typescript TypeScript theme={null}
import { NotteClient } from 'notte-sdk';
const client = new NotteClient({
apiKey: process.env.NOTTE_API_KEY,
});
const result = await client.scrape('https://example.com/article', {
instructions: 'Extract the article title, author, and publication date',
});
console.log(result);
```
### Extracting Lists
Extract multiple items from a page:
```python Python theme={null}
from notte_sdk import NotteClient
from pydantic import BaseModel
class Article(BaseModel):
title: str
url: str
summary: str
class ArticleList(BaseModel):
articles: list[Article]
client = NotteClient()
articles = client.scrape(
"https://news.example.com", response_format=ArticleList, instructions="Extract all articles from the homepage"
)
for article in articles.articles:
print(f"{article.title}: {article.url}")
```
```typescript TypeScript theme={null}
import { z } from 'zod';
import { NotteClient } from 'notte-sdk';
const Article = z.object({
title: z.string(),
url: z.string(),
summary: z.string(),
});
const ArticleList = z.object({
articles: z.array(Article),
});
const client = new NotteClient({
apiKey: process.env.NOTTE_API_KEY,
});
const articles = await client.scrape('https://news.example.com', {
response_format: ArticleList,
instructions: 'Extract all articles from the homepage',
});
for (const article of articles.articles) {
console.log(`${article.title}: ${article.url}`);
}
```
### Nested Structures
Handle complex, nested data:
```python Python theme={null}
from notte_sdk import NotteClient
from pydantic import BaseModel
class Address(BaseModel):
street: str
city: str
country: str
class Company(BaseModel):
name: str
description: str
address: Address
employee_count: int | None
client = NotteClient()
company = client.scrape(
"https://example.com/about", response_format=Company, instructions="Extract company information including address"
)
print(company.address.city)
```
```typescript TypeScript theme={null}
import { z } from 'zod';
import { NotteClient } from 'notte-sdk';
const Address = z.object({
street: z.string(),
city: z.string(),
country: z.string(),
});
const Company = z.object({
name: z.string(),
description: z.string(),
address: Address,
employee_count: z.number().nullable(),
});
const client = new NotteClient({
apiKey: process.env.NOTTE_API_KEY,
});
const company = await client.scrape('https://example.com/about', {
response_format: Company,
instructions: 'Extract company information including address',
});
console.log(company.address.city);
```
## Configuration Options
### Content Filtering
Control what content gets extracted:
```python Python theme={null}
# Only main content (excludes navbars, footers, sidebars)
markdown = client.scrape(url, only_main_content=True)
# Include all page content (default)
markdown = client.scrape(url, only_main_content=False)
```
```typescript TypeScript theme={null}
// Only main content (excludes navbars, footers, sidebars)
let markdown = await client.scrape(url, { only_main_content: true });
// Include all page content (default)
markdown = await client.scrape(url, { only_main_content: false });
```
### Links and Images
Control link and image extraction:
```python Python theme={null}
# Include links (default)
markdown = client.scrape(url, scrape_links=True)
# Exclude links
markdown = client.scrape(url, scrape_links=False)
# Include images in markdown
markdown = client.scrape(url, scrape_images=True)
# Exclude images (default)
markdown = client.scrape(url, scrape_images=False)
```
```typescript TypeScript theme={null}
// Include links (default)
let markdown = await client.scrape(url, { scrape_links: true });
// Exclude links
markdown = await client.scrape(url, { scrape_links: false });
// Include images in markdown
markdown = await client.scrape(url, { scrape_images: true });
// Exclude images (default)
markdown = await client.scrape(url, { scrape_images: false });
```
### Scoped Scraping
Scrape only a specific section of the page:
```python Python theme={null}
from notte_sdk import NotteClient
client = NotteClient()
with client.Session() as session:
# Scrape content within a specific selector
content = session.scrape(selector="article.main-content")
# Scrape a specific container
content = session.scrape(selector="#product-details")
```
```typescript TypeScript theme={null}
import { NotteClient } from 'notte-sdk';
const client = new NotteClient({
apiKey: process.env.NOTTE_API_KEY,
});
await client.Session().use(async (session) => {
// Scrape content within a specific selector
const articleContent = await session.scrape({
selector: 'article.main-content',
});
// Scrape a specific container
const productContent = await session.scrape({
selector: '#product-details',
});
});
```
### Link Placeholders
Reduce output size by using placeholders:
```python Python theme={null}
# Use placeholders for links and images
markdown = client.scrape(url, use_link_placeholders=True)
```
```typescript TypeScript theme={null}
// Use placeholders for links and images
const markdown = await client.scrape(url, {
use_link_placeholders: true,
});
```
### Extraction Model
When you pass `instructions` or `response_format`, the extraction runs on the platform's default model. Set `model` to use a different one, for example `client.scrape(url, instructions="...", model="anthropic/claude-sonnet-4-5-20250929")`.
## Return Types
The scrape method returns different types based on parameters:
| Parameters | Return Type |
| - | - |
| None | `str` (markdown) |
| `instructions` | `dict` with extracted data |
| `response_format` | An instance of your model |
Pass `raise_on_failure=False` to get a `StructuredData` wrapper with `success`, `error`, and `data` fields instead.
### Structured Response
When using structured extraction, the extracted data is returned directly:
```python Python theme={null}
from notte_sdk import NotteClient
from pydantic import BaseModel
class Product(BaseModel):
name: str
price: float
client = NotteClient()
url = "https://example.com/product"
product = client.scrape(url, response_format=Product)
# Access the extracted data
print(f"Name: {product.name}, Price: {product.price}")
```
```typescript TypeScript theme={null}
import { z } from 'zod';
import { NotteClient } from 'notte-sdk';
const Product = z.object({
name: z.string(),
price: z.number(),
});
const client = new NotteClient({
apiKey: process.env.NOTTE_API_KEY,
});
const url = 'https://example.com/product';
const product = await client.scrape(url, {
response_format: Product,
});
// Access the extracted data
console.log(`Name: ${product.name}, Price: ${product.price}`);
```
## Use Cases
### Data Collection
Collect product information:
```python Python theme={null}
from notte_sdk import NotteClient
from pydantic import BaseModel
class ProductInfo(BaseModel):
name: str
price: float
rating: float | None
reviews_count: int | None
client = NotteClient()
urls = [
"https://store.example.com/product/1",
"https://store.example.com/product/2",
]
products: list[ProductInfo] = []
for url in urls:
data = client.scrape(url, response_format=ProductInfo)
products.append(data)
```
```typescript TypeScript theme={null}
import { z } from 'zod';
import { NotteClient } from 'notte-sdk';
const ProductInfo = z.object({
name: z.string(),
price: z.number(),
rating: z.number().nullable(),
reviews_count: z.number().nullable(),
});
const client = new NotteClient({
apiKey: process.env.NOTTE_API_KEY,
});
const urls = [
'https://store.example.com/product/1',
'https://store.example.com/product/2',
];
const products = [];
for (const url of urls) {
const data = await client.scrape(url, {
response_format: ProductInfo,
});
products.push(data);
}
```
### Content Monitoring
Track content changes:
```python Python theme={null}
from notte_sdk import NotteClient
client = NotteClient()
# Get current content
content = client.scrape("https://example.com/pricing", instructions="Extract all pricing tiers and their features")
# Compare with previous version
# ...
```
```typescript TypeScript theme={null}
import { NotteClient } from 'notte-sdk';
const client = new NotteClient({
apiKey: process.env.NOTTE_API_KEY,
});
// Get current content
const content = await client.scrape('https://example.com/pricing', {
instructions: 'Extract all pricing tiers and their features',
});
// Compare with previous version
// ...
```
### Research and Analysis
Extract structured research data:
```python Python theme={null}
from notte_sdk import NotteClient
from pydantic import BaseModel
class ResearchPaper(BaseModel):
title: str
authors: list[str]
abstract: str
publication_date: str | None
citations: int | None
client = NotteClient()
result = client.scrape("https://papers.example.com/paper/123", response_format=ResearchPaper)
```
```typescript TypeScript theme={null}
import { z } from 'zod';
import { NotteClient } from 'notte-sdk';
const ResearchPaper = z.object({
title: z.string(),
authors: z.array(z.string()),
abstract: z.string(),
publication_date: z.string().nullable(),
citations: z.number().nullable(),
});
const client = new NotteClient({
apiKey: process.env.NOTTE_API_KEY,
});
const result = await client.scrape('https://papers.example.com/paper/123', {
response_format: ResearchPaper,
});
```
## Best Practices
### 1. Use Specific Instructions
Clear instructions improve extraction accuracy:
```python Python theme={null}
# Good
instructions = "Extract the product name, price in USD, and availability status"
# Vague
instructions = "Get product info"
```
```typescript TypeScript theme={null}
// Good
const instructions = 'Extract the product name, price in USD, and availability status';
// Vague
const vagueInstructions = 'Get product info';
```
### 2. Define Precise Schemas
Match your schema to the actual page content:
```python Python theme={null}
from pydantic import BaseModel
# Good - matches page structure
class GoodProduct(BaseModel):
name: str
price: float
in_stock: bool
# Bad - fields that may not exist
class BadProduct(BaseModel):
name: str
price: float
manufacturer: str # Page might not have this
warranty: str # Page might not have this
```
```typescript TypeScript theme={null}
import { z } from 'zod';
// Good - matches page structure
const GoodProduct = z.object({
name: z.string(),
price: z.number(),
in_stock: z.boolean(),
});
// Bad - fields that may not exist
const BadProduct = z.object({
name: z.string(),
price: z.number(),
manufacturer: z.string(), // Page might not have this
warranty: z.string(), // Page might not have this
});
```
### 3. Handle Missing Data
Use optional fields for data that might not exist:
```python Python theme={null}
from pydantic import BaseModel
class Product(BaseModel):
name: str
price: float
discount_price: float | None = None # Optional
rating: float | None = None # Optional
```
```typescript TypeScript theme={null}
import { z } from 'zod';
const Product = z.object({
name: z.string(),
price: z.number(),
discount_price: z.number().nullable().optional(), // Optional
rating: z.number().nullable().optional(), // Optional
});
```
### 4. Scope Your Scrapes
Use selectors to focus on relevant content:
```python Python theme={null}
from notte_sdk import NotteClient
client = NotteClient()
with client.Session() as session:
# Scrape only the main article, not comments or sidebar
content = session.scrape(selector="article.main")
```
```typescript TypeScript theme={null}
import { NotteClient } from 'notte-sdk';
const client = new NotteClient({
apiKey: process.env.NOTTE_API_KEY,
});
await client.Session().use(async (session) => {
// Scrape only the main article, not comments or sidebar
const content = await session.scrape({
selector: 'article.main',
});
});
```
## Next Steps
Learn about session management
Find pages before you fetch them
Store credentials for authenticated scraping
Deploy scraping as serverless functions
## Multi-step extraction
Use `client.scrape()` for a single URL, or `session.scrape()` after navigation and authentication:
```python authenticated_scrape.py theme={null}
from notte_core.credentials import EMAIL, PASSWORD
from notte_sdk import NotteClient
from pydantic import BaseModel
class LinkedInConversation(BaseModel):
recipient: str
messages: list[str]
client = NotteClient()
vault = client.Vault(vault_id="")
with client.Session(vault_id=vault.vault_id) as session:
# Log in: the vault replaces the EMAIL / PASSWORD placeholders with your real credentials
session.execute(type="goto", url="https://www.linkedin.com/login")
session.execute(type="form_fill", value={"email": EMAIL, "current_password": PASSWORD})
session.execute(type="click", selector="button[type='submit']")
# Navigate to your messages and extract the most recent conversation
session.execute(type="goto", url="https://www.linkedin.com/messaging/")
conversation = session.scrape(
instructions="Extract the last 10 messages from my most recent conversation",
response_format=LinkedInConversation,
)
print(conversation)
```
Design the schema around the actual page content. Make fields optional when the source may omit them; request only data you need.
```python schema_design.py theme={null}
from pydantic import BaseModel
class Product(BaseModel):
product_url: str
name: str
price: float | None = None
description: str | None = None
image_url: str | None = None
class ProductList(BaseModel):
products: list[Product]
```
## Local Python extraction configuration
Install `notte[llm]` and set `OPENROUTER_API_KEY` for local structured extraction.
The default model is `google/gemini-3.5-flash-lite` at `https://openrouter.ai/api/v1`.
Cloud sessions use the platform's server-side extraction configuration.
For another OpenAI-compatible endpoint, set `NOTTE_LLM_API_KEY` and use the endpoint's own model ID:
* OpenAI: `NOTTE_LLM_BASE_URL=https://api.openai.com/v1`, `NOTTE_LLM_MODEL=gpt-4o`.
* Gemini: `NOTTE_LLM_BASE_URL=https://generativelanguage.googleapis.com/v1beta/openai`, `NOTTE_LLM_MODEL=gemini-3.5-flash-lite`.
* Ollama/custom: `NOTTE_LLM_BASE_URL=http://localhost:11434/v1`, `NOTTE_LLM_MODEL` set to your local model, and `NOTTE_LLM_API_KEY=ollama` for an unauthenticated local server. Ollama is documented but has not been live-tested.
`NOTTE_LLM_API_KEY` takes precedence over `OPENROUTER_API_KEY`; the latter is only used at the default
OpenRouter URL. Custom endpoints require an explicit model, supplied through `NOTTE_LLM_MODEL` or
`LlmExtractor(model=...)`. A model passed to `extract(..., model=...)` applies to that call only.
`NOTTE_LLM_TEMPERATURE` defaults to `0.0`, `NOTTE_LLM_NB_RETRIES_STRUCTURED_OUTPUT` to `2`, and
`NOTTE_LLM_CONTEXT_TOKENS` to `128000`. Document clipping estimates four characters per token.
The client uses a 60-second HTTP timeout and retries transient errors after 2 and 5 seconds.
Typed extraction tries strict schemas, then JSON-object output with the schema in the prompt;
if the endpoint rejects JSON-object mode as well, the schema stays in the prompt alone.
# Search
Source: https://docs.notte.cc/concepts/search
Search the web and get URLs, snippets, or a sourced answer
Read [the Notte skill](https://notte.cc/skill.md) and follow its setup instructions when setting up Notte or building browser automation. This is the shared source for CLI setup, authentication, and the browser-to-code workflow.
Search looks up the public web and returns ranked results, a sourced answer, or structured output. Use it when you need links and context before you [Fetch](/concepts/scraping) a page.
The REST endpoint is `POST /search`. See the [request parameters](#parameters) below.
## Quick start
```bash theme={null}
curl -X POST https://api.notte.cc/search \
-H "Authorization: Bearer $NOTTE_API_KEY" \
-H "Content-Type: application/json" \
-d '{"q": "notte browser automation", "depth": "standard", "outputType": "searchResults"}'
```
```python search.py theme={null}
import os
import requests
response = requests.post(
"https://api.notte.cc/search",
headers={"Authorization": f"Bearer {os.environ['NOTTE_API_KEY']}"},
json={
"q": "notte browser automation",
"depth": "standard",
"outputType": "searchResults",
},
)
response.raise_for_status()
print(response.json())
```
## Parameters
The search query.
How thorough the search is: `standard`, `fast`, or `deep`.
What to return:
* `searchResults`: ranked URLs with name and content snippets
* `sourcedAnswer`: a written answer plus source URLs
* `structured`: structured output when you also pass a schema
Extra fields are forwarded (for example `maxResults` or `includeDomains`).
## Response shapes
`searchResults` includes a `results` array. Each item has `url`, `name`, and `content`.
`sourcedAnswer` includes `answer` (string) and `sources` (list of supporting URLs).
# Browser Sessions
Source: https://docs.notte.cc/concepts/sessions
Cloud-hosted browser instances for automation
Read [the Notte skill](https://notte.cc/skill.md) and follow its setup instructions when setting up Notte or building browser automation. This is the shared source for CLI setup, authentication, and the browser-to-code workflow.
Sessions are isolated browser instances running in the cloud that you can control programmatically. They are managed, scalable, and equipped with anti-detection, proxies, and captcha solving out of the box.
Every operation in Notte, whether you're running an AI agent, scraping data, or automating a workflow, happens within a session.
## Quick Start
Create a session and start automating:
```python Python theme={null}
import os
from notte_sdk import NotteClient
from playwright.sync_api import sync_playwright
client = NotteClient()
with client.Session(idle_timeout_minutes=2) as session:
cdp_url = session.cdp_url()
with sync_playwright() as p:
browser = p.chromium.connect_over_cdp(cdp_url)
try:
page = browser.contexts[0].pages[0]
page.goto("https://www.google.com")
page.screenshot(path=os.environ.get("NOTTE_SCREENSHOT_PATH", "screenshot.png"))
finally:
browser.close()
```
```typescript TypeScript theme={null}
import { chromium } from 'playwright-core';
import { NotteClient } from 'notte-sdk';
const notte = new NotteClient();
await notte.Session({ idle_timeout_minutes: 2 }).use(async (session) => {
const status = await session.status();
const cdpUrl = status.cdp_url;
if (!cdpUrl) {
throw new Error('Session did not return a cdp_url');
}
const browser = await chromium.connectOverCDP(cdpUrl);
try {
const page = browser.contexts()[0].pages()[0];
await page.goto('https://www.google.com');
await page.screenshot({ path: process.env.NOTTE_SCREENSHOT_PATH || 'screenshot.png' });
} finally {
await browser.close();
}
});
```
We strongly recommend using the `with` statement (context manager) to ensure sessions are automatically stopped when done. This prevents orphaned sessions and unexpected costs. See [Session Lifecycle](/features/sessions/lifecycle) for manual management options.
## Core Operations
Sessions provide three methods for interacting with the browser:
| Method | Purpose |
| - | - |
| `observe()` | Get the current page state and available actions |
| `execute()` | Perform an action on the page |
| `scrape()` | Extract structured data from the page |
```python Python theme={null}
from notte_sdk import NotteClient
client = NotteClient()
with client.Session() as page:
url = "https://www.linkedin.com/"
# observe page and take a step
page.execute(type="goto", url=url)
actions = page.observe(instructions="click 'jobs'")
res = page.execute(actions[0])
print(res.message)
# another one
actions = page.observe(instructions="dismiss the sign in check")
res = page.execute(actions[0])
print(res.message)
```
```typescript TypeScript theme={null}
import { NotteClient } from 'notte-sdk';
const client = new NotteClient({
apiKey: process.env.NOTTE_API_KEY,
});
await client.Session().use(async (page) => {
const url = 'https://www.linkedin.com/';
// Observe page and take a step
await page.execute({ type: 'goto', url });
let actions = await page.observe({ instructions: "click 'jobs'" });
let result = await page.execute(actions[0]);
console.log(result.message);
// Another one
actions = await page.observe({ instructions: 'dismiss the sign in check' });
result = await page.execute(actions[0]);
console.log(result.message);
});
```
## Session Capabilities
Sessions come with powerful built-in capabilities:
Route traffic through residential proxies for geo-targeting
Automatically solve reCAPTCHA and hCaptcha
Evade bot detection with fingerprint randomization
Record sessions for debugging and replay
Watch sessions execute in real-time
Persist browser state across sessions
## Next Steps
Create, manage, and stop sessions
All configuration options
Complete action reference
Use Playwright via CDP
# Secret Vaults
Source: https://docs.notte.cc/concepts/vaults
Enterprise-grade credential management for your Sessions
Read [the Notte skill](https://notte.cc/skill.md) and follow its setup instructions when setting up Notte or building browser automation. This is the shared source for CLI setup, authentication, and the browser-to-code workflow.
## Overview
Notte provides a secure vault system that lets sessions authenticate using credential placeholders. Secrets are encrypted at rest and in transit. During supported credential-fill actions, the vault substitutes real credentials for placeholders outside the model context. The target website receives the credentials needed to authenticate. Treat browser access, screenshots, page content, and downloaded files as sensitive; placeholder substitution is not a guarantee that a website cannot reveal a submitted value.
## Secure your credentials
* **Encryption**: Secrets are encrypted at rest and in transit.
* **Credential substitution**: Supported fill actions resolve placeholders outside the model context.
* **Access control**: Authorized API clients and sessions can use the vault.
* **2FA Support**: Support for two-factor authentication credentials.
## How it works
The Vault acts as an intermediary between your code and the browser session.
Attach a vault to a session with `client.Session(vault_id=...)`, then fill forms with placeholder values (`EMAIL`, `PASSWORD`, `MFA`, ... from `notte_core.credentials`).
When a `fill` or `form_fill` action contains a placeholder, the vault takes over the execution and replaces the dummy value with the actual
credential for the current website, directly inside the browser.
This avoids including the real credential in the placeholder fill action.
## Vault Management
### Python SDK
The following snippet shows how to manage your vault using the Notte Python SDK.
```python session_with_vault.py theme={null}
from notte_core.credentials import EMAIL, PASSWORD
from notte_sdk import NotteClient
client = NotteClient()
# Get your vault id from the Notte dashboard
vault = client.Vault(vault_id="my_vault_id")
# Add your credentials securely
vault.add_credentials(
url="https://github.com/",
email="my_cool_email@gmail.com",
password="my_cool_password",
)
# Attach the vault to a session: EMAIL / PASSWORD placeholders are replaced
# by the real credentials inside the browser, never in your code
with client.Session(vault_id=vault.vault_id) as session:
session.execute(type="goto", url="https://github.com/login")
session.execute(type="form_fill", value={"email": EMAIL, "current_password": PASSWORD})
```
## Handling credentials
Use vault placeholders instead of putting real passwords in prompts or source code. Credential substitution happens outside the model context, but the browser still interacts with the destination website. Avoid logging secrets and review what your automation captures or shares.
# Creating Functions
Source: https://docs.notte.cc/features/functions/creating
Write and deploy browser automation Functions
Read [the Notte skill](https://notte.cc/skill.md) and follow its setup instructions when setting up Notte or building browser automation. This is the shared source for CLI setup, authentication, and the browser-to-code workflow.
Learn how to write Function scripts and deploy them to Notte's serverless infrastructure.
## Writing a Function
Functions are Python scripts with a `run()` function that serves as the entry point.
### Basic Function
The minimal Function structure:
```python hello_function.py theme={null}
def run():
"""A simple function that returns a greeting."""
return "Hello from Notte Functions!"
```
### Function with Parameters
Accept input parameters:
```python greet_function.py theme={null}
def run(name: str, greeting: str = "Hello"):
"""
Greet someone by name.
Args:
name: The person's name
greeting: The greeting word (default: "Hello")
Returns:
A personalized greeting
"""
return f"{greeting}, {name}!"
```
### Browser Automation Function
Use Notte SDK for browser automation:
```python scraper_function.py theme={null}
from notte_sdk import NotteClient
def run(url: str, selector: str):
"""
Scrape data from a website.
Args:
url: The website URL
selector: CSS selector for target element
Returns:
Extracted data
"""
client = NotteClient()
with client.Session() as session:
session.execute(type="goto", url=url)
data = session.scrape(instructions=f"Extract content from {selector}")
return {"url": url, "data": data}
```
## Deploying Functions
### Via SDK
Deploy from Python:
```python deploy_sdk.py theme={null}
from notte_sdk import NotteClient
client = NotteClient()
# Deploy function
function = client.Function(path="scraper_function.py", name="Website Scraper", description="Scrapes data from websites")
print(f"Function deployed: {function.function_id}")
print(f"Version: {function.response.latest_version}")
```
```typescript deploy_sdk.ts theme={null}
import { NotteClient } from 'notte-sdk';
const client = new NotteClient();
// Deploy function
const fn = client.NotteFunction({
path: 'scraper_function.py',
name: 'Website Scraper',
description: 'Scrapes data from websites',
});
const response = await fn.get();
console.log(`Function deployed: ${response.function_id}`);
console.log(`Version: ${response.latest_version}`);
```
### Deployment Options
```python deployment_options.py theme={null}
from notte_sdk import NotteClient
client = NotteClient()
function = client.Function(
path="my_function.py",
name="My Function", # Display name
description="What this function does", # Description
shared=False, # Private by default
)
```
```typescript deployment_options.ts theme={null}
import { NotteClient } from 'notte-sdk';
const client = new NotteClient();
const fn = client.NotteFunction({
path: 'my_function.py',
name: 'My Function', // Display name
description: 'What this function does', // Description
shared: false, // Private by default
});
```
**Parameters:**
* `path` (str, required): Path to your Python file
* `name` (str, optional): Function display name
* `description` (str, optional): What the function does
* `shared` (bool, default=False): Whether function is publicly accessible
## Function Requirements
### The Handler
Must have a `run()` function:
```python handler_examples.py theme={null}
# Correct
def run(param1, param2):
return "result"
# Wrong - different name
def execute(param1, param2):
return "result"
# Wrong - no function
result = perform_task()
```
### Dependencies
Import the Notte SDK, the standard library, and common third-party libraries such as `requests` and `pydantic`:
```python dependencies.py theme={null}
# Standard library
import json
import re
from datetime import datetime
# Common third-party libraries
import requests
from pydantic import BaseModel
# Notte SDK
from notte_sdk import NotteClient
def run():
# Your code
pass
```
Imports are checked when you upload the code. Standard library modules that give process, filesystem, or network-socket access (for example `os`, `subprocess`, `pathlib`, `socket`) are rejected by default, as are packages that are not available in the runtime. The upload error lists what is allowed.
### Return Values
Return JSON-serializable data:
```python return_values.py theme={null}
def valid_returns():
# Valid return types (JSON serializable)
return "string"
return 123
return {"key": "value"}
return ["item1", "item2"]
return None
def invalid_returns():
# Invalid returns (not JSON serializable)
# return datetime.now() # datetime not serializable
# return lambda x: x # functions not serializable
pass
```
## Parameter Types
### Supported Types
```python parameter_types.py theme={null}
def run(
text: str, # String
number: int, # Integer
decimal: float, # Float
flag: bool, # Boolean
items: list, # List
data: dict, # Dictionary
optional: str | None = None, # Optional
with_default: int = 10, # Default value
):
pass
```
### Type Validation
Type hints document what each parameter expects. They are not enforced, so convert or validate values yourself:
```python type_validation.py theme={null}
def run(count: int):
# Type hints document the expected input but are not enforced:
# convert explicitly if the caller may send a string
count = int(count)
for i in range(count):
print(i)
```
### Complex Types
Structured parameters arrive as plain JSON values (dicts and lists). Validate them with Pydantic inside `run()`:
```python complex_types.py theme={null}
from notte_sdk import NotteClient
from pydantic import BaseModel
class SearchParams(BaseModel):
url: str
query: str
max_results: int = 10
def run(params: dict):
# Variables arrive as plain JSON values: validate them yourself
search = SearchParams.model_validate(params)
client = NotteClient()
# Use search.url, search.query, etc.
```
## Secrets
Store API keys and tokens as Function Secrets, either in the [Console](https://console.notte.cc) under **Settings > Function Secrets**, or with `POST /secrets` using the `function_env` namespace (`client.secrets.store(...)` in TypeScript). Every run of your functions receives them as environment variables:
```python read_secrets.py theme={null}
import os
def run(url: str):
# Function secrets are available as environment variables during the run
api_key = os.environ.get("MY_API_KEY")
webhook_url = os.environ.get("WEBHOOK_URL")
if not api_key:
return {"error": "MY_API_KEY is not configured"}
return {"url": url, "webhook_configured": webhook_url is not None}
```
Reading environment variables needs the `os` module, which the default code check rejects. Upload code that reads secrets with the check relaxed: `function.update(path="read_secrets.py", restricted=False)` in Python, or `fn.update({ path: './read_secrets.py', restricted: false })` in TypeScript.
## Error Handling
### Graceful Errors
Return error information in results:
```python graceful_errors.py theme={null}
from notte_sdk import NotteClient
def run(url: str):
try:
client = NotteClient()
with client.Session() as session:
session.execute(type="goto", url=url)
data = session.scrape()
return {"success": True, "data": data}
except Exception as e:
return {"success": False, "error": str(e), "error_type": type(e).__name__}
```
### Raising Exceptions
Let Functions fail explicitly:
```python raising_exceptions.py theme={null}
def run(url: str):
if not url.startswith("https://"):
raise ValueError("URL must use HTTPS")
# Continue with automation
```
## Testing Locally
### Test Before Deploying
Run your function locally:
```python test_locally.py theme={null}
# Define your function (from your scraper_function.py)
def run(url: str, selector: str) -> dict:
# Your scraping logic here
return {"url": url, "selector": selector}
# Test with sample parameters
result = run(url="https://example.com", selector=".content")
print(result)
```
## Best Practices
### 1. Document Parameters
Use clear docstrings:
```python document_params.py theme={null}
def run(url: str, max_retries: int = 3):
"""
Fetch data from a website with retries.
Args:
url: The website URL to scrape
max_retries: Number of retry attempts on failure (default: 3)
Returns:
Dict with 'success' (bool) and 'data' (any) keys
"""
pass
```
### 2. Return Structured Data
Use consistent return formats:
```python return_structured.py theme={null}
from datetime import datetime
def scrape_url(url: str) -> dict:
return {"scraped": url}
def run(url: str):
try:
# Perform automation
data = scrape_url(url)
return {"success": True, "data": data, "url": url, "timestamp": datetime.now().isoformat()}
except Exception as e:
return {"success": False, "error": str(e), "url": url}
```
### 3. Add Logging
Log key steps for debugging:
```python add_logging.py theme={null}
from loguru import logger
from notte_sdk import NotteClient
def run(url: str):
logger.info(f"Starting scrape of {url}")
client = NotteClient()
with client.Session() as session:
logger.info("Session created")
session.execute(type="goto", url=url)
logger.info("Page loaded")
data = session.scrape()
logger.info(f"Extracted {len(data)} characters of markdown")
return data
```
### 4. Set Timeouts
Bound how long each browser session can stay open, so a stuck page does not keep the run alive:
```python set_timeouts.py theme={null}
from notte_sdk import NotteClient
def run(url: str):
client = NotteClient()
# Close the browser session after 5 idle minutes and cap it at 15 minutes
with client.Session(idle_timeout_minutes=5, max_duration_minutes=15) as session:
session.execute(type="goto", url=url)
data = session.scrape()
return data
```
### 5. Validate Inputs
Check parameters before processing:
```python validate_inputs.py theme={null}
def run(url: str, count: int):
# Validate inputs
if not url.startswith("http"):
return {"error": "Invalid URL format"}
if count < 1 or count > 100:
return {"error": "Count must be between 1 and 100"}
# Proceed with automation
pass
```
## Examples
### Simple Scraper
```python simple_scraper.py theme={null}
from notte_sdk import NotteClient
def run(url: str):
"""Scrape page content."""
client = NotteClient()
with client.Session() as session:
session.execute(type="goto", url=url)
content = session.scrape()
return content
```
### Form Submission
```python form_submission.py theme={null}
from notte_sdk import NotteClient
def run(form_url: str, name: str, email: str, message: str):
"""Submit a contact form."""
client = NotteClient()
with client.Session() as session:
session.execute(type="goto", url=form_url)
session.execute(type="fill", id="name", value=name)
session.execute(type="fill", id="email", value=email)
session.execute(type="fill", id="message", value=message)
session.execute(type="click", selector="button[type='submit']")
return {"status": "submitted"}
```
### Data Extraction
```python structured_extraction.py theme={null}
from notte_sdk import NotteClient
from pydantic import BaseModel
class Product(BaseModel):
name: str
price: float
in_stock: bool
def run(product_url: str):
"""Extract structured product data."""
client = NotteClient()
with client.Session() as session:
session.execute(type="goto", url=product_url)
product = session.scrape(response_format=Product, instructions="Extract product details")
return product.model_dump()
```
## Next Steps
Learn how to call your Functions
Schedule Functions to run automatically
Update and manage Functions
Back to Functions overview
# Function Invocations
Source: https://docs.notte.cc/features/functions/invocations
Call Functions via API, SDK, cURL, or webhooks
Read [the Notte skill](https://notte.cc/skill.md) and follow its setup instructions when setting up Notte or building browser automation. This is the shared source for CLI setup, authentication, and the browser-to-code workflow.
Functions can be invoked through the Python or TypeScript SDK, HTTP API, cURL commands, or as webhook endpoints.
## Via Python SDK
The simplest way to call a Function:
```python invoke_function.py theme={null}
from notte_sdk import NotteClient
client = NotteClient()
# Get function by ID
function = client.Function(function_id=function_id)
# Run function with parameters
result = function.run(url="https://example.com", search_query="laptop")
print(result.result) # Access the return value
print(result.status) # "closed" or "failed"
print(result.session_id) # Session ID if created
```
```typescript invoke_function.ts theme={null}
import { NotteClient } from 'notte-sdk';
const client = new NotteClient();
// Get function by ID
const fn = client.NotteFunction({ function_id: functionId });
// Run function with parameters
const result = await fn.run({ url: 'https://example.com', search_query: 'laptop' });
console.log(result.result); // Access the return value
console.log(result.status); // "closed" or "failed"
console.log(result.session_id); // Session ID if created
```
### Run Parameters
```python run_parameters.py theme={null}
from notte_sdk import NotteClient
client = NotteClient()
function = client.Function(function_id="func_abc123")
result = function.run(
# Your function parameters (passed as variables)
url="https://example.com",
query="search term",
# Execution options
stream=True, # Stream logs in real-time (default: True)
timeout=300, # Timeout in seconds (default: 300)
raise_on_failure=True, # Raise exception on failure (default: True)
)
```
### Handling Results
```python handling_results.py theme={null}
result = function.run(url="https://example.com")
# Check status
if result.status == "closed":
print("Success!")
print(result.result) # Function return value
elif result.status == "failed":
print("Function failed")
print(result.result) # Error message
# Access metadata
print(f"Function ID: {result.function_id}")
print(f"Run ID: {result.function_run_id}")
print(f"Session ID: {result.session_id}")
```
```typescript handling_results.ts theme={null}
const result = await fn.run({ url: 'https://example.com' });
// Check status
if (result.status === 'closed') {
console.log('Success!');
console.log(result.result); // Function return value
} else if (result.status === 'failed') {
console.log('Function failed');
console.log(result.result); // Error message
}
// Access metadata
console.log(`Function ID: ${result.function_id}`);
console.log(`Run ID: ${result.function_run_id}`);
console.log(`Session ID: ${result.session_id}`);
```
## Via HTTP API
Call Functions as HTTP endpoints. Send your API key both as `Authorization: Bearer` and in the `x-notte-api-key` header. Set `"stream": false` to get a single JSON response instead of a log stream:
### POST Request
```http HTTP theme={null}
POST https://api.notte.cc/functions/{function_id}/runs/start
Authorization: Bearer YOUR_API_KEY
x-notte-api-key: YOUR_API_KEY
Content-Type: application/json
{
"function_id": "function_abc123",
"variables": {
"url": "https://example.com",
"query": "laptop"
},
"stream": false
}
```
### Response
```json JSON theme={null}
{
"function_id": "function_abc123",
"function_run_id": "run_xyz789",
"session_id": "session_456",
"result": {
"data": ["result1", "result2"],
"count": 2
},
"status": "closed"
}
```
## Via cURL
### Basic cURL Request
```bash cURL theme={null}
curl -X POST https://api.notte.cc/functions/function_abc123/runs/start \
-H "Authorization: Bearer YOUR_API_KEY" \
-H "x-notte-api-key: YOUR_API_KEY" \
-H "Content-Type: application/json" \
-d '{
"function_id": "function_abc123",
"variables": {
"url": "https://example.com",
"search_query": "laptop"
}
}'
```
### Generate cURL Command
Get the exact cURL command for your Function:
```python Python theme={null}
from notte_sdk import NotteClient
client = NotteClient()
function = client.Function(function_id="func_abc123")
# Generate cURL command
curl_command = function.get_curl(url="https://example.com", search_query="laptop")
print(curl_command)
```
Output:
```bash Output theme={null}
curl --location 'https://api.notte.cc/functions/function_abc123/runs/start' \
--header 'x-notte-api-key: YOUR_API_KEY' \
--header 'Content-Type: application/json' \
--header 'Authorization: Bearer YOUR_API_KEY' \
--data '{
"function_id": "function_abc123",
"variables": {
"url": "https://example.com",
"search_query": "laptop"
}
}'
```
## Via JavaScript/Node.js
Call from JavaScript applications:
```typescript TypeScript theme={null}
// Using fetch
const response = await fetch(
'https://api.notte.cc/functions/function_abc123/runs/start',
{
method: 'POST',
headers: {
'Authorization': 'Bearer YOUR_API_KEY',
'x-notte-api-key': 'YOUR_API_KEY',
'Content-Type': 'application/json'
},
body: JSON.stringify({
function_id: 'function_abc123',
variables: {
url: 'https://example.com',
search_query: 'laptop'
},
stream: false
})
}
);
const result = await response.json();
console.log(result);
```
### With Axios
```typescript TypeScript theme={null}
const axios = require('axios');
const result = await axios.post(
'https://api.notte.cc/functions/function_abc123/runs/start',
{
function_id: 'function_abc123',
variables: {
url: 'https://example.com',
search_query: 'laptop'
},
stream: false
},
{
headers: {
'Authorization': 'Bearer YOUR_API_KEY',
'x-notte-api-key': 'YOUR_API_KEY',
'Content-Type': 'application/json'
}
}
);
console.log(result.data);
```
## Streaming Logs
Watch function execution in real-time:
### Via SDK
```python stream_sdk.py theme={null}
# Stream logs while running
result = function.run(
url="https://example.com",
stream=True, # Logs printed to console
)
```
```typescript stream_sdk.ts theme={null}
// Stream logs while waiting for the final result.
const result = await fn.run({ url: 'https://example.com' }, { stream: true });
```
### Via HTTP (Server-Sent Events)
```bash cURL theme={null}
curl -X POST https://api.notte.cc/functions/function_abc123/runs/start \
-H "Authorization: Bearer YOUR_API_KEY" \
-H "x-notte-api-key: YOUR_API_KEY" \
-H "Content-Type: application/json" \
-d '{
"function_id": "function_abc123",
"variables": {"url": "https://example.com"},
"stream": true
}' \
--no-buffer
```
Response streams:
```text Output theme={null}
data: {"type": "log", "message": "Starting function..."}
data: {"type": "session_start", "message": "session_456"}
data: {"type": "log", "message": "Navigation complete"}
data: {"type": "result", "message": "{\"function_id\": \"...\", ...}"}
```
## Async Invocation
### Create and Start Separately
Use Python's `function.create_run()` or TypeScript's `fn.createRun()` only when you need a run ID before execution begins.
It creates the run record only. Pass that ID to `function.run(function_run_id=...)`
or `fn.run(variables, { functionRunId })` to execute it. Omitting the ID automatically creates a new run.
Set `NOTTE_FUNCTION_ID` to your deployed function's ID before running these examples:
```bash theme={null}
export NOTTE_FUNCTION_ID=your_function_id
```
```python async_create_start.py theme={null}
import os
from notte_sdk import NotteClient
client = NotteClient()
function = client.Function(os.environ["NOTTE_FUNCTION_ID"])
# Optional: create a run record when you need its ID before execution.
created = function.create_run()
run_id = created.function_run_id
print(f"Run created: {run_id}")
# Execute that run and wait for its result.
result = function.run(function_run_id=run_id, url="https://example.com")
print(result.result)
run = function.get_run(run_id)
print(run.status)
```
```typescript async_create_start.ts theme={null}
import { NotteClient } from 'notte-sdk';
const client = new NotteClient();
const fn = client.NotteFunction({ function_id: process.env.NOTTE_FUNCTION_ID! });
// Optional: create a run record when you need its ID before execution.
const created = await fn.createRun();
console.log(`Run created: ${created.function_run_id}`);
// Execute that run and wait for its result. Omit functionRunId to create a new run.
const result = await fn.run(
{ url: 'https://example.com' },
{ functionRunId: created.function_run_id },
);
console.log(result.result);
const run = await fn.getRun(created.function_run_id);
console.log(run.status);
```
### Run Without Blocking the Event Loop
`function.run()` is synchronous and waits for completion. `stream=True` (the default)
prints live logs; `stream=False` receives the final JSON response without live logs.
Disabling streaming does not make standard-runtime execution non-blocking.
Use `asyncio.to_thread()` to keep your application's event loop available:
```python async_run.py theme={null}
import asyncio
from notte_sdk import NotteClient
async def main():
function = NotteClient().Function("function_abc123")
created = await asyncio.to_thread(function.create_run)
pending = asyncio.create_task(
asyncio.to_thread(
function.run,
function_run_id=created.function_run_id,
url="https://example.com",
)
)
# Other async work can run while execution is in progress.
print(f"Run ID: {created.function_run_id}")
result = await pending
print(result.result)
asyncio.run(main())
```
Keep the application running until the task finishes. This runs the synchronous
SDK call in a local thread; it does not create a detached server-side job.
### Check Run Status
`function.get_run(run_id)` fetches the current status and result without waiting
for execution to finish. From async code, use `await asyncio.to_thread(function.get_run, run_id)`.
In TypeScript, use `await fn.getRun(runId)`; `fn.retrieve(runId)` remains a compatible alias.
```python check_run_status.py theme={null}
run_status = function.get_run(run_id)
print(f"Status: {run_status.status}") # "active", "closed", "failed"
print(f"Result: {run_status.result}")
```
```typescript check_run_status.ts theme={null}
const runStatus = await fn.getRun(runId);
console.log(`Status: ${runStatus.status}`); // "active", "closed", "failed"
console.log(`Result: ${runStatus.result}`);
```
### Stop a Running Function
```python stop_running.py theme={null}
function.stop_run(run_id)
```
## Webhook Integration
### As Webhook Endpoint
Use Functions as webhook receivers:
```python webhook_handler.py theme={null}
def run(event_type: str, data: dict):
"""Handle webhook events."""
if event_type == "order.created":
# Process new order
process_order(data)
elif event_type == "user.signup":
# Welcome new user
send_welcome_email(data)
return {"status": "processed", "event": event_type}
```
Configure webhook in external service:
```
Webhook URL: https://api.notte.cc/functions/function_abc123/runs/start
Method: POST
Headers:
Authorization: Bearer YOUR_API_KEY
x-notte-api-key: YOUR_API_KEY
```
### Testing Webhooks
Test webhook locally:
```bash cURL theme={null}
curl -X POST https://api.notte.cc/functions/function_abc123/runs/start \
-H "Authorization: Bearer YOUR_API_KEY" \
-H "x-notte-api-key: YOUR_API_KEY" \
-H "Content-Type: application/json" \
-d '{
"function_id": "function_abc123",
"variables": {
"event_type": "order.created",
"data": {"order_id": "123", "amount": 99.99}
}
}'
```
## Error Handling
### SDK Error Handling
```python sdk_error_handling.py theme={null}
from notte_sdk import NotteClient
client = NotteClient()
function = client.Function(function_id="func_abc123")
try:
result = function.run(url="https://example.com")
if result.status == "failed":
print(f"Function failed: {result.result}")
except Exception as e:
print(f"Unexpected error: {e}")
```
### Disable Raise on Failure
```python disable_raise.py theme={null}
# Inspect a failed run without raising an exception.
result = function.run(url="https://example.com", fail=True, raise_on_failure=False)
if result.status == "failed":
print(f"Function failed: {result.result}")
else:
print(f"Success: {result.result}")
```
```typescript disable_raise.ts theme={null}
// Inspect a failed run without throwing an exception.
const result = await fn.run(
{ url: 'https://example.com', fail: true },
{ raiseOnFailure: false },
);
if (result.status === 'failed') {
console.log(`Function failed: ${result.result}`);
} else {
console.log(`Success: ${result.result}`);
}
```
### HTTP Error Handling
```python Python theme={null}
import requests
response = requests.post(
"https://api.notte.cc/functions/function_abc123/runs/start",
headers={"Authorization": "Bearer YOUR_API_KEY", "x-notte-api-key": "YOUR_API_KEY"},
json={"function_id": "function_abc123", "variables": {"url": "https://example.com"}, "stream": False},
)
if response.status_code == 200:
result = response.json()
print(result)
else:
print(f"Error {response.status_code}: {response.text}")
```
## Batch Invocations
### Run Multiple Functions
```python batch_invocation.py theme={null}
from concurrent.futures import ThreadPoolExecutor
from notte_sdk import NotteClient
client = NotteClient()
function = client.Function(function_id=function_id)
urls = ["https://site1.com", "https://site2.com", "https://site3.com"]
def invoke_function(url):
return function.run(url=url)
# Run in parallel
with ThreadPoolExecutor(max_workers=3) as executor:
results = list(executor.map(invoke_function, urls))
for result in results:
print(result.result)
```
```typescript batch_invocation.ts theme={null}
import { NotteClient } from 'notte-sdk';
const client = new NotteClient();
const fn = client.NotteFunction({ function_id: functionId });
const urls = ['https://site1.com', 'https://site2.com', 'https://site3.com'];
async function invokeFunction(url: string) {
return fn.run({ url });
}
// Run in parallel
const results = await Promise.all(urls.map(invokeFunction));
for (const result of results) {
console.log(result.result);
}
```
### Sequential Invocations
```python sequential.py theme={null}
results = []
for url in urls:
result = function.run(url=url)
results.append(result.result)
print(results)
```
```typescript sequential.ts theme={null}
const results: unknown[] = [];
for (const url of urls) {
const result = await fn.run({ url });
results.push(result.result);
}
console.log(results);
```
## Cloud Execution
Functions always run on Notte's cloud infrastructure:
```python cloud_execution.py theme={null}
# Runs on Notte infrastructure
result = function.run(url="https://example.com")
```
```typescript cloud_execution.ts theme={null}
// Run on Notte infrastructure (the default).
const result = await fn.run({ url: 'https://example.com' });
```
**Advantages:**
* Scalable
* No local resources needed
* Built-in logging
* Session replays available
To iterate on a function before deploying it, call its `run` handler directly from Python (see [Testing Locally](/features/functions/creating#testing-locally)).
## Rate Limits
### Account Limits
Functions have rate limits based on your plan:
* **Free**: 10 concurrent runs
* **Pro**: 50 concurrent runs
* **Enterprise**: Custom limits
### Handling Rate Limits
```python Python theme={null}
import time
from notte_sdk import NotteClient
from notte_sdk.errors import NotteAPIError
client = NotteClient()
function = client.Function(function_id="func_abc123")
def invoke_with_retry(function, **variables):
max_retries = 3
for attempt in range(max_retries):
try:
return function.run(**variables)
except NotteAPIError as e:
if e.status_code == 429:
wait_time = 2**attempt # Exponential backoff
time.sleep(wait_time)
else:
raise
raise RuntimeError("Max retries exceeded")
result = invoke_with_retry(function, url="https://example.com")
```
## Best Practices
### 1. Use Appropriate Timeout
```python timeout_examples.py theme={null}
from notte_sdk import NotteClient
client = NotteClient()
function = client.Function(function_id=function_id)
# Stop waiting if the server sends nothing for 60 seconds (default: 300)
result = function.run(url="https://example.com", timeout=60)
# Tolerate longer quiet periods, e.g. long steps that log nothing
result = function.run(url="https://example.com", timeout=600)
```
```typescript timeout_examples.ts theme={null}
import { NotteClient } from 'notte-sdk';
const client = new NotteClient();
const fn = client.NotteFunction({ function_id: functionId });
// Stop waiting after 60 seconds in total (throws NotteTimeoutError)
let result = await fn.run({ url: 'https://example.com' }, { timeoutMs: 60_000 });
// Allow up to 10 minutes for a long task
result = await fn.run({ url: 'https://example.com' }, { timeoutMs: 600_000 });
```
### 2. Stream Logs for Debugging
```python stream_debug.py theme={null}
from notte_sdk import NotteClient
client = NotteClient()
function = client.Function(function_id="function_abc123")
# Development - stream logs
result = function.run(url="https://example.com", stream=True)
# Production - no log output (still waits for the result)
result = function.run(url="https://example.com", stream=False)
```
### 3. Handle Errors Gracefully
```python graceful_errors.py theme={null}
try:
result = function.run(url="https://example.com")
process_result(result.result)
except Exception as e:
log_error(e)
send_alert(str(e))
```
### 4. Use Variables for Dynamic Data
```python use_variables.py theme={null}
# Good - parameterized
result = function.run(url=dynamic_url, query=user_input)
# Bad - hardcoded
result = function.run() # URL hardcoded in function
```
## Next Steps
Schedule Functions automatically
Update and monitor Functions
Learn to write Functions
Full API documentation
# Function Management
Source: https://docs.notte.cc/features/functions/management
Update, version, monitor, and manage your deployed Functions
Read [the Notte skill](https://notte.cc/skill.md) and follow its setup instructions when setting up Notte or building browser automation. This is the shared source for CLI setup, authentication, and the browser-to-code workflow.
Manage the lifecycle of your Functions including updates, versioning, monitoring executions, and viewing replays.
## Viewing Functions
### List All Functions
View your deployed Functions:
```python list_functions.py theme={null} theme={null}
from notte_sdk import NotteClient
client = NotteClient()
# List all functions
functions = client.functions.list()
for func in functions.items:
print(f"ID: {func.function_id}")
print(f"Name: {func.name}")
print(f"Version: {func.latest_version}")
print(f"Created: {func.created_at}")
print("---")
```
List calls are paginated (10 items per page by default, up to 100 with `page_size`). Use `page` and the response's `has_next` to read further pages. In Python, `client.functions.list()` and `list_runs()` default to `only_active=True`.
### Get Function Details
Retrieve specific Function information:
Set `function_id` (Python) or `functionId` (Node) to your deployed Function's ID.
```python get_function_details.py theme={null} theme={null}
from notte_sdk import NotteClient
client = NotteClient()
# Get function by ID
function = client.Function(function_id=function_id)
# Access function properties
print(f"Function ID: {function.function_id}")
print(f"Name: {function.response.name}")
print(f"Description: {function.response.description}")
print(f"Latest Version: {function.response.latest_version}")
print(f"Versions: {function.response.versions}")
```
```typescript get_function_details.ts theme={null} theme={null}
import { NotteClient } from 'notte-sdk';
const client = new NotteClient();
// Get function by ID
const fn = client.NotteFunction({ function_id: functionId });
const response = await fn.get();
// Access function properties
console.log(`Function ID: ${response.function_id}`);
console.log(`Name: ${response.name}`);
console.log(`Description: ${response.description}`);
console.log(`Latest Version: ${response.latest_version}`);
console.log(`Versions: ${response.versions}`);
```
## Updating Functions
### Update Function Code
Deploy a new version of your Function:
```python update_code.py theme={null} theme={null}
from notte_sdk import NotteClient
client = NotteClient()
function = client.Function(function_id="func_abc123")
# Update with new code
function.update(path="updated_function.py")
print(f"Updated to version: {function.response.latest_version}")
```
### Update Function Metadata
Change metadata with `PATCH /functions/{function_id}`, `client.functions.metadata_update()` in Python, or `fn.updateMetadata()` in TypeScript.
Set `function_id` / `functionId` to your function ID. For Python, also set `api_url` and `api_key`.
```python update_metadata.py theme={null} theme={null}
import requests
endpoint = f"{api_url}/functions/{function_id}"
headers = {"Authorization": f"Bearer {api_key}"}
response = requests.patch(
endpoint,
headers=headers,
json={"name": "Price monitor", "description": "Checks competitor prices"},
timeout=30,
)
response.raise_for_status()
metadata = requests.get(endpoint, headers=headers, timeout=30)
metadata.raise_for_status()
print(metadata.json()["name"])
```
```typescript update_metadata.ts theme={null} theme={null}
import { NotteClient } from 'notte-sdk';
const client = new NotteClient();
const fn = client.NotteFunction({ function_id: functionId });
await fn.updateMetadata({ name: 'Price monitor', description: 'Checks competitor prices' });
const metadata = await fn.get();
console.log(metadata.name);
```
**Note:** Code updates create new versions automatically. Previous versions remain available for rollback.
## Versioning
### Version Management
Functions automatically version on each update:
```python version_management.py theme={null} theme={null}
from notte_sdk import NotteClient
client = NotteClient()
function = client.Function(function_id=function_id)
# Get all versions
print(f"Available versions: {function.response.versions}")
# Get latest version
print(f"Latest: {function.response.latest_version}")
```
```typescript version_management.ts theme={null} theme={null}
import { NotteClient } from 'notte-sdk';
const client = new NotteClient();
const fn = client.NotteFunction({ function_id: functionId });
const response = await fn.get();
// Get all versions
console.log('Available versions:', response.versions);
// Get latest version
console.log(`Latest: ${response.latest_version}`);
```
### Roll Back to a Previous Version
Cloud runs always execute the latest version. To go back to an older one, roll back with `POST /functions/{function_id}/rollback` and a version from `versions`, or `fn.rollback({ version })` in TypeScript. The old code is redeployed as a new latest version:
```python rollback_version.py theme={null} theme={null}
import requests
from notte_sdk import NotteClient
client = NotteClient()
function = client.Function(function_id="func_abc123")
# Cloud runs always execute the latest version
result = function.run(url="https://example.com")
# To go back to an older version, roll back to it: its code is redeployed
# as a new latest version
print(function.response.versions)
response = requests.post(
f"https://api.notte.cc/functions/{function.function_id}/rollback",
headers={"Authorization": "Bearer YOUR_API_KEY"},
json={"version": function.response.versions[0]},
)
response.raise_for_status()
print(response.json()["latest_version"])
```
### Version History
Track changes across versions:
```python version_history.py theme={null} theme={null}
from notte_sdk import NotteClient
client = NotteClient()
functions = client.functions.list()
for func in functions.items:
print(f"Function: {func.name}")
print(f"Versions: {', '.join(func.versions)}")
print(f"Latest: {func.latest_version}")
```
```typescript version_history.ts theme={null} theme={null}
import { NotteClient } from 'notte-sdk';
const client = new NotteClient();
const functions = await client.functions.list();
for (const fn of functions) {
console.log(`Function: ${fn.name}`);
console.log(`Versions: ${fn.versions.join(', ')}`);
console.log(`Latest: ${fn.latest_version}`);
}
```
## Monitoring Executions
### View Run History
List recent Function executions:
Before running this example, set `function_id` (Python) or `functionId` (Node)
to the ID of the Function whose history you want to inspect. Pass `only_active=False` to include finished and failed runs.
```python view_run_history.py theme={null} theme={null}
from notte_sdk import NotteClient
client = NotteClient()
# Get recent runs
runs = client.functions.list_runs(
function_id=function_id,
only_active=False, # Include completed runs
)
for run in runs.items:
print(f"Run ID: {run.function_run_id}")
print(f"Status: {run.status}")
print(f"Created: {run.created_at}")
print(f"Updated: {run.updated_at}")
print("---")
```
```typescript view_run_history.ts theme={null} theme={null}
import { NotteClient } from 'notte-sdk';
const client = new NotteClient();
// Get recent runs
const runs = await client.NotteFunction({ function_id: functionId }).runs({
only_active: false, // Include completed runs
});
for (const run of runs.items) {
console.log(`Run ID: ${run.function_run_id}`);
console.log(`Status: ${run.status}`);
console.log(`Created: ${run.created_at}`);
console.log(`Updated: ${run.updated_at}`);
console.log('---');
}
```
### Check Run Status
Monitor specific execution:
Set `function_id` and `run_id` (Python), or `functionId` and `runId` (Node),
to your Function's ID and the run ID returned by its execution.
```python check_run_status.py theme={null} theme={null}
from notte_sdk import NotteClient
client = NotteClient()
# Get run details
run_status = client.functions.run_get_metadata(function_id, run_id)
print(f"Status: {run_status.status}") # "active", "closed", "failed"
print(f"Result: {run_status.result}")
print(f"Session ID: {run_status.session_id}")
```
```typescript check_run_status.ts theme={null} theme={null}
import { NotteClient } from 'notte-sdk';
const client = new NotteClient();
// Get run details
const runStatus = await client.NotteFunction({ function_id: functionId }).getRun(runId);
console.log(`Status: ${runStatus.status}`); // "active", "closed", "failed"
console.log('Result:', runStatus.result);
console.log(`Session ID: ${runStatus.session_id}`);
```
**Status values:**
* `active` - Function is currently running
* `closed` - Function completed successfully
* `failed` - Function encountered an error
### Filter Active Runs
View only running executions:
```python filter_active_runs.py theme={null} theme={null}
from notte_sdk import NotteClient
client = NotteClient()
# Get only active runs
active_runs = client.functions.list_runs(function_id, only_active=True)
print(f"Active runs: {len(active_runs.items)}")
for run in active_runs.items:
print(f"Run {run.function_run_id} - {run.status}")
```
```typescript filter_active_runs.ts theme={null} theme={null}
import { NotteClient } from 'notte-sdk';
const client = new NotteClient();
// Get only active runs
const activeRuns = await client.NotteFunction({ function_id: functionId }).runs({ only_active: true });
console.log(`Active runs: ${activeRuns.items.length}`);
for (const run of activeRuns.items) {
console.log(`Run ${run.function_run_id} - ${run.status}`);
}
```
## Viewing Replays
### Access Function Replay
Watch MP4 replay of Function execution:
```python access_replay.py theme={null} theme={null}
from notte_sdk import NotteClient
client = NotteClient()
function = client.Function(function_id="func_abc123")
# Run the function first
result = function.run(url="https://example.com")
# Get replay for this run (uses the session from the last run)
replay = function.replay()
# Download the replay
replay.download("function_replay.mp4")
```
**Replay features:**
* Video recording of browser session
* Shows all actions taken
* Useful for debugging failures
* Available after running a function
### Replay After Run
Get replay immediately after execution:
```python replay_after_run.py theme={null} theme={null}
from notte_sdk import NotteClient
client = NotteClient()
function = client.Function(function_id="func_abc123")
# Run function
result = function.run(url="https://example.com")
# Get replay for this run (automatically uses the session from the run)
replay = function.replay()
# Download the video
replay.download("execution_replay.mp4")
```
## Managing Runs
### Stop Running Function
Cancel a long-running execution:
```python stop_running.py theme={null} theme={null}
from notte_sdk import NotteClient
client = NotteClient()
# Stop specific run
client.functions.run_stop("function_abc123", "run_xyz789")
print("Function execution stopped")
```
### Monitoring Long Runs
Track execution progress:
```python monitor_runs.py theme={null} theme={null}
import time
from concurrent.futures import ThreadPoolExecutor
from notte_sdk import NotteClient
client = NotteClient()
function = client.Function(function_id=function_id)
# Create the run first so you know its ID
run_id = function.create_run().function_run_id
# run() blocks until completion; keep its exception visible to the polling thread.
with ThreadPoolExecutor(max_workers=1) as executor:
execution = executor.submit(
function.run,
function_run_id=run_id,
url="https://example.com",
stream=False,
)
# Poll status while it runs
while True:
if execution.done():
execution.result() # Propagate a rejected start instead of polling forever.
status = client.functions.run_get_metadata(function_id, run_id)
print(f"Status: {status.status}")
if status.status in ["closed", "failed"]:
print(f"Final result: {status.result}")
break
time.sleep(5) # Check every 5 seconds
execution.result()
```
```typescript monitor_runs.ts theme={null} theme={null}
import { setTimeout as sleep } from 'node:timers/promises';
import { NotteClient } from 'notte-sdk';
const client = new NotteClient();
const fn = client.NotteFunction({ function_id: functionId });
// Create the run first so you know its ID
const { function_run_id: runId } = await fn.createRun();
// Start it without awaiting: run() resolves only when the run finishes
let callError: unknown;
const execution = fn
.run({ url: 'https://example.com' }, { functionRunId: runId, stream: false })
.catch((error: unknown) => {
callError = error;
});
// Poll status while it runs
while (true) {
if (callError) {
console.log('Run failed:', callError);
break;
}
const status = await fn.getRun(runId);
console.log(`Status: ${status.status}`);
if (status.status === 'closed' || status.status === 'failed') {
console.log('Final result:', status.result);
break;
}
await sleep(5000); // Check every 5 seconds
}
await execution;
```
## Forking Functions
### Fork Shared Function
Create your own copy of a shared Function:
```python fork_function.py theme={null} theme={null}
from notte_sdk import NotteClient
client = NotteClient()
# Fork a function from marketplace or teammate
original = client.Function(function_id="func_abc123")
forked_function = original.fork()
print(f"Forked function ID: {forked_function.function_id}")
print("Original ID: func_abc123")
```
**Use cases:**
* Customize shared Functions
* Create templates from marketplace
* Copy teammate's Functions
* Experiment without affecting original
### Modify Forked Function
After forking, update as needed:
```python modify_forked.py theme={null} theme={null}
from notte_sdk import NotteClient
client = NotteClient()
# Fork function
original = client.Function(function_id="shared_function_id")
forked = original.fork()
# Update your copy
forked.update(path="my_modified_version.py")
# Run your version
result = forked.run(url="https://example.com")
```
## Downloading Functions
### Download Function Code
Retrieve Function source code:
```python download_code.py theme={null} theme={null}
from notte_sdk import NotteClient
client = NotteClient()
# Notte-managed functions also need decryption_key="..."
function = client.Function(function_id="func_abc123")
# Download function code
code = function.download()
print(code) # Function source code
# Or download directly to a file
code = function.download(path="downloaded_function.py")
```
**Requirements:**
* Only works for Functions you own or have access to
* Notte-managed functions also need their decryption key, passed as `decryption_key` when creating the Function instance
### Download for Backup
Back up all your Functions:
```python backup_functions.py theme={null} theme={null}
import os
from notte_sdk import NotteClient
client = NotteClient()
functions = client.functions.list()
# Create backup directory
os.makedirs("function_backups", exist_ok=True)
for func in functions.items:
function = client.Function(function_id=func.function_id, decryption_key="your-key")
try:
code = function.download()
filename = f"function_backups/{func.name}_{func.latest_version}.py"
with open(filename, "w") as f:
f.write(code)
print(f"Backed up: {func.name}")
except Exception as e:
print(f"Failed to backup {func.name}: {e}")
```
## Deleting Functions
### Delete Function
Remove a Function permanently:
```python delete_function.py theme={null} theme={null}
from notte_sdk import NotteClient
client = NotteClient()
function = client.Function(function_id="func_abc123")
# Delete function
function.delete()
print("Function deleted")
```
**Warning:**
* Deletion is permanent
* All versions are deleted
* Cannot be undone
## Sharing Functions
### Change sharing for an existing function
Set `shared` on the metadata operation to enable or disable its public code/fork
link without creating a function or uploading code again:
* Python: `client.functions.metadata_update(function_id, shared=True)`.
* TypeScript: `await client.functions.metadataUpdate(functionId, { shared: true })`,
or `await fn.updateMetadata({ shared: true })` for an existing function wrapper.
* HTTP: `PATCH /functions/{function_id}` with the JSON body `{"shared": true}`.
Use `False` in Python or `false` in HTTP/TypeScript to disable the fork link.
Omitting `shared`, or sending null, preserves the current setting. HTTP requires
an actual boolean, rather than the strings `"true"` or `"false"`.
```bash theme={null}
notte functions configure --function-id ID --shared
notte functions configure --function-id ID --shared=false
```
Sharing exposes source code, including literals embedded in it. Runs, results and
stored runtime secrets stay private. Sharing does not enable public execution of
the author's function. Disabling the link prevents new forks; copies already
forked into other workspaces remain usable. Share the URL
`/fork/` using the Console for the same environment.
### Create a public function
You can also choose sharing when creating a function:
```python make_public.py theme={null} theme={null}
from notte_sdk import NotteClient
client = NotteClient()
# Deploy as shared
function = client.Function(
path="my_function.py",
name="Public Scraper",
shared=True, # Other users can view and fork it
)
```
**Shared Functions:**
* ✅ Others can view and fork
* ✅ Original remains under your control
* ❌ Others cannot modify your version
### Private Functions
Keep Functions private (default):
```python private_function.py theme={null} theme={null}
from notte_sdk import NotteClient
client = NotteClient()
function = client.Function(
path="my_function.py",
name="Private Automation",
shared=False, # Private (default)
)
```
```typescript private_function.ts theme={null} theme={null}
import { NotteClient } from 'notte-sdk';
const client = new NotteClient();
const fn = client.NotteFunction({
path: 'my_function.py',
name: 'Private Automation',
shared: false, // Private (default)
});
```
## Best Practices
### 1. Version Your Functions
Update incrementally with clear changes:
```python version_backup.py theme={null} theme={null}
from notte_sdk import NotteClient
client = NotteClient()
function = client.Function(function_id="func_abc123")
# Before major update, download current version
current_code = function.download()
# Save backup
with open(f"backups/function_v{function.response.latest_version}.py", "w") as f:
f.write(current_code)
# Update function
function.update(path="new_version.py")
```
### 2. Monitor Function Health
Regular health checks:
```python function_health_check.py theme={null} theme={null}
from notte_sdk import NotteClient
client = NotteClient()
def check_function_health(function_id: str):
"""Check recent run success rate."""
runs = client.functions.list_runs(function_id, only_active=False)
total = len(runs.items)
failed = sum(1 for r in runs.items if r.status == "failed")
success_rate = ((total - failed) / total * 100) if total > 0 else 0
print(f"Success rate: {success_rate:.1f}%")
print(f"Total runs: {total}")
print(f"Failed runs: {failed}")
return success_rate
# Monitor function
check_function_health("func_abc123")
```
### 3. Clean Up Old Runs
Archive or analyze old execution data:
```python cleanup_old_runs.py theme={null} theme={null}
import json
from datetime import datetime, timedelta, timezone
from notte_sdk import NotteClient
client = NotteClient()
def archive_old_runs(function_id: str, days_old: int = 30):
"""Archive runs older than specified days."""
# Most recent page of runs (up to 100), including finished ones
runs = client.functions.list_runs(function_id, only_active=False, page_size=100)
cutoff_date = datetime.now(timezone.utc) - timedelta(days=days_old)
archive = []
for run in runs.items:
if run.created_at < cutoff_date:
run_detail = client.functions.run_get_metadata(function_id, run.function_run_id)
archive.append(
{
"run_id": run.function_run_id,
"status": run.status,
"created_at": run.created_at.isoformat(),
"result": str(run_detail.result)[:100], # First 100 chars
}
)
# Save archive
with open(f"archives/{function_id}_{datetime.now().date()}.json", "w") as f:
json.dump(archive, f, indent=2)
print(f"Archived {len(archive)} old runs")
archive_old_runs("function_abc123")
```
### 4. Document Changes
Keep change log for Function updates:
```python document_changes.py theme={null} theme={null}
"""
Function: Data Scraper
Function ID: func_abc123
Version History:
- v1 (2024-01-15): Initial release
- v2 (2024-01-20): Added error handling
- v3 (2024-01-25): Improved scraping accuracy
- v4 (2024-02-01): Added retry logic
"""
def run(url: str):
# Your function code
pass
```
### 5. Test Before Production
Validate updates before deploying:
```python test_before_prod.py theme={null} theme={null}
from notte_sdk import NotteClient
def validate_before_production():
client = NotteClient()
# Deploy the new version as a separate staging function first
staging = client.Function(path="tested_function.py", name="my-function-staging")
# Run it in the cloud against a test site
test_result = staging.run(url="https://test-site.com")
if test_result.status == "closed":
print("Test passed, ready to update production")
production = client.Function(function_id="func_abc123")
production.update(path="tested_function.py")
else:
print(f"Test failed: {test_result.result}")
# Remove the staging function once validated
staging.delete()
```
## Troubleshooting
### Function Fails to Run
Check run details for errors:
```python troubleshoot_fails.py theme={null} theme={null}
from notte_sdk import NotteClient
client = NotteClient()
function = client.Function(function_id="func_abc123")
run = client.functions.run_get_metadata("func_abc123", "run_xyz789")
if run.status == "failed":
print(f"Error: {run.result}")
# Run the function to get a replay
result = function.run(url="https://example.com", raise_on_failure=False)
replay = function.replay()
replay.download("debug_replay.mp4")
```
### Missing Secrets
Function Secrets reach your code as environment variables (see [Secrets](/features/functions/creating#secrets)). Check for them at the start of `run()`:
```python missing_env_vars.py theme={null} theme={null}
import os
def run():
# Names of the Function Secrets this code expects
required_vars = ["API_KEY", "WEBHOOK_URL"]
missing = [var for var in required_vars if not os.environ.get(var)]
if missing:
return {"error": f"Missing secrets: {', '.join(missing)}"}
# Continue with automation
```
### High Failure Rate
Analyze failure patterns:
Set `function_id` (Python) or `functionId` (Node) to your Function's ID.
Include completed runs with `only_active=False` / `only_active: false` so failed runs appear.
```python high_failure_rate.py theme={null} theme={null}
from notte_sdk import NotteClient
client = NotteClient()
runs = client.functions.list_runs(function_id, only_active=False)
failures = [r for r in runs.items if r.status == "failed"]
print(f"Failed runs: {len(failures)}/{len(runs.items)}")
# Analyze failure reasons
for run in failures[:5]: # Last 5 failures
run_detail = client.functions.run_get_metadata(function_id, run.function_run_id)
print(f"Run {run.function_run_id}:")
print(f" Error: {run_detail.result}")
print(f" Time: {run.created_at}")
```
```typescript high_failure_rate.ts theme={null} theme={null}
import { NotteClient } from 'notte-sdk';
const client = new NotteClient();
const fn = client.NotteFunction({ function_id: functionId });
const runs = await fn.runs({ only_active: false });
const failures = runs.items.filter(run => run.status === 'failed');
console.log(`Failed runs: ${failures.length}/${runs.items.length}`);
// Analyze failure reasons
for (const run of failures.slice(0, 5)) { // First 5 failures on this page
const runDetail = await fn.getRun(run.function_run_id);
console.log(`Run ${run.function_run_id}:`);
console.log(` Error: ${runDetail.result}`);
console.log(` Time: ${run.created_at}`);
}
```
## Next Steps
Learn how to write Functions
Call Functions via API or SDK
Schedule Functions with cron
Back to Functions overview
# Function Schedules
Source: https://docs.notte.cc/features/functions/schedules
Run Functions automatically on a cron schedule
Read [the Notte skill](https://notte.cc/skill.md) and follow its setup instructions when setting up Notte or building browser automation. This is the shared source for CLI setup, authentication, and the browser-to-code workflow.
Schedule a Function to run automatically, for example to collect data every morning or check a page every hour. Each scheduled run executes the latest version of the Function with the variables you saved on the schedule.
A Function has at most one schedule. You can manage it from the **Schedule** tab of the Function in the [Notte Console](https://console.notte.cc), over HTTP, or with the TypeScript SDK. The Python SDK has no schedule methods yet, so call the HTTP API from Python.
## Cron Expressions
Schedules use the AWS EventBridge cron format: **six** space-separated fields, minutes first. This is not the five-field Unix crontab format.
`minutes hours day-of-month month day-of-week year`
* Exactly one of `day-of-month` and `day-of-week` must be `?`; the other must be a real value.
* Schedules fire in UTC.
* `rate(...)` expressions such as `rate(1 hour)` are also accepted.
| Schedule | Expression |
| - | - |
| Every day at 9:00 UTC | `0 9 ? * * *` |
| Every hour | `0 * ? * * *` |
| Every 15 minutes | `0/15 * ? * * *` |
| Every Monday at 8:00 UTC | `0 8 ? * MON *` |
| Weekdays at 18:00 UTC | `0 18 ? * MON-FRI *` |
| First day of the month at midnight UTC | `0 0 1 * ? *` |
| Every hour (rate) | `rate(1 hour)` |
A five-field expression such as `0 9 * * *` is rejected.
## Variables
Every scheduled run receives the same `variables`. They must supply every parameter of `run()` that has no default value, and must not include names that `run()` does not declare. A Function without parameters can use an empty object.
## Create or Update a Schedule
Setting a schedule replaces the existing one, if any. Set `function_id` / `functionId` to your function ID. For Python, set `api_url` to your API base URL and `api_key` to your API key. The complete example creates, reads, and then removes the schedule:
```python manage_schedule.py theme={null}
import requests
endpoint = f"{api_url}/functions/{function_id}"
headers = {"Authorization": f"Bearer {api_key}"}
try:
# Create or replace the schedule. Times are UTC.
response = requests.post(
f"{endpoint}/schedule",
headers=headers,
json={"cron": "0 9 ? * * *", "variables": {"url": "https://example.com"}},
timeout=30,
)
response.raise_for_status()
# Read the schedule from the function metadata.
metadata = requests.get(endpoint, headers=headers, timeout=30)
metadata.raise_for_status()
schedule = metadata.json()
print(schedule["schedule_cron"], schedule["schedule_state"])
finally:
# Delete the schedule when it is no longer needed.
deleted = requests.delete(f"{endpoint}/schedule", headers=headers, timeout=30)
deleted.raise_for_status()
```
```typescript manage_schedule.ts theme={null}
import { NotteClient } from 'notte-sdk';
const client = new NotteClient();
const fn = client.NotteFunction({ function_id: functionId });
let schedule;
try {
// Create or replace the schedule. Times are UTC.
await fn.setSchedule({
cron: '0 9 ? * * *',
variables: { url: 'https://example.com' },
});
// Read the schedule from the function metadata.
schedule = await fn.get();
console.log(schedule.schedule_cron, schedule.schedule_state);
} finally {
// Delete the schedule when it is no longer needed.
await fn.deleteSchedule();
}
```
## Check a Schedule
The Function's metadata (`GET /functions/{function_id}`, or `fn.get()` in TypeScript) includes:
* `schedule_cron`: the expression, or `null` when the Function is not scheduled
* `schedule_variables`: the variables passed to each scheduled run
* `schedule_state`: `enabled`, `paused`, or `null` when there is no schedule
The example above reads these fields after setting the schedule.
Scheduled runs appear in the Function's run history like any other run. See [Function Management](/features/functions/management#monitoring-executions).
## Paused Schedules
Notte pauses a schedule when its runs cannot succeed without your intervention, for example when your workspace runs out of credit or the Function is no longer active. A paused schedule keeps its expression and variables but stops firing, and `schedule_state` becomes `paused`.
To resume it, fix the cause and set the schedule again with the same request as above.
## Delete a Schedule
Deleting the schedule stops future runs. The Function itself is unchanged and can still be invoked manually.
The example above deletes the schedule in `finally`, including when a metadata request fails.
## Best Practices
* Match the frequency to how often the data actually changes; every run uses browser time and credits.
* Make runs idempotent so a repeated or overlapping run does no harm.
* Return structured results and raise on failure, so failed scheduled runs show up as `failed` in the run history.
## Next Steps
Invoke Functions manually
Monitor runs and manage versions
Write schedulable Functions
Configure schedules in the Console
# Ad Blocking
Source: https://docs.notte.cc/features/sessions/ad-blocking
Built-in ad and tracker blocking for browser sessions
Read [the Notte skill](https://notte.cc/skill.md) and follow its setup instructions when setting up Notte or building browser automation. This is the shared source for CLI setup, authentication, and the browser-to-code workflow.
Ad Blocking provides built-in filtering of advertisements, trackers, and unwanted content in your browser sessions. This improves page load performance, reduces bandwidth usage, and creates cleaner pages for scraping and automation.
## Key Benefits
* **Faster page loads** - Skip downloading ads and trackers
* **Reduced bandwidth** - Lower data transfer costs
* **Cleaner content** - Easier extraction without ad clutter
* **Better reliability** - Avoid ad-related JavaScript errors
## Usage
Ad blocking is enabled by default on every Notte-hosted session, controlled by the `block_ads` session parameter (default `true`). Set `block_ads=False` to turn it off, for example when a site breaks without its ad or tracking scripts:
```python ad_blocking.py theme={null}
from notte_sdk import NotteClient
client = NotteClient()
# Ad blocking is on by default
with client.Session() as default_session:
default_session.execute(type="goto", url="https://example.com")
# Turn it off when a site needs its ads or trackers to load
with client.Session(block_ads=False) as session:
session.execute(type="goto", url="https://example.com")
```
```typescript ad_blocking.ts theme={null}
import { NotteClient } from 'notte-sdk';
const client = new NotteClient();
// Ad blocking is on by default
const defaultSession = client.Session();
await defaultSession.use(async (s) => {
await s.execute({ type: 'goto', url: 'https://example.com' });
});
// Turn it off when a site needs its ads or trackers to load
const session = client.Session({ block_ads: false });
await session.use(async (s) => {
await s.execute({ type: 'goto', url: 'https://example.com' });
});
```
`block_ads` only applies to browsers Notte launches. It has no effect when you connect an [external browser](/features/sessions/external-providers) with `cdp_url`; use your provider's own ad blocking settings instead.
## Next Steps
Explore all session options
Route traffic through residential proxies
# Browser Controls
Source: https://docs.notte.cc/features/sessions/browser-controls
Complete reference of all browser actions available in sessions
Read [the Notte skill](https://notte.cc/skill.md) and follow its setup instructions when setting up Notte or building browser automation. This is the shared source for CLI setup, authentication, and the browser-to-code workflow.
Sessions provide a comprehensive set of browser actions for interacting with web pages. All actions are executed through `session.execute()`.
## Navigation Actions
### Goto
Navigate to a URL.
```python goto.py theme={null}
from notte_sdk import NotteClient
client = NotteClient()
with client.Session() as session:
session.execute(type="goto", url="https://example.com")
```
```typescript goto.ts theme={null}
import { NotteClient } from 'notte-sdk';
const client = new NotteClient();
const session = client.Session();
await session.use(async () => {
await session.execute({ type: "goto", url: "https://example.com" });
});
```
**Parameters:**
* `url` (str): The URL to navigate to
* `wait_until` (str, optional): Navigation event to wait for: `load` (default), `domcontentloaded`, `commit` or `networkidle`. `commit` and `domcontentloaded` skip the post-load settle wait, which makes them the cheap choice when the page is only needed as an origin for `session.fetch()`
**Use for:** Opening pages, navigating between pages
***
### GotoNewTab
Open a URL in a new browser tab.
```python goto_new_tab.py theme={null}
from notte_sdk import NotteClient
client = NotteClient()
with client.Session() as session:
session.execute(type="goto_new_tab", url="https://example.com/products")
```
**Parameters:**
* `url` (str): The URL to open in a new tab
**Use for:** Opening multiple pages simultaneously, preserving context
***
### SwitchTab
Switch to a different browser tab.
```python switch_tab.py theme={null}
from notte_sdk import NotteClient
client = NotteClient()
with client.Session() as session:
# Open multiple tabs
session.execute(type="goto", url="https://example.com")
session.execute(type="goto_new_tab", url="https://example.com/products")
# Switch back to first tab
session.execute(type="switch_tab", tab_index=0)
```
**Parameters:**
* `tab_index` (int): Zero-based index of the tab to switch to
**Use for:** Managing multiple tabs, comparing content across pages
***
### GoBack
Navigate back to the previous page.
```python go_back.py theme={null}
from notte_sdk import NotteClient
client = NotteClient()
with client.Session() as session:
session.execute(type="goto", url="https://example.com")
session.execute(type="goto", url="https://example.com/products")
# Go back to homepage
session.execute(type="go_back")
```
**Use for:** Browser back button functionality, returning to previous pages
***
### GoForward
Navigate forward in browser history.
```python go_forward.py theme={null}
from notte_sdk import NotteClient
client = NotteClient()
with client.Session() as session:
session.execute(type="goto", url="https://example.com")
session.execute(type="goto", url="https://example.com/products")
session.execute(type="go_back")
# Now go forward again
session.execute(type="go_forward")
```
**Use for:** Browser forward button functionality
***
### Reload
Reload the current page.
```python reload.py theme={null}
from notte_sdk import NotteClient
client = NotteClient()
with client.Session() as session:
session.execute(type="goto", url="https://example.com")
session.execute(type="reload")
```
```typescript reload.ts theme={null}
import { NotteClient } from 'notte-sdk';
const client = new NotteClient();
const session = client.Session();
await session.use(async () => {
await session.execute({ type: "goto", url: "https://example.com" });
await session.execute({ type: "reload" });
});
```
**Use for:** Refreshing page content, retrying after errors
***
## Interaction Actions
### Click
Click on an element.
```python click.py theme={null}
from notte_sdk import NotteClient
client = NotteClient()
with client.Session() as session:
session.execute(type="goto", url="https://example.com")
# Click by CSS selector
session.execute(type="click", selector="button#submit")
# Pseudo observe output: [B1] button "Submit"
# Only use IDs that appear in your live observe() output.
session.execute(type="click", id="B1")
# Click by text selector
session.execute(type="click", selector="button:has-text('Submit')")
```
**Parameters:**
* `selector` (str): Selector for the element (CSS, XPath, or Playwright selector such as `button:has-text('Submit')`)
* `id` (str): Element ID from the latest `observe()` output (for example `B1`)
* `timeout` (int, optional): Action timeout in milliseconds
**Use for:** Clicking buttons, links, checkboxes, any clickable element
***
### Fill
Fill a text input or textarea.
```python fill.py theme={null}
from notte_sdk import NotteClient
client = NotteClient()
with client.Session() as session:
session.execute(type="goto", url="https://example.com")
# Fill by selector
session.execute(type="fill", selector="input[name='email']", value="user@example.com")
# Pseudo observe output: [I1] input "Email"
# Only use IDs that appear in your live observe() output.
session.execute(type="fill", id="I1", value="user@example.com")
# Fill by placeholder selector
session.execute(type="fill", selector="input[placeholder='Enter your email']", value="user@example.com")
```
**Parameters:**
* `selector` (str): Selector for the input
* `id` (str): Element ID from the latest `observe()` output (for example `I1`)
* `value` (str): The text to fill
* `clear_before_fill` (bool, optional): Clear the field first (default: True)
* `press_enter` (bool, optional): Press Enter after filling
**Use for:** Filling forms, search boxes, text inputs
***
### Check
Check or uncheck a checkbox.
```python check.py theme={null}
from notte_sdk import NotteClient
client = NotteClient()
with client.Session() as session:
session.execute(type="goto", url="https://example.com")
# Check a checkbox
session.execute(type="check", selector="input[type='checkbox']#terms", value=True)
# Uncheck a checkbox
session.execute(type="check", selector="input[type='checkbox']#newsletter", value=False)
```
**Parameters:**
* `selector` (str): Selector for the checkbox
* `id` (str): Element ID from the latest `observe()` output
* `value` (bool): True to check, False to uncheck
**Use for:** Toggling checkboxes, accepting terms, selecting options
***
### SelectDropdownOption
Select an option from a dropdown.
```python select_dropdown_option.py theme={null}
from notte_sdk import NotteClient
client = NotteClient()
with client.Session() as session:
session.execute(type="goto", url="https://example.com")
# Select by visible text
session.execute(type="select_dropdown_option", selector="select#country", value="United States")
# Select by value attribute
session.execute(type="select_dropdown_option", selector="select#country", value="us")
```
**Parameters:**
* `selector` (str): Selector for the select element
* `id` (str): Element ID from the latest `observe()` output
* `value` (str): Visible text or value attribute of the option to select
**Use for:** Selecting from dropdowns, choosing options
***
### PressKey
Press a keyboard key.
```python press_key.py theme={null}
from notte_sdk import NotteClient
client = NotteClient()
with client.Session() as session:
session.execute(type="goto", url="https://example.com")
# Press Enter
session.execute(type="press_key", key="Enter")
# Press Escape
session.execute(type="press_key", key="Escape")
# Press Tab
session.execute(type="press_key", key="Tab")
```
```typescript press_key.ts theme={null}
import { NotteClient } from 'notte-sdk';
const client = new NotteClient();
const session = client.Session();
await session.use(async () => {
await session.execute({ type: "goto", url: "https://example.com" });
// Press Enter
await session.execute({ type: "press_key", key: "Enter" });
// Press Escape
await session.execute({ type: "press_key", key: "Escape" });
// Press Tab
await session.execute({ type: "press_key", key: "Tab" });
});
```
**Parameters:**
* `key` (str): Key name (e.g., "Enter", "Escape", "Tab", "ArrowDown")
**Use for:** Keyboard navigation, submitting forms, triggering shortcuts
**Common keys:** Enter, Escape, Tab, ArrowUp, ArrowDown, ArrowLeft, ArrowRight, Backspace, Delete
***
### EvaluateJs
Evaluate JavaScript code on the current page and return the result. `session.evaluate_js(code)` returns the evaluated string directly (objects and arrays as JSON); a failing script raises with the actual JavaScript error.
```python eval_js.py theme={null}
import json
from notte_sdk import NotteClient
client = NotteClient()
with client.Session() as session:
session.execute(type="goto", url="https://notte.cc/")
# evaluate_js returns the evaluated string; failures raise with the JS error
title = session.evaluate_js("document.title")
# objects and arrays come back as JSON
links = json.loads(session.evaluate_js("Array.from(document.querySelectorAll('a')).map(a => a.href)"))
```
```typescript eval_js.ts theme={null}
import { NotteClient } from 'notte-sdk';
const client = new NotteClient();
const session = client.Session();
await session.use(async () => {
await session.execute({ type: 'goto', url: 'https://notte.cc/' });
// evaluateJs returns the evaluated string; failures raise with the JS error
const title = await session.evaluateJs('document.title');
// objects and arrays come back as JSON
const links = JSON.parse(await session.evaluateJs("Array.from(document.querySelectorAll('a')).map(a => a.href)"));
});
```
**Parameters:**
* `code` (str): The JavaScript code to evaluate on the page
**Use for:** Evaluating JavaScript code, extracting data from the page
***
### Fetch
Issue an HTTP request from the page the session is on. `session.fetch(url)` runs the browser's own `fetch()` inside the current page, so the request carries the page's cookies, the session's proxy and the browser's network fingerprint. A relative URL resolves against the current page, which also keeps it same-origin; a cross-origin URL is subject to CORS as in any browser tab, so `goto` the target origin first. The result is a standard `requests.Response`: `status_code`, `ok`, `headers`, `text`, `url`, `json()` and `raise_for_status()` work as usual.
```python fetch.py theme={null}
from notte_sdk import NotteClient
client = NotteClient()
with client.Session() as session:
session.execute(type="goto", url="https://en.wikipedia.org/wiki/Main_Page")
# fetch runs inside the page: same cookies, proxy and fingerprint as the browser
response = session.fetch("/api/rest_v1/page/summary/Main_Page")
response.raise_for_status()
summary = response.json()
```
**Parameters:**
* `url` (str): The URL to request, absolute or relative to the current page
* `method` (str): HTTP method, `GET` by default
* `headers` (dict): Extra request headers
* `params` (dict): Query parameters appended to the URL
* `json` (Any): Body serialised as JSON with an `application/json` content type
* `data` (str | dict): Body sent verbatim, or form-encoded when a dict
* `timeout` (float): Seconds before the request is aborted
**Use for:** Calling a site's own JSON endpoints with the session's cookies and IP, without leaving the browser
***
## Scrolling Actions
Both scroll actions accept an optional `selector` for a scroll container, including iframe selectors.
For example, pass `selector="#results"` to scroll a results panel. The action calls `scrollBy`
on that container directly, emitting scroll events without sending a wheel event or moving the
pointer. If `amount` is omitted, the action scrolls by 70% of the container's height. Reaching the requested boundary succeeds without moving or scrolling the parent page.
A missing target or a blocked scroll before the boundary still fails.
Without a selector, the action sends a wheel event at the current pointer position and fails
if neither the page nor a container moves.
### ScrollUp
Scroll up on the page.
```python scroll_up.py theme={null}
from notte_sdk import NotteClient
client = NotteClient()
with client.Session() as session:
# Create a page tall enough to scroll.
session.evaluate_js(
"""(() => {
document.open();
document.write('Scroll demo');
document.close();
document.body.style.margin = '0';
document.querySelector('main').style.height = '300vh';
return document.documentElement.scrollHeight;
})()"""
)
session.execute(type="scroll_down")
print("Moved down: " + session.evaluate_js("String(window.scrollY > 0)"))
session.execute(type="scroll_up")
print("Returned to top: " + session.evaluate_js("String(window.scrollY === 0)"))
```
```typescript scroll_up.ts theme={null}
import { NotteClient } from 'notte-sdk';
const client = new NotteClient();
const session = client.Session();
await session.use(async () => {
// Create a page tall enough to scroll.
await session.evaluateJs(`(() => {
document.open();
document.write('Scroll demo');
document.close();
document.body.style.margin = '0';
document.querySelector('main').style.height = '300vh';
return document.documentElement.scrollHeight;
})()`);
await session.execute({ type: "scroll_down" });
console.log("Moved down: " + await session.evaluateJs("String(window.scrollY > 0)"));
await session.execute({ type: "scroll_up" });
console.log("Returned to top: " + await session.evaluateJs("String(window.scrollY === 0)"));
});
```
**Use for:** Scrolling to top of page, revealing content above
***
### ScrollDown
Scroll down on the page.
```python scroll_down.py theme={null}
from notte_sdk import NotteClient
client = NotteClient()
with client.Session() as session:
# Create a page tall enough to scroll.
session.evaluate_js(
"""(() => {
document.open();
document.write('Scroll demo');
document.close();
document.body.style.margin = '0';
document.querySelector('main').style.height = '300vh';
return document.documentElement.scrollHeight;
})()"""
)
session.execute(type="scroll_down")
print("Moved down: " + session.evaluate_js("String(window.scrollY > 0)"))
```
```typescript scroll_down.ts theme={null}
import { NotteClient } from 'notte-sdk';
const client = new NotteClient();
const session = client.Session();
await session.use(async () => {
// Create a page tall enough to scroll.
await session.evaluateJs(`(() => {
document.open();
document.write('Scroll demo');
document.close();
document.body.style.margin = '0';
document.querySelector('main').style.height = '300vh';
return document.documentElement.scrollHeight;
})()`);
await session.execute({ type: "scroll_down" });
console.log("Moved down: " + await session.evaluateJs("String(window.scrollY > 0)"));
});
```
**Use for:** Loading lazy content, revealing content below, pagination
***
## File Actions
### UploadFile
Upload a file to a file input.
```python upload_file.py theme={null}
from notte_sdk import NotteClient
client = NotteClient()
with client.Session() as session:
session.execute(type="goto", url="https://example.com/upload")
# Upload a local file to the cloud session, then attach it by filename.
uploaded = session.storage.upload("/path/to/document.pdf")
session.execute(type="upload_file", selector="input[type='file']", file_path=uploaded.filename)
# Or attach a public HTTP(S) file URL directly. Signed URLs also work.
session.execute(
type="upload_file",
selector="input[type='file']",
file_path="https://example.com/document.pdf",
)
```
**Parameters:**
* `selector` (str): Selector for the file input
* `id` (str): Element ID from the latest `observe()` output
* `file_path` (str): In cloud sessions, a stored filename or a public HTTP(S) file URL, including signed URLs.
URL files download on the API server without navigating away from the form. They do
not use browser cookies, browser proxy settings, or custom authentication headers,
and are not added to the persistent session file catalog. The cloud backend must
support URL uploads; no SDK update is required. Standalone local sessions keep
their existing storage/path behavior.
**Use for:** Uploading documents, images, any file input
***
### DownloadFile
Download a file from a link.
```python download_file.py theme={null}
from notte_sdk import NotteClient
client = NotteClient()
with client.Session() as session:
session.execute(type="goto", url="https://example.com")
result = session.execute(type="download_file", selector="a.download-link")
print(f"Downloaded: {result}")
```
**Parameters:**
* `selector` (str): Selector for the download link
* `id` (str): Element ID from the latest `observe()` output
**Use for:** Downloading files, reports, documents
***
## Wait Action
### Wait
Pause execution for a specified duration.
```python wait.py theme={null}
from notte_sdk import NotteClient
client = NotteClient()
with client.Session() as session:
session.execute(type="goto", url="https://example.com")
# Wait 2 seconds
session.execute(type="wait", time_ms=2000)
# Wait 5 seconds for page to load
session.execute(type="wait", time_ms=5000)
```
```typescript wait.ts theme={null}
import { NotteClient } from 'notte-sdk';
const client = new NotteClient();
const session = client.Session();
await session.use(async () => {
await session.execute({ type: "goto", url: "https://example.com" });
// Wait 2 seconds
await session.execute({ type: "wait", time_ms: 2000 });
// Wait 5 seconds for page to load
await session.execute({ type: "wait", time_ms: 5000 });
});
```
**Parameters:**
* `time_ms` (int): Time to wait in milliseconds
**Use for:** Waiting for page loads, animations, dynamic content
**Tip:** Use sparingly. Notte automatically waits for page loads and element visibility. Only use Wait for specific timing requirements.
***
## Data Extraction
### Scrape
Extract data from the current page.
```python scrape.py theme={null}
from notte_sdk import NotteClient
from pydantic import BaseModel
client = NotteClient()
class Product(BaseModel):
name: str
price: float
with client.Session() as session:
session.execute(type="goto", url="https://example.com")
# Get page markdown
markdown = session.scrape()
# Extract with instructions
data = session.scrape(instructions="Extract all product names and prices")
# Structured extraction (wrap list in a model)
products = session.scrape(response_format=Product, instructions="Extract all products")
```
**Parameters:**
* `instructions` (str): Natural language extraction instructions
* `response_format` (type\[BaseModel]): Pydantic model for structured output
* `only_main_content` (bool): Extract only main content, ignore headers/footers
**Use for:** Data extraction, web scraping, content analysis
See [Scraping](/concepts/scraping) for detailed documentation.
***
## Reading Emails and SMS
Reading a persona inbox doesn't act on the browser, so it isn't a session action: call the persona directly while the session drives the page.
### SMS
```python sms_read.py theme={null}
from collections.abc import Sequence
from notte_sdk import NotteClient
from notte_sdk.types import SMSResponse
client = NotteClient()
def extract_code(messages: Sequence[SMSResponse]) -> str:
# Extract verification code from messages
for msg in messages:
if msg.body and "code" in msg.body.lower():
return msg.body
return ""
# Create persona with phone number
persona = client.Persona(create_phone_number=True)
phone_number = persona.info.phone_number
# Use in session
with client.Session() as session:
# Trigger SMS (e.g., 2FA code)
if phone_number:
session.execute(type="fill", selector="input[name='phone']", value=phone_number)
session.execute(type="click", selector="button.send-code")
# Read SMS from persona
messages = persona.sms()
# Extract verification code
code = extract_code(messages)
```
* `persona.sms(limit=..., timedelta=..., only_unread=...)`: list recent SMS
* `persona.wait_for_sms(timeout=120, only_unread=True)`: poll until an SMS arrives
**Use for:** Reading 2FA codes, SMS verification, phone-based authentication
### Emails
```python email_read.py theme={null}
from collections.abc import Sequence
from notte_sdk import NotteClient
from notte_sdk.types import EmailResponse
client = NotteClient()
def extract_link(messages: Sequence[EmailResponse]) -> str:
# Extract verification link from messages
for msg in messages:
if msg.text_content and "verify" in msg.text_content.lower():
return msg.text_content
return ""
# Create persona with email
persona = client.Persona()
# Use in session
with client.Session() as session:
# Trigger email (e.g., verification email)
session.execute(type="fill", selector="input[name='email']", value=persona.info.email)
session.execute(type="click", selector="button.send-verification")
# Read emails from persona
messages = persona.emails()
# Extract verification link
link = extract_link(messages)
```
* `persona.emails(limit=..., timedelta=..., only_unread=...)`: list recent emails
* `persona.wait_for_emails(timeout=120, only_unread=True)`: poll until an email arrives
**Use for:** Reading verification emails, email-based authentication
Listed messages are marked as read. See [Personas](/concepts/personas) for details.
***
## Action Patterns
### Chaining Actions
Execute multiple actions in sequence:
```python action_chaining.py theme={null}
from notte_sdk import NotteClient
client = NotteClient()
with client.Session() as session:
# Navigation
session.execute(type="goto", url="https://example.com/login")
# Form filling
session.execute(type="fill", selector="input[name='email']", value="user@example.com")
session.execute(type="fill", selector="input[name='password']", value="password123")
# Submission
session.execute(type="click", selector="button[type='submit']")
# Wait for redirect
session.execute(type="wait", time_ms=2000)
# Verify success
content = session.scrape()
print(content)
```
### Error Handling
Handle action failures:
```python error_handling.py theme={null}
from notte_sdk import NotteClient
client = NotteClient()
with client.Session() as session:
session.execute(type="goto", url="https://example.com")
# Don't raise on failure
result = session.execute(type="click", selector="button.maybe-exists", raise_on_failure=False)
if result.success:
print("Button clicked successfully")
session.execute(type="click", selector="button.next")
else:
print(f"Click failed: {result.message}")
# Try alternative approach
session.execute(type="click", selector="button.alternative")
```
### Conditional Actions
Execute actions based on conditions:
```python conditional_actions.py theme={null}
from notte_sdk import NotteClient
client = NotteClient()
with client.Session() as session:
session.execute(type="goto", url="https://example.com")
# Check if element exists
result = session.execute(type="click", selector="button.optional", raise_on_failure=False)
if result.success:
# Element was there and clicked
session.execute(type="click", selector="button.next")
else:
# Element wasn't there, skip
print("Optional button not found, continuing...")
```
```typescript conditional_actions.ts theme={null}
import { NotteClient } from 'notte-sdk';
const client = new NotteClient();
const session = client.Session();
await session.use(async () => {
await session.execute({ type: 'goto', url: 'https://example.com' });
// Check if element exists
const result = await session.execute({ type: 'click', selector: 'button.optional' }, { raiseOnFailure: false });
if (result.success) {
// Element was there and clicked
await session.execute({ type: 'click', selector: 'button.next' });
} else {
// Element wasn't there, skip
console.log('Optional button not found, continuing...');
}
});
```
## Selector Strategies
### CSS Selectors
Most flexible and commonly used:
```python css_selectors.py theme={null}
from notte_sdk import NotteClient
client = NotteClient()
with client.Session() as session:
session.execute(type="goto", url="https://example.com")
# By ID
session.execute(type="click", selector="#submit-button")
# By class
session.execute(type="click", selector=".btn-primary")
# By attribute
session.execute(type="click", selector="button[type='submit']")
# By combination
session.execute(type="click", selector="form#login button.submit")
# By nth-child
session.execute(type="click", selector="ul li:nth-child(2)")
```
### Direct Attributes
Use direct attribute parameters when available:
```python direct_attributes.py theme={null}
from notte_sdk import NotteClient
client = NotteClient()
with client.Session() as session:
session.execute(type="goto", url="https://example.com")
# Pseudo observe output: [B1] button "Submit"
# Only use IDs that appear in your live observe() output.
session.execute(type="click", id="B1")
# By selector
session.execute(type="click", selector="button[type='submit']")
# By placeholder selector
session.execute(type="fill", selector="input[placeholder='Enter email']", value="user@example.com")
# By ARIA label selector
session.execute(type="click", selector="button[aria-label='Close dialog']")
```
### Best Practices
**Prefer stable selectors:**
```python stable_selectors.py theme={null}
from notte_sdk import NotteClient
client = NotteClient()
with client.Session() as session:
session.execute(type="goto", url="https://example.com")
# Good - uses the element's id attribute
session.execute(type="click", selector="#submit-btn")
# Good - uses data attribute
session.execute(type="click", selector="button[data-testid='submit']")
# Avoid - fragile position-based
session.execute(type="click", selector="div > div > button:nth-child(3)")
```
**Use specific selectors:**
```python specific_selectors.py theme={null}
from notte_sdk import NotteClient
client = NotteClient()
with client.Session() as session:
session.execute(type="goto", url="https://example.com")
# Good - specific
session.execute(type="fill", selector="form#login input[name='email']", value="user@example.com")
# Less specific - might match wrong element
session.execute(type="fill", selector="input", value="user@example.com")
```
## Common Workflows
### Form Submission
```python form_submission.py theme={null}
from notte_sdk import NotteClient
client = NotteClient()
with client.Session() as session:
session.execute(type="goto", url="https://example.com/contact")
session.execute(type="fill", selector="#name", value="John Doe")
session.execute(type="fill", selector="#email", value="john@example.com")
session.execute(type="fill", selector="#message", value="Hello!")
session.execute(type="click", selector="button[type='submit']")
```
### Search and Extract
```python search_extract.py theme={null}
from notte_sdk import NotteClient
client = NotteClient()
with client.Session() as session:
session.execute(type="goto", url="https://example.com")
session.execute(type="fill", selector="input[name='search']", value="laptop")
session.execute(type="press_key", key="Enter")
session.execute(type="wait", time_ms=2000)
results = session.scrape(instructions="Extract search results")
print(results)
```
### Multi-Tab Workflow
```python multi_tab.py theme={null}
from notte_sdk import NotteClient
client = NotteClient()
def process_data(data):
print(f"Processed: {data[:100]}...")
with client.Session() as session:
# Open multiple tabs
session.execute(type="goto", url="https://site1.com")
session.execute(type="goto_new_tab", url="https://site2.com")
session.execute(type="goto_new_tab", url="https://site3.com")
# Extract from each tab
for tab_index in range(3):
session.execute(type="switch_tab", tab_index=tab_index)
data = session.scrape()
process_data(data)
```
### File Upload and Download
```python file_upload_download.py theme={null}
from notte_sdk import NotteClient
client = NotteClient()
with client.Session() as session:
session.execute(type="goto", url="https://example.com/upload")
# Upload file
session.execute(type="upload_file", selector="input[type='file']", file_path="/path/to/document.pdf")
session.execute(type="click", selector="button.submit")
# Wait for processing
session.execute(type="wait", time_ms=3000)
# Download result
result = session.execute(type="download_file", selector="a.download")
print(f"Downloaded: {result}")
```
## Next Steps
Configure session behavior
Manage session states
Detailed API documentation
Upload and download files in sessions
# Browser Profiles
Source: https://docs.notte.cc/features/sessions/browser-profiles
Persist browser state across sessions
Read [the Notte skill](https://notte.cc/skill.md) and follow its setup instructions when setting up Notte or building browser automation. This is the shared source for CLI setup, authentication, and the browser-to-code workflow.
Profiles let you save and reuse browser state (cookies, local storage, and session storage) across multiple sessions. This is useful for maintaining login states, preserving user preferences, or building realistic multi-session workflows.
## How Profiles Work
A profile is a saved snapshot of a browser's cookies, local storage, and session storage. Other browser data, such as the HTTP cache, history, and extensions, is not saved. By default, each Notte session uses a fresh browser state to ensure isolation.
When you create a profile and use it in a session with `persist=True`, Notte saves that session's browser state when the session ends. You can also save any running session into a profile at any time with `save_profile()`. You can then attach the profile to future sessions to restore the saved state.
## Create a Profile
Create a profile to store browser state. Optionally, give it a name to keep track of different profiles:
```python create_profile.py theme={null}
from notte_sdk import NotteClient
client = NotteClient()
# Create a profile with optional name
profile = client.profiles.create(name="my-profile")
print(f"Profile created: {profile.profile_id}")
print(f"Profile name: {profile.name}")
```
## Use a Profile in a Session
Attach a profile to a session to load saved browser state:
```python use_profile.py theme={null}
from notte_sdk import NotteClient
client = NotteClient()
# Use an existing profile in a session
with client.Session(profile={"id": "notte-profile-abc123", "persist": False}) as session:
# Session loads saved browser state from the profile
session.execute(type="goto", url="https://example.com")
```
### Persist Changes
The `persist` parameter controls whether session changes are saved back to the profile:
* `True` - Save browser state to the profile when the session ends
* `False` (default) - Use the profile as read-only; don't save changes
Set `persist=True` the first time you use a new profile to save the initial browser state. After that, you can use `persist=False` to reuse the profile without modifying it.
## Profile Login Example
### Step 1: Login and Save to Profile
First, create a profile and perform a login. When the session ends with `persist=True`, all cookies and browser state are saved to the profile:
```python persist_profile.py theme={null}
from notte_sdk import NotteClient
client = NotteClient()
# Create a new profile
profile = client.profiles.create(name="github-login")
# Use profile with persist=True to save browser state
with client.Session(profile={"id": profile.profile_id, "persist": True}) as session:
# Navigate and perform login
session.execute(type="goto", url="https://github.com/login")
session.execute(type="fill", selector='input[name="login"]', value="username")
session.execute(type="fill", selector='input[name="password"]', value="password")
session.execute(type="click", selector='input[type="submit"]')
# Browser state is saved to profile when session ends
```
### Save a Running Session
If you only decide to keep the browser state after the session has started, save it directly. `save_profile()` snapshots the running session into a new profile (`name`) or an existing one (`profile_id`) and the session keeps running. It works on any session, including one started without a profile or with `persist=False`. With neither argument, it saves into the profile the session was started with.
```python save_profile.py theme={null}
from notte_sdk import NotteClient
client = NotteClient()
# Start a session without a profile, or with a read-only one
with client.Session() as session:
session.execute(type="goto", url="https://example.com")
# ... log in ...
# Save the current browser state into a new profile; the session keeps running
profile = session.save_profile(name="example-login")
print(profile.profile_id, profile.persisted_domains)
# Later saves can overwrite the same profile
again = session.save_profile(profile_id=profile.profile_id)
results = [profile.name, again.profile_id == profile.profile_id]
```
```typescript save_profile.ts theme={null}
import { NotteClient } from "notte-sdk";
const client = new NotteClient();
// Start a session without a profile, or with a read-only one
const session = client.Session();
const profile = await session.use(async (session) => {
await session.execute({ type: "goto", url: "https://example.com" });
// ... log in ...
// Save the current browser state into a new profile; the session keeps running
const profile = await session.saveProfile({ name: "example-login" });
console.log(profile.profile_id, profile.persisted_domains);
// Later saves can overwrite the same profile
const again = await session.saveProfile({ profile_id: profile.profile_id });
return { ...profile, sameProfile: again.profile_id === profile.profile_id };
});
```
`save_profile()` does not change the session's `persist` setting. A session started with `persist=True` still saves again when it ends.
### Step 2: Reuse the Saved Profile
Use the saved profile in a new session. The browser automatically loads the saved state, so you're already authenticated:
```python reuse_profile.py theme={null}
from notte_sdk import NotteClient
client = NotteClient()
# Reuse a previously saved profile (read-only)
with client.Session(profile={"id": "notte-profile-abc123", "persist": False}) as session:
# Navigate directly - already authenticated!
session.execute(type="goto", url="https://github.com")
# Perform actions as logged-in user
obs = session.observe()
print(f"Page title: {obs.metadata.title}")
```
**What happens:**
1. A new session is created using the same profile ID
2. The browser loads saved cookies and state from the profile
3. You navigate directly without needing to log in again
4. The session picks up right where the previous one left off
This pattern is useful for:
* Avoiding repeated logins across automation runs
* Testing authenticated user flows
* Managing multiple accounts with separate profiles
* Maintaining session state over time
## Managing Profiles
### List Profiles
Retrieve all your profiles with optional filtering:
```python list_profiles.py theme={null}
from notte_sdk import NotteClient
client = NotteClient()
# List all profiles
profiles = client.profiles.list()
for profile in profiles:
print(f"- {profile.name}: {profile.profile_id}")
# Filter by name
filtered = client.profiles.list(name="github")
print(f"Found {len(filtered)} profiles matching 'github'")
```
### Get Profile Details
Retrieve information about a specific profile:
```python get_profile.py theme={null}
from notte_sdk import NotteClient
client = NotteClient()
# Get profile details
profile = client.profiles.get("notte-profile-abc123")
print(f"Profile ID: {profile.profile_id}")
print(f"Profile Name: {profile.name}")
print(f"Created At: {profile.created_at}")
```
### Delete a Profile
Delete a profile when you no longer need it:
```python delete_profile.py theme={null}
from notte_sdk import NotteClient
client = NotteClient()
# Delete a profile
deleted = client.profiles.delete("notte-profile-abc123")
if deleted:
print("Profile deleted successfully")
```
## Read-Only Profile Usage
Load a profile without saving changes by setting `persist=False`:
```python use_profile.py theme={null}
from notte_sdk import NotteClient
client = NotteClient()
# Use an existing profile in a session
with client.Session(profile={"id": "notte-profile-abc123", "persist": False}) as session:
# Session loads saved browser state from the profile
session.execute(type="goto", url="https://example.com")
```
**Use cases for read-only profiles:**
* Test workflows without affecting saved state
* Run parallel sessions with the same profile safely
* Maintain a clean baseline profile for repeated use
By default, `persist` is `False`, so you don't have to explicitly specify it for read-only access.
## Best Practices
### Profile Management
* Use descriptive names to identify profiles by their purpose (e.g., `github-login`, `twitter-bot-1`)
* Create separate profiles for different accounts or services
* Periodically verify that saved sessions are still valid
### Security
* Profiles contain sensitive session data; treat profile IDs as secrets
* Delete profiles for accounts that are no longer needed
* Use separate profiles for production and development environments
## Comparison: Profiles vs Cookies
| Feature | Profiles | Cookies |
| - | - | - |
| **Scope** | Cookies, local storage, and session storage | Cookies only |
| **Storage** | Managed by Notte | JSON file you manage |
| **Persistence** | Save on session end with `persist=True`, or call `save_profile()` | Manual export/import, or automatic load/save with `cookie_file` |
| **Use case** | Long-term state preservation | Quick cookie injection |
Use profiles when you need to preserve cookies and web storage together. Use cookies when you only need to inject specific authentication tokens.
## Next Steps
Manage session lifecycle
Manual cookie management
Secure credential storage
All session options
# CAPTCHA Solving
Source: https://docs.notte.cc/features/sessions/captcha-solving
Automatically detect and solve captchas during browser automation
Read [the Notte skill](https://notte.cc/skill.md) and follow its setup instructions when setting up Notte or building browser automation. This is the shared source for CLI setup, authentication, and the browser-to-code workflow.
Notte can automatically detect and solve captchas including reCAPTCHA v2, hCaptcha, Cloudflare Turnstile, press-and-hold challenges, and simple text challenges during your browser automation. Captcha solving is on by default (`solve_captchas=True`).
## Quick Start
Captcha solving is enabled by default. You can set it explicitly when creating a session:
```python Python theme={null}
from notte_sdk import NotteClient
client = NotteClient()
with client.Session(
solve_captchas=True,
) as session:
page = session.page
page.goto("https://www.google.com/recaptcha/api2/demo")
# Captchas are automatically solved
```
## Supported CAPTCHA Types
Notte automatically detects and solves:
* ✅ **reCAPTCHA v2** - Checkbox and image challenges
* ✅ **hCaptcha** - Checkbox and image challenges
* ✅ **Cloudflare Turnstile**
* ✅ **Press-and-hold challenges**
* ✅ **Text CAPTCHAs** - Simple "read the characters and type them" challenges
## How It Works
When captcha solving is enabled:
1. **Detection** - Notte monitors the page for captcha elements
2. **Solving** - Captchas are solved automatically in the background
3. **Continuation** - Your automation continues seamlessly
No additional code is needed - captchas are solved automatically when encountered.
## Captcha + Proxies
Combine captcha solving with proxies for better success rates:
Residential proxies and CAPTCHA solving are both on by default. Use [Proxies](/features/sessions/proxies) to choose a location or supply a custom proxy.
## Best Practices
### 1. Use Realistic Delays
Add human-like delays before and after captcha-protected actions:
```python realistic_delays.py theme={null}
import time
from notte_sdk import NotteClient
client = NotteClient()
with client.Session(solve_captchas=True) as session:
page = session.page
# Navigate to page
page.goto("https://example.com/protected")
# Wait for page to load
time.sleep(2)
# Fill form
page.fill('input[name="email"]', "user@example.com")
time.sleep(1)
# Submit (captcha will be solved)
page.click('button[type="submit"]')
```
### 2. Monitor for Failures
Check if captcha solving failed:
```python monitor_failures.py theme={null}
from notte_sdk import NotteClient
client = NotteClient()
with client.Session(solve_captchas=True) as session:
page = session.page
page.goto("https://example.com/protected")
try:
page.click('button[type="submit"]')
page.wait_for_url("**/success", timeout=30000) # 30 seconds
except Exception as e:
print(f"Captcha solving may have failed: {e}")
# Handle failure
```
### 3. Use the Live Viewer for Debugging
Watch captcha solving in action:
```python headless_debug.py theme={null}
from notte_sdk import NotteClient
client = NotteClient()
with client.Session(
solve_captchas=True,
open_viewer=True,
) as session:
# You can watch captchas being solved
pass
```
### 4. Combine with Stealth
Use stealth features for better success rates:
```python combine_stealth.py theme={null}
from notte_sdk import NotteClient
client = NotteClient()
with client.Session(solve_captchas=True, proxies=True, viewport_width=1920, viewport_height=1080) as session:
# Maximum captcha success rate
pass
```
```typescript combine_stealth.ts theme={null}
import { NotteClient } from 'notte-sdk';
const client = new NotteClient();
const session = client.Session({
solve_captchas: true,
proxies: true,
viewport_width: 1920,
viewport_height: 1080,
});
await session.use(async () => {
// Maximum captcha success rate
});
```
## Examples
### reCAPTCHA
For reCAPTCHA challenges, use the `captcha_solve` action with `captcha_type="recaptcha"`. This example comes from the [reCAPTCHA template](https://www.notte.cc/templates/captcha-bot-handling/basic-recaptcha).
```python recaptcha_solve_action.py theme={null}
from notte_sdk import NotteClient
DEMO_URL = "https://nopecha.com/captcha/recaptcha#easy"
client = NotteClient()
with client.Session(solve_captchas=True, proxies=True, open_viewer=True) as session:
session.execute(type="goto", url=DEMO_URL)
result = session.execute(
type="captcha_solve",
captcha_type="recaptcha",
raise_on_failure=False,
)
if not result.success:
raise RuntimeError(f"reCAPTCHA solve failed: {result.message}")
session.execute(type="wait", time_ms=2000)
```
### Waiting for a solve in Python
The Python SDK polls one ongoing solve through short HTTP requests. Repeated
`captcha_solve` calls join that solve or return its completed result. Configure the
total wait separately from ordinary action timeouts:
Set `captcha_timeout_seconds=180` when constructing `NotteClient`. Then use the `captcha_solve` action shown above; do not restart the session to wait for the same challenge.
The default CAPTCHA wait is three minutes. A terminal solver failure, page change,
or expired wait budget follows the usual `raise_on_failure` behavior. Polling does
not restart the solve or extend the session's maximum lifetime.
SDK actions blocked before execution wait for the CAPTCHA and then retry. If an
action already executed and triggered a CAPTCHA, the SDK waits without replaying
it. This coordination applies to Python SDK actions; direct Playwright/CDP calls
do not participate. It requires a backend version that supports CAPTCHA polling.
### Text CAPTCHAs
Text CAPTCHAs are the old-school challenges that show distorted letters, numbers, or a short phrase and ask the user to type the answer into a field. For these challenges, use `captcha_type="text"`. This example comes from the [text CAPTCHA template](https://www.notte.cc/templates/captcha-bot-handling/basic-text-captcha).
```python text_captcha_solve_action.py theme={null}
from notte_sdk import NotteClient
DEMO_URL = "https://captcha.com/demos/features/captcha-demo.aspx#"
VALIDATE_SELECTOR = 'internal:role=button[name="Validate"i]'
client = NotteClient()
with client.Session(solve_captchas=True, proxies=True, open_viewer=True) as session:
session.execute(type="goto", url=DEMO_URL)
result = session.execute(
type="captcha_solve",
captcha_type="text",
raise_on_failure=False,
)
if not result.success:
raise RuntimeError(f"Text CAPTCHA solve failed: {result.message}")
session.execute(type="click", selector=VALIDATE_SELECTOR)
```
## Limitations
### What CAPTCHA Solving Can Do:
* ✅ Solve most reCAPTCHA v2 challenges
* ✅ Solve most hCaptcha challenges
* ✅ Solve Cloudflare Turnstile and press-and-hold challenges
* ✅ Solve simple text CAPTCHA challenges
* ✅ Work with residential proxies
* ✅ Work in remote browser sessions
### What It Cannot Do:
* ❌ Solve 100% of captchas (some are unsolvable)
* ❌ Reliably solve heavily distorted or ambiguous text challenges
* ❌ Solve custom/proprietary captcha systems
* ❌ Bypass all bot detection (combine with stealth)
## Common Issues
### Issue: Captchas Not Being Solved
**Possible causes:**
* `solve_captchas` was set to `False`
* Captcha type not supported
* Site has additional bot detection
**Solution:**
```python ensure_requirements.py theme={null}
from notte_sdk import NotteClient
client = NotteClient()
# Ensure all requirements are met
with client.Session(
solve_captchas=True, # Must be enabled
proxies=True, # Helps with detection
) as session:
pass
```
```typescript ensure_requirements.ts theme={null}
import { NotteClient } from 'notte-sdk';
const client = new NotteClient();
const session = client.Session({
solve_captchas: true, // Must be enabled
proxies: true, // Helps with detection
});
await session.use(async () => {
// Use your session here
});
```
### Issue: Timeout Waiting for Captcha
Increase session timeout for captcha-heavy workflows:
```python timeout_handling.py theme={null}
from notte_sdk import NotteClient
client = NotteClient()
with client.Session(
solve_captchas=True,
idle_timeout_minutes=15, # Longer timeout
) as session:
pass
```
## Testing CAPTCHA Solving
Test captcha solving on demo sites:
Use the live viewer to inspect a demo challenge and save a screenshot or recording of the result. The reCAPTCHA and text CAPTCHA examples above handle failure explicitly. Verify the challenge type on your target site rather than treating a demo success as a guarantee.
## Next Steps
All session configuration options
Combine with anti-detection features
Use proxies with captcha solving
Watch captcha solving in real-time
# Cloudflare Web Bot Auth
Source: https://docs.notte.cc/features/sessions/cloudflare-web-bot-auth
Access Cloudflare-protected websites by cryptographically signing browser requests
Read [the Notte skill](https://notte.cc/skill.md) and follow its setup instructions when setting up Notte or building browser automation. This is the shared source for CLI setup, authentication, and the browser-to-code workflow.
Many websites use Cloudflare's [Web Bot Auth](https://webbotauth.io) protocol to distinguish between legitimate automated services and unwanted bots. Notte is registered as a verified bot in Cloudflare's registry, which means sessions can cryptographically prove their identity and gain access to otherwise restricted content.
## How It Works
Cloudflare's Web Bot Auth relies on [HTTP Message Signatures (RFC 9421)](https://datatracker.ietf.org/doc/html/rfc9421) to verify the origin of requests. When enabled, Notte transparently signs every outgoing HTTP request in the session with our registered cryptographic identity. Cloudflare then checks these signatures against its verified bots list and grants access accordingly.
In practice, this means:
1. You create a session with `web_bot_auth=True`
2. Every HTTP request leaving the browser is signed with Notte's credentials
3. Cloudflare verifies the signature and recognizes Notte as a trusted bot
4. The request passes through without triggering bot challenges
No extra code or configuration is needed on your side beyond flipping the flag.
## Quick Start
Pass `web_bot_auth=True` when creating a session:
```python quick_start.py theme={null}
from notte_sdk import NotteClient
client = NotteClient()
with client.Session(web_bot_auth=True) as session:
page = session.page
page.goto("https://example.com")
# All requests are automatically signed
```
## Complete Example
Web bot auth pairs well with other session features. Here's an example combining it with residential proxies and the live viewer:
```python complete_example.py theme={null}
from notte_sdk import NotteClient
client = NotteClient()
with client.Session(
web_bot_auth=True,
proxies=True, # Use residential proxies
open_viewer=True,
) as session:
page = session.page
# Navigate to a Cloudflare-protected site
page.goto("https://example.com")
# Interact with the page normally
page.click("a[href='/dashboard']")
page.wait_for_load_state("networkidle")
print("Successfully accessed protected content!")
```
## Verifying Your Setup
The [webbotauth.io/test](https://webbotauth.io/test) page is a handy diagnostic tool that shows whether incoming requests carry valid signatures. Point a web bot auth session at it to confirm everything is working:
```python testing.py theme={null}
from notte_sdk import NotteClient
client = NotteClient()
with client.Session(
web_bot_auth=True,
open_viewer=True,
) as session:
page = session.page
page.goto("https://webbotauth.io/test")
page.screenshot(path="web_bot_auth_test.png")
print("Check the screenshot to verify authentication status")
```
## Use Cases
* **Scraping Cloudflare-protected sites** — bypass bot challenges on sites that recognize verified bots
* **Reliable agent workflows** — prevent agents from getting stuck on Cloudflare interstitial pages
* **Data pipelines** — collect data from protected endpoints without manual intervention
Web bot auth proves your identity cryptographically, which is fundamentally different from [stealth mode](/features/sessions/stealth-mode) (which hides your identity) or [proxies](/features/sessions/proxies) (which mask your IP). These approaches are complementary — combining them gives you the broadest coverage.
## Limitations
* Only effective on sites that participate in Cloudflare's verified bots program
* Requests are identified as coming from Notte — the target site must trust Notte as a verified bot
* Does not replace other anti-detection techniques for sites that don't use Cloudflare Web Bot Auth
## Further Reading
* [Cloudflare Blog — Verified Bots with Cryptography](https://blog.cloudflare.com/verified-bots-with-cryptography/)
* [HTTP Message Signatures — RFC 9421](https://datatracker.ietf.org/doc/html/rfc9421)
* [Web Bot Auth IETF Draft](https://datatracker.ietf.org/doc/html/draft-meunier-web-bot-auth-architecture)
* [WebBotAuth.io](https://webbotauth.io)
## Next Steps
Combine with anti-detection features
Route traffic through residential proxies
Automatically solve captchas
Explore all session options
# Session Configurations
Source: https://docs.notte.cc/features/sessions/configuration
Learn how to create and configure Notte browser sessions
Read [the Notte skill](https://notte.cc/skill.md) and follow its setup instructions when setting up Notte or building browser automation. This is the shared source for CLI setup, authentication, and the browser-to-code workflow.
Sessions are isolated browser instances running in the cloud that you can control programmatically. Each session provides a Playwright-compatible page that you can automate using familiar browser automation APIs.
## Quick Start
Create a session with default settings:
```python Python theme={null}
with client.Session() as session:
print(f"Session ID: {session.session_id}")
page = session.page # Playwright-compatible page
page.goto("https://example.com")
```
```typescript TypeScript theme={null}
import { NotteClient, type SessionResponse } from 'notte-sdk';
const client = new NotteClient();
let status: SessionResponse | undefined;
let title: string | undefined;
const session = client.Session();
await session.use(async () => {
status = await session.status();
if (status.status !== 'active') throw new Error('Session is not active');
const page = await session.page();
await page.goto('https://example.com');
title = await page.title();
console.log(`Page title: ${title}`);
});
// Automatically stopped here.
```
Active sessions can be viewed and managed in the [Sessions](https://console.notte.cc/sessions) page in the console.
Sessions are automatically stopped when exiting the context manager. Always use the `with` statement to ensure proper cleanup.
## Configuration Options
Customize your session with these options:
```python Python theme={null}
with client.Session(
advanced_stealth=False,
solve_captchas=True,
proxies=True,
viewport_width=1920,
viewport_height=1080,
idle_timeout_minutes=10,
) as session:
page = session.page
page.goto("https://example.com")
```
```typescript TypeScript theme={null}
import { NotteClient, type SessionResponse } from 'notte-sdk';
const client = new NotteClient();
let status: SessionResponse | undefined;
let title: string | undefined;
let viewport: { width: number; height: number } | undefined;
const session = client.Session({
idle_timeout_minutes: 10,
advanced_stealth: false,
solve_captchas: true,
proxies: true,
viewport_width: 1920,
viewport_height: 1080,
});
await session.use(async () => {
status = await session.status();
if (status.status !== 'active') throw new Error('Session is not active');
const page = await session.page();
await page.goto('https://example.com');
title = await page.title();
console.log(`Page title: ${title}`);
viewport = await page.evaluate(() => ({
width: window.innerWidth,
height: window.innerHeight,
}));
console.log(viewport);
});
// Automatically stopped here.
```
### Common Parameters
Enable Notte's highest-fidelity browser environment for sites with sophisticated bot detection. Access is currently available to approved workspaces; contact [hello@notte.cc](mailto:hello@notte.cc) to enable it.
Automatically detect and solve captchas (reCAPTCHA, hCaptcha, etc.). Set this option on the session request.
The retired global `solve_captchas` TOML key is ignored with a warning.
Block ads and trackers in managed browsers using Basic filtering, without cosmetic filtering or injected scriptlets.
To opt out, use `client.Session(block_ads=False)` in Python or `client.Session({ block_ads: false })` in TypeScript.
When omitted, the SDK leaves the setting to the API default. Explicit values require an API that supports `block_ads`.
This setting does not configure local browsers or externally connected CDP browsers.
Residential proxies are enabled by default. Set to `false` to disable them or provide custom proxy configuration.
See [Proxy Configuration](#proxies) for details.
Idle timeout in minutes (max: 30). The session closes after this long without any operation; each operation resets it. Cannot exceed `max_duration_minutes`. `timeout_minutes` is accepted as a legacy alias.
Maximum session lifetime in minutes (max: 1440), regardless of activity.
Browser viewport width in pixels (minimum 500). Set together with `viewport_height`. When neither is set, Notte picks a viewport around 1280x1080.
Browser viewport height in pixels (minimum 500). Set together with `viewport_width`.
Viewport shape preset: `"5:4"` or `"16:9"`. Notte fits the largest viewport of this shape in the available screen area. Cannot be combined with `viewport_width`/`viewport_height`.
### Advanced Parameters
Custom user agent string for the browser session.
Custom Chrome/Chromium launch arguments. Advanced use only.
Connect to an external browser session via Chrome DevTools Protocol URL (e.g., from Kernel.sh or other providers). Proxies, `user_agent`, `chrome_args`, and the viewport must be configured with the external provider, so leave them unset (proxies default to off when `cdp_url` is set).
Extra HTTP headers sent with every request.
Browser profile to load, with `persist=True` to save state back on close. See [Browser Profiles](/features/sessions/browser-profiles).
Vault whose credentials the session can use. See [Vaults](/concepts/vaults).
Sign requests with Web Bot Auth. See [Cloudflare Web Bot Auth](/features/sessions/cloudflare-web-bot-auth).
Path to a JSON file for automatic cookie loading at session start and saving at session end.
Attach a file storage instance to the session for file operations. Python creates `session.storage` automatically when this is omitted. In TypeScript, create `client.FileStorage()` and pass it here before using storage.
Default perception type for observations: `"fast"` (simple) or `"deep"` (LLM-powered).
Raise exceptions when action execution fails instead of returning error results.
## Managed Auth
Up to 10 Managed Auth connection IDs to verify and, when needed, log in inside the session. While login runs, the session status is `authenticating`.
SDK option. When true, starting the session waits until authentication is ready. When false, call `session.wait_for_auth()` before browser actions.
Additional retries (0 to 2) for transient Managed Auth login failures.
## Session Response
When you start a session, you get access to these properties and methods:
```python session_response.py theme={null}
from notte_sdk import NotteClient
client = NotteClient()
session = client.Session()
session.start()
# Session properties
session_id = session.session_id # Unique identifier
cdp_url = session.cdp_url() # Chrome DevTools Protocol URL
page = session.page # Playwright page object
# Session status
status = session.status() # Get current status
# Viewing
session.viewer() # Open live viewer in browser
session.viewer_cdp() # Open CDP debugger
session.viewer_notebook() # Display in Jupyter notebook
# Cookies
cookies = session.get_cookies()
session.set_cookies(cookies=cookies)
session.stop()
# Replay
replay = session.replay() # Get replay after session ends
replay.download("session.mp4")
```
## Advanced Stealth
Use Notte's approval-gated browser environment for difficult sites:
```python Python theme={null}
from notte_sdk import NotteClient
client = NotteClient()
# Advanced Stealth requires approval for your workspace.
with client.Session(advanced_stealth=True, open_viewer=True) as session:
page = session.page
page.goto("https://example.com")
```
Use `open_viewer=True` independently when you want the live viewer to open as the session starts.
## Proxies
Route sessions through residential proxies for geo-targeting and enhanced stealth:
```python proxies_simple.py theme={null}
with client.Session(proxies=True) as session:
page = session.page
page.goto("https://example.com")
```
```typescript proxies_simple.ts theme={null}
import { NotteClient, type SessionResponse } from 'notte-sdk';
const client = new NotteClient();
let status: SessionResponse | undefined;
let title: string | undefined;
const session = client.Session({ proxies: true });
await session.use(async () => {
status = await session.status();
if (status.status !== 'active') throw new Error('Session is not active');
const page = await session.page();
await page.goto('https://example.com');
title = await page.title();
console.log(`Page title: ${title}`);
});
// Automatically stopped here.
```
See the [Proxies Guide](/features/sessions/proxies) for country-specific proxies, custom proxies, and troubleshooting.
## Captcha Solving
Automatically detect and solve captchas during automation:
```python Python theme={null}
from notte_sdk import NotteClient
client = NotteClient()
with client.Session(
solve_captchas=True,
) as session:
page = session.page
page.goto("https://www.google.com/recaptcha/api2/demo")
# Captchas are automatically solved
```
See the [Captcha Solving](/features/sessions/captcha-solving) guide for more details.
## Session Timeout
Configure how long sessions stay active:
```python Python theme={null}
# Session closes after 10 minutes of inactivity
with client.Session(idle_timeout_minutes=10) as session:
page = session.page
page.goto("https://example.com")
```
```typescript TypeScript theme={null}
import { NotteClient, type SessionResponse } from 'notte-sdk';
const client = new NotteClient();
let status: SessionResponse | undefined;
let title: string | undefined;
const session = client.Session({ idle_timeout_minutes: 10 });
await session.use(async () => {
status = await session.status();
if (status.status !== 'active') throw new Error('Session is not active');
const page = await session.page();
await page.goto('https://example.com');
title = await page.title();
console.log(`Page title: ${title}`);
});
// Automatically stopped here.
```
The idle timeout ranges from 1 to 30 minutes and cannot exceed `max_duration_minutes` (default 15, max 1440). Sessions close when either limit is reached.
## Custom Viewport Size
Set custom screen dimensions for specific testing scenarios:
```python Python theme={null}
# 4K resolution
with client.Session(viewport_width=3840, viewport_height=2160) as session:
page = session.page
page.goto("https://example.com")
```
```typescript TypeScript theme={null}
import { NotteClient, type SessionResponse } from 'notte-sdk';
const client = new NotteClient();
let status: SessionResponse | undefined;
let title: string | undefined;
let viewport: { width: number; height: number } | undefined;
const session = client.Session({
viewport_width: 3840,
viewport_height: 2160,
});
await session.use(async () => {
status = await session.status();
if (status.status !== 'active') throw new Error('Session is not active');
const page = await session.page();
await page.goto('https://example.com');
title = await page.title();
console.log(`Page title: ${title}`);
viewport = await page.evaluate(() => ({
width: window.innerWidth,
height: window.innerHeight,
}));
console.log(viewport);
});
// Automatically stopped here.
```
## Cookie Management
Automatically load and save cookies across sessions using the `cookie_file` parameter:
```python cookie_file.py theme={null}
from notte_sdk import NotteClient
client = NotteClient()
with client.Session(cookie_file="cookies.json") as session:
page = session.page
page.goto("https://example.com")
# Cookies auto-loaded at start, auto-saved when session ends
```
```typescript cookie_file.ts theme={null}
import { NotteClient } from "notte-sdk";
const client = new NotteClient();
const session = client.Session({ cookie_file: "cookies.json" });
await session.use(async (session) => {
const page = await session.page();
await page.goto("https://example.com");
// Cookies auto-loaded at start, auto-saved when session ends
});
```
See the [Cookies Guide](/features/sessions/cookies) for manual cookie management and best practices.
## External Providers (CDP)
Use Notte with external browser providers like Kernel.sh:
```python Python theme={null}
from kernel import Kernel
from notte_sdk import NotteClient
client = NotteClient()
kernel = Kernel()
# Create browser on Kernel
kernel_browser = kernel.browsers.create()
# Connect Notte to Kernel's browser
with client.Session(cdp_url=kernel_browser.cdp_ws_url) as session:
page = session.page
page.goto("https://example.com")
```
See the [Kernel.sh Integration](/integrations/kernel) guide for complete setup instructions.
## Next Steps
Watch recorded sessions and debug issues
Configure proxies and anti-detection features
Watch sessions in real-time
Persist authentication across sessions
# Cookies
Source: https://docs.notte.cc/features/sessions/cookies
Persist authentication and manage cookies across sessions
Read [the Notte skill](https://notte.cc/skill.md) and follow its setup instructions when setting up Notte or building browser automation. This is the shared source for CLI setup, authentication, and the browser-to-code workflow.
Cookies allow you to persist authentication state across sessions, avoiding repeated logins and maintaining session continuity.
## Automatic Persistence
The simplest approach is automatic cookie persistence using `cookie_file`:
```python automatic_persistence.py theme={null}
from notte_sdk import NotteClient
client = NotteClient()
# Cookies automatically loaded at start, saved when session ends
with client.Session(cookie_file="cookies.json") as session:
page = session.page
page.goto("https://example.com/login")
# Log in once, cookies are captured
page.fill('input[name="email"]', "user@example.com")
page.fill('input[name="password"]', "password")
page.click('button[type="submit"]')
# Next time: already logged in!
with client.Session(cookie_file="cookies.json") as session:
page = session.page
page.goto("https://example.com/dashboard")
# Authentication persisted from previous session
```
The `cookie_file` parameter handles everything automatically - loading cookies when the session starts and saving them when it ends.
## Manual Cookie Management
For more control, use `set_cookies()` and `get_cookies()`:
```python manual_cookie_management.py theme={null}
import json
from notte_sdk import NotteClient
client = NotteClient()
# Save cookies from a session
with client.Session() as session:
page = session.page
page.goto("https://example.com")
# Perform login...
# Get and save cookies
cookies = session.get_cookies()
with open("cookies.json", "w") as f:
json.dump(cookies, f)
# Load cookies in a new session
with client.Session() as session:
with open("cookies.json", "r") as f:
cookies = json.load(f)
session.set_cookies(cookies=cookies)
page = session.page
page.goto("https://example.com/dashboard")
# Already authenticated
```
## Uploading Cookies
Upload cookies from an existing file:
```python upload_cookies.py theme={null}
from typing import Any
from notte_sdk import NotteClient
# Upload cookies for github.com to automatically login
cookies: list[dict[str, Any]] = [
{
"name": "sb-db-auth-token",
"value": "base64-cookie-value",
"domain": "github.com",
"path": "/",
"expires": 9778363203.913704,
"httpOnly": False,
"secure": False,
"sameSite": "Lax",
}
]
# create a new session
client = NotteClient()
with client.Session() as session:
_ = session.set_cookies(cookies=cookies) # type: ignore[arg-type] # can also set cookie_file="path/to/cookies.json"
# Use the cookies in your session
session.execute(type="goto", url="https://github.com/nottelabs/notte")
repo_info = session.scrape(instructions="extract the repo info and whether the user is logged in")
# or get the cookies from the session
cookies_resp = session.get_cookies()
```
### Cookie File Format
Cookies must be a valid JSON array:
```json theme={null}
[
{
"name": "session_id",
"value": "abc123",
"domain": ".example.com",
"path": "/",
"expires": 1735689600,
"httpOnly": true,
"secure": true
}
]
```
## Extracting Cookies
### From Your Browser
Export cookies from your browser:
```python extract_cookies_local.py theme={null}
import json
from pathlib import Path
from patchright.sync_api import sync_playwright
cookie_path = Path("github_cookies.json")
# Initialize Playwright
with sync_playwright() as playwright:
browser = playwright.chromium.launch(headless=False)
context = browser.new_context()
page = context.new_page()
# Navigate to login page
github_login_url = "https://github.com/login"
page.goto(github_login_url)
print("Please log into GitHub in the browser window...")
input("Press Enter after you've logged in...")
# Save cookies to file
print("Login successful. Saving cookies...")
cookies = context.cookies(urls=["https://github.com"])
if cookies:
cookie_path.write_text(json.dumps(cookies, indent=4))
print(f"Cookies saved to {cookie_path}")
else:
print("No cookies found to save.")
```
### From a Notte Session
Extract cookies programmatically:
```python extract_cookies_notte.py theme={null}
import json
from notte_sdk import NotteClient
client = NotteClient()
with client.Session() as session:
# Observe the state of a webpage
obs = session.observe(url="https://google.com/travel/flights")
# Get and save cookies from the session
with open("cookies.json", "w") as f:
cookies = session.get_cookies()
json.dump(cookies, f)
```
## Best Practices
### Security
* Store cookie files securely outside version control
* Add `*.json` cookie files to `.gitignore`
* Rotate cookies regularly for sensitive services
* Use environment-specific cookie files
### Maintenance
* Monitor cookie expiration dates
* Set up automated refresh for long-running systems
* Keep backup copies of valid cookies
### Troubleshooting
| Issue | Solution |
| - | - |
| Session not authenticated | Verify cookies haven't expired |
| Cookies not loading | Check JSON format is valid |
| Partial authentication | Ensure all required cookies are included |
| Domain mismatch | Verify cookie domains match target site |
## Next Steps
Configure sessions with all options
Persist full browser state
Secure credential storage
Manage session states
# Embed in your app
Source: https://docs.notte.cc/features/sessions/embed-iframe
Drop the live browser viewer into your own web app via iframe
Read [the Notte skill](https://notte.cc/skill.md) and follow its setup instructions when setting up Notte or building browser automation. This is the shared source for CLI setup, authentication, and the browser-to-code workflow.
You can embed any active Notte session directly into your own web app: a Next.js dashboard, an internal tool, a customer-facing demo. The live viewer is just a URL that you render inside an `