Browser automation in your terminal
Notte CLI - Browser automation in your terminal
Control browser sessions and web scraping through intuitive resource-based commands
→ Read more at: Landing • Console • Docs • X • LinkedIn
What is Notte CLI?
The Notte CLI brings the full power of notte.cc to your terminal — letting you drive browser sessions and web scraping pipelines from the command line. Pair it with shell scripts, CI/CD pipelines, or AI coding assistants for repeatable, scriptable web automation.
Features
- Browser sessions - remote Chromium/Chrome with full control
- Files - upload and download files to notte.cc
- Output formats - human-readable text or JSON for scripting
- Personas - create and manage digital identities with email, phone, and SMS
- Secure credentials - system keyring for API keys, vaults for website passwords
- Web scraping - structured data extraction with custom schemas
- Functions - schedule and execute repeatable automation tasks
Installation
Install script (macOS and Linux)
curl -fsSL https://notte.cc/install-cli.sh | sh
Installs the latest release to /usr/local/bin. Set NOTTE_VERSION to install a specific version, or NOTTE_INSTALL_DIR to install somewhere else.
Homebrew
brew tap nottelabs/notte-cli https://github.com/nottelabs/notte-cli.git
brew install notte
Go Install
go install github.com/nottelabs/notte-cli/cmd/notte@latest
Build from Source
git clone https://github.com/nottelabs/notte-cli.git
cd notte-cli
make build
Quick Start
1. Authenticate
Specify the API key using one of three methods (checked in priority order):
# 1. Via environment variable (recommended for CI/CD)
export NOTTE_API_KEY="your-api-key"
# 2. Via system keyring (recommended for local development)
notte auth login
# 3. Via config file (~/.notte/cli/config.json)
# create ~/.notte/cli/config.json and add your API key
notte auth status
2. Start a Browser Session
notte sessions start
Watch the session live through the ViewerUrl in the output.
Commands
Authentication
notte auth login # Store API key in system keychain
notte auth logout # Remove API key from keychain
notte auth status # Show authentication status
Web Search
notte search <query> # Search the web for a query
notte search <query> --depth fast|standard|deep # Tune search depth (default: standard)
notte search <query> --output-type sourcedAnswer # Get an LLM answer with sources
The query may be quoted (notte search "what is anthropic") or passed as separate
words (notte search what is anthropic). Use --output json to get the raw API
response for scripting.
Browser Sessions
notte sessions list [--page N] [--page-size N] [-a|--all] # List running sessions (-a includes stopped)
notte sessions start [flags] # Start a new session
notte sessions status # Get current session status
notte sessions status --auth # Get managed authentication readiness and operation details
notte sessions stop # Stop current session
notte sessions cookies # Get all cookies from current session
notte sessions cookies-set --file cookies.json # Set cookies in current session
notte sessions network # View network activity logs
notte sessions replay # Get session replay data
notte sessions workflow-code # Export session steps as Python code
notte sessions viewer # Open session viewer in browser
notte sessions code # Get Python script for session steps
Note: When you start a session, it automatically becomes the "current" session. All subsequent commands use this session by default. Use --session-id <session-id> only when you need to manage multiple sessions simultaneously or reference a specific session.
Session Start Options
notte sessions start \
--browser-type chromium|chrome # Browser type (default: chromium)
--idle-timeout-minutes <minutes> # Idle timeout (default: 3)
--max-duration-minutes <minutes> # Maximum session lifetime (default: 15)
--user-agent <string> # Custom user agent
--viewport-width <pixels> # Viewport width
--viewport-height <pixels> # Viewport height
--proxy # Use default proxy rotation
--proxy-country <code> # Proxy with specific country (e.g. us, gb, fr)
--no-solve-captchas # Turn OFF captcha solving (on by default)
--no-file-storage # Detach FileStorage (attached by default).
# Disables page download and files --from session
--advanced-stealth # Highest-fidelity browser for sites with
# sophisticated bot detection (approved workspaces)
--cdp-url <url> # CDP URL of remote session provider
--profile-id <id> # Profile ID to use for session
--profile-persist # Save browser state to profile on close
--vault-id <id> # Vault used to resolve credential fields
--screenshot-type <type> # Screenshot type (raw, full, last_action)
--chrome-args <args> # Chrome instance arguments (repeatable)
Page Actions
Interact with pages using simplified commands (requires an active session). Start
the session with --vault-id <id> before using page fill --vault-field:
notte page observe # Get page state and available actions
notte page scrape --instructions "..." # Scrape content from the page
notte page click "@B3" # Click an element by ID
notte page fill "@I1" "text" # Fill an input field
notte page fill "#email" --vault-field email # Fill from the session vault
notte page fill "#password" --vault-field password # Fill the stored password
notte page fill "#card-number" --vault-field card_number
notte page fill "#card-name" --vault-field card_holder_name
notte page fill "#card-expiry" --vault-field card_expiration
notte page fill "#card-cvc" --vault-field card_cvv
notte page goto "https://example.com" # Navigate to a URL
notte page back # Go back in history
notte page forward # Go forward in history
notte page scroll-down [amount] # Scroll down the page
notte page scroll-up [amount] # Scroll up
notte page press "Enter" # Press a key
notte page screenshot # Take a screenshot
notte page select <id> "option" # Select dropdown option
notte page check <id> # Check/uncheck checkbox
notte page upload <id> --file <name> # Fill a file input. <name> is a file in your
# uploads store, not a local path - send it with
# `notte files upload` first
notte page download <id> # Download by clicking. The file lands in the
# session store; retrieve it with
# `notte files download <name> --from session`
notte page new-tab <url> # Open URL in new tab
notte page switch-tab <index> # Switch to tab by index
notte page close-tab # Close current tab
notte page reload # Reload page
notte page wait <seconds> # Wait for duration
notte page captcha-solve # Solve captcha
notte page eval-js "document.title" # Evaluate JavaScript in the page
Evaluating JavaScript
page eval-js prints the evaluated value alone on stdout — objects and
arrays as JSON, a JS null as null — with the status line on stderr, so it
captures and pipes without post-processing:
title=$(notte page eval-js "document.title")
notte page eval-js "JSON.stringify([...document.querySelectorAll('a')].map(a => a.href))" | jq length
Return JSON.stringify(...) when the answer is structured. console.log output
is discarded — only the returned value comes back. A failing script exits
non-zero and reports the actual JavaScript error; use -o json to get the full
execution result instead of the bare value.
Functions
notte functions list [--page N] [--page-size N] [--include-deleted] # List functions
notte functions create --file workflow.py # Create a new function
notte functions show # View current function details
notte functions show --function-id <id> # View specific function details (different from current function)
notte functions download workflow.py [--version <version>] # Download current function code to disk
notte functions create --file workflow.py --response-format @schema.json # ... with its response documented
notte functions update --file workflow.py # Update current function code
notte functions update --file workflow.py --response-format @schema.json # ... and re-document its response
notte functions configure --run-instructions "..." --self-healing # Set usage notes and self-healing
notte functions configure --response-format @schema.json # Document run()'s return schema without re-upload
notte functions rollback --version <version> # Restore an earlier version (see `versions` in show)
notte functions health # Runtime health: Python version, installed packages, reachability
notte functions delete # Delete current function
notte functions fork # Fork current function to new version
notte functions run # Execute current function
notte functions run --no-stream # Poll until complete, returning final run metadata without log output
notte functions run --no-wait # Return the run ID after startup
notte functions run-metadata --wait --run-id <id> [--wait-timeout 30m] # Wait for an existing run
notte functions runs [--page N] [--page-size N] [--running] # List runs for current function (--running = in-flight only)
notte functions run-stop --run-id <id> # Stop a running function execution
notte functions run-metadata --run-id <id> # Get run logs and results
notte functions schedule --cron "0 12 ? * * *" # Schedule current function (six-field cron: daily at noon UTC)
notte functions unschedule # Remove schedule from current function
Function runs print their ID and a tracking command to stderr as soon as the
server creates the run. After the runner accepts the request, the CLI closes the
response stream and polls metadata. --timeout applies to each API request;
--wait-timeout limits the total wait (unlimited by default). --no-wait returns
after startup so another command can wait separately.
In JSON mode, stdout contains one object: the run ID and status with --no-wait,
or final run metadata (including status, logs, and the stored result string)
when waiting. Logs are printed to stderr as they become available in metadata;
some runners persist logs only at completion. --no-stream suppresses those
log messages. An interrupted or timed-out CLI does not cancel the server run:
resume with run-metadata --wait, or explicitly stop it with run-stop.
Personas, Profiles and Usage
notte personas update --persona-id <id> --name "checkout tester" # Rename a persona
notte profiles cookies --profile-id <id> # Read a profile's cookies
notte profiles cookies-set --profile-id <id> --file cookies.json # Import cookies into a profile
notte usage logs [--endpoint /sessions/start] [--page N] # List API requests made with your key
profiles cookies-set takes either a bare array of cookies — what Playwright's
storageState and the browser extensions export — or an object with a cookies
key. Add --source-format chrome if they came from Chrome, and --mode append
to add to the profile's cookies rather than replace them.
--response-format takes a JSON Schema describing what run() returns, as
inline JSON, @file.json, or - for stdin. The API never derives it, so a
function created without it has no documented response — which is what the
console reads to show callers the shape they will get back. From a pydantic
return model:
python -c 'import json, typing, client; print(json.dumps(typing.get_type_hints(client.run)["return"].model_json_schema()))' > schema.json
notte functions create --file client.py --response-format @schema.json
# Or document it later without re-uploading the code:
notte functions configure --response-format @schema.json
--run-instructions is documentation for whoever calls the function — how long a
run takes, what each variable is for, which sites it trips over:
notte functions configure --run-instructions "Takes ~3 min, so call it with --no-wait. \
Hits a captcha on the login page every few runs. \
\`query\` is the search term; \`max_items\` caps the results."
It is not input to the self-healing agent, which is the separate
--self-healing flag.
configure sends only the flags you pass, so setting --run-instructions leaves
self-healing untouched. Disable self-healing with --self-healing=false: the
API treats an absent field as "leave it alone" rather than "off". Note that it
can only be enabled on functions an agent built — a CLI-created function has no
thread for the healer to resume, and the API rejects it.
Note: When you create a function, it automatically becomes the "current" function. All subsequent commands use this function by default. Use --function-id <function-id> only when you need to manage multiple functions simultaneously or reference a specific function.
Vaults
notte vaults list [--page N] [--page-size N] [--include-deleted] # List all vaults
notte vaults create # Create a new vault
notte vaults update --vault-id <id> # Update vault metadata
notte vaults delete --vault-id <id> # Delete a vault
notte vaults credentials list --vault-id <id> # List all credentials
notte vaults credentials add --vault-id <id> # Add credentials
notte vaults credentials get --vault-id <id> # Get credentials for URL
notte vaults credentials delete --vault-id <id> # Delete credentials
Personas
notte personas list [--page N] [--page-size N] [--include-deleted] # List all personas
notte personas create # Create a new persona
notte personas show --persona-id <id> # View persona details
notte personas delete --persona-id <id> # Delete a persona
notte personas emails --persona-id <id> # List emails
notte personas sms --persona-id <id> # List SMS messages
Profiles
notte profiles list [--page N] [--page-size N] [--name "..."] [--include-deleted] # List all profiles
notte profiles create # Create a new profile
notte profiles show --profile-id <id> # View profile details
notte profiles delete --profile-id <id> # Delete a profile
notte profiles sync # Copy your local browser login into a profile
profiles sync reads the cookies from a local Firefox, Chrome, Brave, Edge or
Chromium profile and uploads them into a Notte profile, so remote sessions start
already logged in. The cookies are read from a copy of the browser's own files on
your machine (and, for the Chromium browsers, decrypted with the key from your OS
keychain); nothing is sent until you confirm, and --domain limits the sync to
the sites you name.
notte profiles sync # pick a browser profile, create a Notte profile
notte profiles sync --domain github.com --domain mail.google.com # only these sites (and subdomains)
notte profiles sync --profile-id <id> # refresh an existing profile after a re-login
notte profiles sync --browser brave --local-profile Work # choose the source without the prompt
Then start a session with it: notte sessions start --profile-id <id>. Re-run
sync whenever the local login changes. Supported on macOS and Linux; for the
Chromium browsers the first run asks permission to read the browser key from your
OS keychain (Firefox stores cookies in the clear and needs no such permission).
Scoped syncs into an existing profile may want --mode append so other sites'
cookies are kept.
Files
notte files upload <path> # Upload a persistent input file
notte files list --from uploads # List persistent input files
notte files download <filename> --from uploads # Download a persistent input file
notte files list --from session [--session-id <id>] # List files produced by a session
notte files download <filename> [--session-id <id>] # Download a file produced by a session
Utilities
notte usage # View API usage statistics
notte health # Check API health status
notte version # Show CLI version
Output Formats
Text
Human-readable tables with colors and formatting:
$ notte sessions list
ID STATUS BROWSER CREATED
ses_abc123def456 ACTIVE chromium 2024-01-15 10:30:00
ses_xyz789uvw012 STOPPED chrome 2024-01-15 09:15:00
JSON
Machine-readable output:
$ notte sessions list --output json
{
"sessions": [
{
"id": "ses_abc123def456",
"status": "ACTIVE",
"browser": "chromium",
"created_at": "2024-01-15T10:30:00Z"
}
]
}
Data goes to stdout, errors and progress to stderr for clean piping.
Examples
Automated Web Scraping Pipeline
# Start session (automatically becomes the current session)
notte sessions start
# Navigate to page
notte page goto "https://news.ycombinator.com"
# Extract structured data
notte page scrape --instructions "Extract top 10 stories with title and URL"
# Cleanup
notte sessions stop
Running a Workflow
# List functions to find ID
notte functions list
# Run workflow
notte functions run --function-id func_abc123
Managing Credentials Securely
# Create a vault for production credentials
VAULT_ID=$(notte vaults create --name "Production Sites" -o json | jq -r '.id')
# Add website credentials
notte vaults credentials add --vault-id $VAULT_ID \
--username "admin@example.com" \
--password "$SECURE_PASSWORD" \
--url "https://app.example.com"
# List stored credentials
notte vaults credentials list --vault-id $VAULT_ID
Multi-Step Browser Automation
# Start browser with specific configuration
notte sessions start \
--browser-type chrome \
--viewport-width 1920 \
--viewport-height 1080
# Navigate and interact
notte page goto "https://example.com"
notte page click "#login-button"
notte page fill "#username" "user@example.com"
# Get current page state with available actions
notte page observe
# Stop when done
notte sessions stop
JQ Filtering
# Get only active sessions (using built-in filter)
notte sessions list --all
# Paginate through results
notte sessions list --page 2 --page-size 5
# Extract session IDs with jq
notte sessions list --output json | jq -r '.sessions[].id'
Usage with AI Agents
Just Ask the Agent
The simplest approach - just tell your agent to use it:
Use notte to test the login flow. Run
notte --helpto see available commands.
The --help output is comprehensive and most agents can figure it out from there.
AI Coding Assistants
Add the skill to your AI coding assistant for richer context:
npx skills add nottelabs/notte-skills
This works with Claude Code, Cursor, Windsurf, and other MCP-compatible assistants.
AGENTS.md / CLAUDE.md
For more consistent results, add to your project or global instructions file:
## Browser Automation
Use `notte` for web automation. Run `notte --help` for all commands.
Core workflow:
1. `notte sessions start` - Start a browser session
2. `notte page goto <url>` - Navigate to a URL
3. `notte page observe` - Get interactive elements with IDs (@B1, @B2)
4. `notte page click "@B1"` / `notte page fill "@I1" "text"` - Interact using element IDs
5. `notte page scrape --instructions "..."` - Extract structured data
6. `notte sessions stop` - Clean up when done
Tips
- Viewing sessions: When you start a session, the output includes a
ViewerUrl- open it to watch the browser live - Session lifetime: sessions close after 3 minutes idle or 15 minutes total by default. Raise
--idle-timeout-minutes/--max-duration-minutesfor anything slow, or the next command fails withSession closed - Element selectors: If element IDs from
observe(like@B1) don't work, use Playwright selectors:#id,.class,button:has-text('Submit') - Multiple matches: Use
>> nth=0suffix to select the first match:button:has-text('OK') >> nth=0 - Closing modals:
notte page press "Escape"reliably dismisses most dialogs
Skills Documentation
For comprehensive documentation including templates and reference guides, see the notte-skills/plugins/notte-cli/skills/notte-browser folder (vendored as a submodule from nottelabs/notte-skills).
Security
Credential Storage
API keys are stored securely in your system's keychain:
- macOS: Keychain Access
- Linux: Secret Service (GNOME Keyring, KWallet)
- Windows: Credential Manager
Best Practices
- Never pass API keys on the command line
- Use vaults for website passwords and payment cards
- Rotate API keys regularly from notte.cc dashboard
- Use
notte auth logoutto remove stored keys
Shell Completions
Generate shell completions for your preferred shell:
Bash
# macOS (Homebrew):
notte completion bash > $(brew --prefix)/etc/bash_completion.d/notte
# Linux:
notte completion bash > /etc/bash_completion.d/notte
# Or source directly:
source <(notte completion bash)
Zsh
notte completion zsh > "${fpath[1]}/_notte"
Fish
notte completion fish > ~/.config/fish/completions/notte.fish
PowerShell
notte completion powershell | Out-String | Invoke-Expression
Development
After cloning, install git hooks:
make setup
This installs lefthook pre-commit and pre-push hooks for linting and testing.
License
This project is licensed under the MIT License.
Links
Copyright © 2025 Notte Labs, Inc.
Coverage guards
Two checks keep the CLI in step with what surrounds it. Both read a live source
of truth, so both need the network; make runs them with -strict, where an
unreachable source is a failure. The pre-commit hooks run them without it, so an
offline commit warns and proceeds.
make check-endpoints # every API endpoint is reachable or recorded as skipped
make check-skills # every command is documented in nottelabs/notte-skills
make check-coverage # both
check-endpoints compares the API's OpenAPI spec against the commands. An
endpoint is covered when a command calls its generated client method; anything
else has to be listed in scripts/endpoint-coverage.txt
with a reason, as manual (a command builds the request itself) or skip (not
exposed on purpose). A line whose endpoint the API no longer serves fails too,
so the file cannot rot the way the flag generator's endpoint map did.
check-skills walks the cobra tree and requires every non-hidden command to be
mentioned somewhere under plugins/notte/skills/ in the skills repository. Pass
-skills-dir <path> to check against a working copy before pushing it:
go run ./scripts/checkcoverage -check skills -skills-dir ../notte-skills -strict
Session payments
Connect your wallet first. This returns a URL and phrase; authorize in your browser.
Completion is recorded in the background. Re-run the same command to confirm
connected or retrieve the pending link:
notte payment connect --mode test -o json
Then request spending for an existing session (amounts use currency units):
notte payment request --session-id "$SESSION_ID" --mode test --amount 1.00 --currency usd \
--merchant-url https://example.com --merchant-name Example \
--description "Buy one sandbox item for this browser session, with a maximum total of one US dollar including all applicable fees." \
--idempotency-key "$REQUEST_KEY" -o json
notte payment status "$PAYMENT_ID" -o json
notte payment wait "$PAYMENT_ID" --wait-timeout 10m -o json
When --mode is omitted, both commands use the API default. Use --mode test on both commands for a development/test payment. Wallet
connections are scoped to your authenticated account and mode. Requests fail with
wallet_not_connected until connection completes; they do not start connection
or reserve a card slot. Once connected, a request provides a spending approval
URL. Send that URL to the user before waiting for the card.
To switch wallets, run notte payment disconnect --mode live, then
notte payment connect --mode live. Disconnect affects only the selected mode
and refuses while payments or card cleanup are still active. Use --mode test
for the test wallet. Omitting the mode uses the API default.
If Link requires additional wallet verification, payment wait prints the action
URL. Resumable verification keeps waiting on the same request. When Link requires
a new spend request, the command exits with instructions to complete verification
and request payment again with a new idempotency key. payment status -o json
includes the structured next_action instructions.
wait displays approval and verification instructions on stderr as they become
available and prints one final result on stdout when ready. It exits nonzero on
failure, expiration, decline, session closure, or timeout. Stopping the CLI does
not cancel provisioning; resume with the same payment ID. Requests print their
idempotency key on stderr; reuse it with the same inputs to recover an uncertain
request without creating another one.
ready means the temporary card is available for existing vault placeholders.
It does not confirm a merchant purchase. Payment commands never return card
numbers, security codes, or wallet tokens. Descriptions must contain 100 to 4000
characters. --amount 100.91 --currency usd requests USD 100.91. The backend
validates currency precision without rounding and enforces a 50,000-minor-unit
limit (USD 500.00).
The deployed sandbox lifecycle test runs real CLI subprocesses and creates a short-lived browser session and unapproved test spend request. It checks request replay, status, wait timeout, and session-close cleanup without approving a wallet request or submitting a merchant payment:
NOTTE_API_URL=https://YOUR_ENABLED_TEST_DEPLOYMENT \
NOTTE_PAYMENT_E2E_REQUIRED=1 \
go test -tags=integration ./tests/integration \
-run '^TestPaymentSandboxLifecycle$' -count=1 -v
Supply NOTTE_API_KEY through the environment. The normal integration suite
skips this case only when the API explicitly returns payments_disabled.
NOTTE_PAYMENT_E2E_REQUIRED=1 makes that condition fail instead, so a release
validation cannot count a disabled feature as a passed payment test. Auth and
configuration errors always fail. Use a dedicated test principal without someone
concurrently approving its payment requests.