notebooklm-py Documentation

Unofficial Python API and agentic skill for Google Gemini Notebook. Full programmatic access to NotebookLM's features—including capabilities the web UI...

RAW Doc

File: README.md

notebooklm-py

A Comprehensive Google Gemini Notebook Skill & Unofficial Python API. Full programmatic access to NotebookLM's features—including capabilities the web UI doesn't expose—via Python, CLI, and AI agents like Claude Code, Codex, and OpenClaw.

Note (July 2026): Google rebranded NotebookLM to Gemini Notebook. It remains the same standalone product (now also reachable inside the Gemini app), existing links redirect automatically, and this library drives the same underlying service and works unchanged. The package keeps the notebooklm-py name.




Source & Development: https://github.com/teng-lin/notebooklm-py

⚠️ Unofficial Library - Use at Your Own Risk

This library uses undocumented Google APIs that can change without notice.

  • Not affiliated with Google - This is a community project
  • APIs may break - Google can change internal endpoints anytime
  • Rate limits apply - Heavy usage may be throttled

Best for prototypes, research, and personal projects. See Troubleshooting for debugging tips.

What You Can Build

🤖 AI Agent Tools - Integrate NotebookLM into Claude Code, Codex, and other LLM agents. Ships with a root NotebookLM skill for GitHub and npx skills add discovery, local notebooklm skill install support for Claude Code and .agents skill directories, and repo-level Codex guidance in AGENTS.md.

📚 Research Automation - Bulk-import sources (URLs, PDFs, YouTube, Google Drive), run web/Drive research queries with auto-import, and extract insights programmatically. Build repeatable research pipelines.

🎙️ Content Generation - Generate Audio Overviews (podcasts), videos, slide decks, quizzes, flashcards, infographics, data tables, mind maps, and study guides. Full control over formats, styles, and output.

📥 Downloads & Export - Download all generated artifacts locally (MP3, MP4, PDF, PNG, CSV, JSON, Markdown). Export to Google Docs/Sheets. Features the web UI doesn't offer: batch downloads, quiz/flashcard export in multiple formats, mind map JSON extraction.

Use Cases & Recipes

NotebookLM is a grounded engine: Gemini does the heavy reading and answers from your sources with citations. The winning pattern is to let it do the expensive analysis while your agent (Claude Code, Codex, …) orchestrates and handles the final mile — using NotebookLM as a zero-token synthesis + memory layer an agent drives in a loop, and pulling structured artifacts out in bulk and in richer, scriptable formats. Recipes people build on top of this library, grouped by what they use NotebookLM as:

Spend fewer tokens — let NotebookLM do the expensive thinking:

  • 🪙 Zero-token research offload — Throw 30 documents into a notebook, let Gemini do the heavy analysis, and have your agent spend tokens only on the final polish. The agent just orchestrates (create → source add → ask); the reasoning happens server-side. In the wild: a four-workflow guide to stop Claude Code burning tokens on NotebookLM.
  • 🧠 Knowledge distillation → a permanent skill — Run Deep Research (source add-research "your topic" --mode deep) or load a doc corpus, let NotebookLM's Gemini condense it, and bake the result into a SKILL.md your agent loads at startup — build once, reuse with zero runtime tokens or network calls, git-versioned and immune to UI drift. A packaged domain expert without hand-curating sources. (Dumping raw docs into a skill flattens the hierarchy; NotebookLM condensing first is what makes it work.)
  • ✅ Self-validating skills — Have NotebookLM generate the eval set — a quiz straight from your sources — to grade an agent skill against ground truth instead of test questions you'd bias yourself. Build the skill, run it against the NotebookLM-authored evals, iterate to a pass. In the wild: a skill that scored 4/10 on the first pass and 10/10 after one iteration, graded by a NotebookLM-generated quiz.

Give your agent memory — persistent, grounded recall:

  • 💾 Persistent cross-session memory — Keep a "Master Brain" notebook; a wrap-up step appends each session's decisions and fixes as notes (note create / ask --save-as-note), and a line in your CLAUDE.md queries it (ask) at the start of the next session. Storage and recall live on Google's infrastructure.
  • 🧩 Grounded memory for coding agents — Expose a notebook of your internal docs/RFCs/architecture over the MCP server (or plain ask) so an agent answers from your code with citations rather than plausible-sounding guesses — a zero-infra alternative to standing up your own vector DB and embedding pipeline. In the wild: turning a notebook into the source-grounded "project brain" a coding agent consults before it writes code.
  • 🪞 Query your own notes / journal — Load years of daily notes, meeting logs, or a journal and ask for cited answers across your own history — surfacing long-term patterns a keyword search can't (e.g. a weekly summary synthesized from 282 daily notes, every claim linked back to the entry it came from). In the wild: chatting with a year of daily notes as a cited knowledge base.

Turn your sources into answers & artifacts — cited responses, generated media, and exports:

  • 📞 Grounded knowledge base / troubleshooting oracle (RAG) — Load product docs, FAQs, RFCs, and past tickets, then ask --json for source-grounded, cited answers for support, on-call, or internal Q&A. Or have an agent point it at an entire fast-moving tool's docs — more than the agent can hold in context — as a troubleshooting oracle it queries the moment it hits an error. In the wild: OpenClaw drove the library to scrape all 524 pages of docs.openclaw.ai, dedupe the duplicate translations, and audit it down to 269 clean sources (missing/extra/duplicate = 0).
  • 🔁 Multi-format content repurposing — One source set, every format: generate audio (podcast), generate video, generate slide-deck, plus a generate report blog draft, generate quiz, and generate flashcards — fan a single notebook out across channels.
  • 📤 Bulk, scriptable exports — Pull mind maps as JSON, flashcards/quizzes as JSON/Markdown/HTML, data tables as CSV, and reports as Markdown — in bulk, to local files, straight into Anki, your mind-mapping tool, or a repo (download <type> / download <type> --all). The programmatic "get data out" half of the library, not just "put sources in."
  • 🕸️ Obsidian / knowledge-graph sync — Run the CLI from your vault root so downloaded artifacts (reports, mind-map JSON, transcripts) land as files in your knowledge graph; community skills built on this library even resolve NotebookLM's citation markers into Obsidian [[wikilinks]]. Pair with a podcast overview for an audio digest of your notes. In the wild: "Claude Code + NotebookLM + Obsidian = GOD MODE".

Run it unattended, at scale, or on the go — scheduled, headless, and remote:

  • 🚨 Incident runbook generator — On an alert, spin up a notebook of the relevant docs, ask targeted diagnostic questions, and generate a briefing-doc report (generate report --format briefing-doc --wait, then download report) as an automated runbook.
  • 📚 Curriculum / study-set builder — Scrape a syllabus or developer roadmap, create one notebook per topic (with deliberate pacing to dodge rate limits), and bulk-generate podcasts, quizzes, and flashcards for each.
  • 📰 Scheduled audio briefings — Pair auth refresh --quiet (cron/launchd/systemd) with generate audio to publish a fresh personalized briefing to a podcast feed on a schedule.
  • 📱 NotebookLM from your phone, agent-driven — Self-host the remote MCP connector behind a Cloudflare/Tailscale tunnel and add it as a custom connector on the web (claude.ai Connectors, or ChatGPT with Developer Mode). Then drive the full toolset — deep research, source ingestion, studio generation, cited Q&A — from the claude.ai mobile app on the go (ChatGPT's MCP connectors are web-only), chained with your other MCP tools, instead of app-hopping.

These combine ordinary library primitives — see the CLI Reference and Python API. The agent-side glue (skills, scheduling, vault layout) lives in your own setup, not this package. Per-notebook source counts depend on your Google account tier — split across notebooks if you hit a cap.

New here? Start with a walkthrough: Claude Code + NotebookLM = CHEAT CODE (video) · 5 demos + 50 use cases, with prompts.

Ways to Use

Method Best For
Python API Application integration, async workflows, custom pipelines
CLI Shell scripts, quick tasks, CI/CD automation
MCP Server Claude Desktop/Code, Codex, etc. — locally via stdio, or as a self-hosted remote connector (behind a Cloudflare/Tailscale tunnel) reachable from claude.ai and ChatGPT, mobile included.
REST Server Local automation over guarded HTTP routes without spawning a CLI process per call
Agent Integration Claude Code, Codex, LLM agents, natural language automation

Features

Complete NotebookLM Coverage

Category Capabilities
Notebooks Create, list, rename, delete
Sources URLs, YouTube, files (PDF, text, Markdown, Word, EPUB, audio, video, images), Google Drive, pasted text; refresh, get guide/fulltext
Chat Questions, conversation history, custom personas, suggested starter prompts
Notes Create, list, rename, delete, save chat answers, save conversation history
Source Labels AI-generated or manual topic labels; add/remove source membership; filter sources by label
Research Web and Drive research agents (fast/deep modes) with auto-import
Sharing Public/private links, user permissions (viewer/editor), view level control

Content Generation (All Artifact Types)

Type Options Download Format
Audio Overview 4 formats (deep-dive, brief, critique, debate), 3 lengths, 50+ languages MP3
Video Overview 4 formats (explainer, brief, cinematic, short), 8 visual styles (+ auto/custom), plus a dedicated cinematic-video CLI alias MP4
Slide Deck Detailed or presenter format, adjustable length; individual slide revision PDF, PPTX
Infographic 3 orientations, 3 detail levels PNG
Quiz Configurable quantity and difficulty JSON, Markdown, HTML
Flashcards Configurable quantity and difficulty JSON, Markdown, HTML
Report Briefing doc, study guide, blog post, or custom prompt Markdown
Data Table Custom structure via natural language CSV
Mind Map Hierarchical node tree — two kinds: note-backed JSON or the newer interactive studio map (--kind / MindMapKind) JSON

Beyond the Web UI

Programmatic, batch, and local-file capabilities the API/CLI make easy — several in richer formats, or at a scale, than clicking through the web app:

  • Batch downloads - Download all artifacts of a type at once
  • Quiz/Flashcard export - Get structured JSON, Markdown, or HTML files
  • Mind map data extraction - Export hierarchical JSON for visualization tools
  • Data table CSV export - Download structured tables as spreadsheets
  • Slide deck as PPTX or PDF - Download editable PowerPoint or PDF files
  • Slide revision - Modify individual slides with natural-language prompts
  • Report template customization - Append extra instructions to built-in format templates
  • Save chat history to notes - Persist a whole Q&A conversation (not just a single answer) as a notebook note
  • Source fulltext access - Retrieve the indexed text content of any source
  • Programmatic sharing - Manage permissions without the UI

Installation

The full install guide — six personas (agent, end-user, library, headless, contributor, power-user), optional extras matrix, platform notes — lives in docs/installation.md.

Quickest start (CLI users and AI agents) — install the CLI with uv tool (recommended) or pipx:

bash
uv tool install "notebooklm-py[browser]"   # or: pipx install "notebooklm-py[browser]"
notebooklm login                           # first run auto-downloads Chromium (~170 MB), then Google sign-in
notebooklm auth check --test --json        # verify: expect "status": "ok"

Why uv tool / pipx? They install the CLI into its own isolated environment and put notebooklm on your PATH — no dependency clashes with other tools, a one-line upgrade (uv tool upgrade notebooklm-py) or uninstall, and, crucially, they work on modern macOS (Homebrew Python) and Debian/Ubuntu where a system-wide pip install is blocked with error: externally-managed-environment (PEP 668). No uv yet? curl -LsSf https://astral.sh/uv/install.sh | sh (or brew install uv / winget install astral-sh.uv).

Prefer plain pip? It works the same inside a virtualenv (and directly on Windows, where Python isn't externally-managed):

bash
python3 -m venv .venv && source .venv/bin/activate   # Windows: .venv\Scripts\activate
pip install "notebooklm-py[browser]"

As a library (embedded in your app — no Playwright, no Chromium):

bash
uv add notebooklm-py                    # or, inside a virtualenv: pip install notebooklm-py

If playwright install chromium fails on Linux with TypeError: onExit is not a function, see the Linux workaround. Contributors: see CONTRIBUTING.md.

Authentication & Access

Flexible auth for local dev, headless servers, and multi-tenant setups:

  • Three ways to get cookies - Interactive Playwright login (default), import from an already-signed-in browser (login --browser-cookies chrome, no Playwright), or a durable master token.
  • Master-token auth - Mints fresh web cookies on demand with no per-session browser (login --master-token --account [email protected]), so it self-heals expired sessions unattended — the auth model for servers, CI, and the remote MCP connector (claude.ai / ChatGPT).
  • Multi-account profiles - Switch between Google accounts without re-authenticating.

Agent Setup

Option 1 — CLI install:

bash
notebooklm skill install

Installs the skill into ~/.claude/skills/notebooklm and ~/.agents/skills/notebooklm.

Option 2 — npx install (via the open skills ecosystem):

bash
npx skills add teng-lin/notebooklm-py

Fetches the canonical SKILL.md directly from GitHub.

Quick Start

CLI

text
/* Detailed source-code truncated for AI context efficiency. */

Other useful CLI commands:

bash
notebooklm auth check --test         # Diagnose auth/cookie issues
notebooklm auth refresh --quiet      # One-shot cookie keepalive (for cron / launchd / systemd)
notebooklm auth refresh --browser-cookies chrome  # Re-extract and repair account routing
notebooklm auth inspect --browser 'chrome::Profile 1'  # Preview one Chromium profile
notebooklm agent show codex          # Print bundled Codex instructions
notebooklm agent show claude         # Print bundled Claude Code skill template
notebooklm language list             # List supported output languages
notebooklm metadata --json           # Export notebook metadata and sources
notebooklm share status              # Inspect sharing state
notebooklm source add-research "AI" --import-all  # web research + import found sources
notebooklm skill status              # Check local agent skill installation
notebooklm profile list              # List all Google account profiles
notebooklm profile switch work       # Switch active account profile

Use --prompt-file PATH with ask, prompt-based generate commands, and source add-research when the text is too long for the shell command line. This reads prompt/query text from a file and is separate from source add ./file.pdf, which still uploads that file as a NotebookLM source.

Python API

python
import asyncio
from notebooklm import NotebookLMClient, MindMapKind


async def main():
    async with NotebookLMClient.from_storage() as client:
        # Create notebook and add sources
        nb = await client.notebooks.create("Research")
        await client.sources.add_url(nb.id, "https://example.com", wait=True)

        # Chat with your sources
        result = await client.chat.ask(nb.id, "Summarize this")
        print(result.answer)

        # Generate content (podcast, video, quiz, etc.)
        status = await client.artifacts.generate_audio(nb.id, instructions="make it fun")
        await client.artifacts.wait_for_completion(nb.id, status.task_id)
        await client.artifacts.download_audio(nb.id, "podcast.m4a")

        # Generate quiz and download as JSON
        status = await client.artifacts.generate_quiz(nb.id)
        await client.artifacts.wait_for_completion(nb.id, status.task_id)
        await client.artifacts.download_quiz(nb.id, "quiz.json", output_format="json")

        # Generate a mind map via the unified client.mind_maps API (issue #1256) —
        # two kinds: the newer MindMapKind.INTERACTIVE studio map (shown; polled to
        # completion by default) or MindMapKind.NOTE_BACKED JSON. Both export via:
        mm = await client.mind_maps.generate(nb.id, kind=MindMapKind.INTERACTIVE)
        await client.artifacts.download_mind_map(nb.id, "mindmap.json", mm.id)


asyncio.run(main())

Documentation

For Contributors

License

MIT License. See LICENSE for details.


File: docs/cli-reference.md

CLI Reference

Status: Active
Last Updated: 2026-08-12

Complete command reference for the notebooklm CLI—providing full programmatic access to all NotebookLM features, including capabilities not exposed in the web UI.

Exit codes: every command follows the convention documented in CLI Exit-Code Convention (0 success, 1 user/app error, 2 system/unexpected, 130 SIGINT). Two commands intentionally deviate for shell control-flow use (source stale --exit-on-stale opts into an inverted predicate; source wait is three-way); see the doc for details.

Command Structure

bash
notebooklm [-p PROFILE] [--storage PATH] [--version] [-v|--quiet] <command> [OPTIONS] [ARGS]

Global Options

  • -p, --profile NAME - Use a named profile (overrides NOTEBOOKLM_PROFILE env var)
  • --storage PATH - Override the default storage location
  • -v, --verbose - Increase verbosity (-v for INFO, -vv for DEBUG)
  • --quiet - Suppress status output and INFO/WARN log records (only errors survive). Structured --json payloads are still emitted. Mutually exclusive with -v/-vv; combining the two raises UsageError (exit 2).
  • --version - Show version and exit
  • --help - Show help message

Environment Variables

  • NOTEBOOKLM_HOME - Base directory for all config files (default: ~/.notebooklm)
  • NOTEBOOKLM_PROFILE - Active profile name (default: default)
  • NOTEBOOKLM_NOTEBOOK - Default notebook ID for any command exposing -n/--notebook. Resolution order: -n/--notebook flag > NOTEBOOKLM_NOTEBOOK env > active context (notebooklm use) > error. Empty/whitespace values are treated as unset.
  • NOTEBOOKLM_AUTH_JSON - Inline authentication JSON (for CI/CD, no file writes needed)
  • NOTEBOOKLM_HL - Default output language for artifact generation (overridden by --language)
  • NOTEBOOKLM_LOG_LEVEL - Override the package logger level (e.g. DEBUG, INFO, WARNING, ERROR)
  • NOTEBOOKLM_DEBUG_RPC - Enable RPC debug logging (1 to enable)
  • NOTEBOOKLM_QUIET_DEPRECATIONS - Suppress deprecation notices (1 to enable)

See Configuration for full env-var precedence and CI/CD setup.

Command Organization

  • Session commands - Authentication and context management
  • Notebook commands - CRUD operations on notebooks
  • Chat commands - Querying and conversation management
  • Grouped commands - source, label, collection, artifact, agent, generate, download, note, share, research, language, skill, auth, profile, mcp
  • Utility commands - metadata, doctor

Quick Reference

Session Commands

Command Description Example
login Authenticate via browser notebooklm login / notebooklm login --browser msedge
login --master-token Headless auth: mint cookies from a durable master token ([headless]) notebooklm login --master-token --account [email protected]
use Set active notebook notebooklm use abc123
status Show current context notebooklm status
status --paths Show configuration paths notebooklm status --paths
status --json Output status as JSON notebooklm status --json
clear Clear current context notebooklm clear
auth check Diagnose authentication issues notebooklm auth check
auth check --test Validate with network test notebooklm auth check --test
auth check --json Output as JSON notebooklm auth check --json
auth inspect List Google accounts visible to a browser cookie store (read-only) notebooklm auth inspect --browser chrome
auth import-cookies Import auth cookies from a JSON file (or stdin) into the active profile notebooklm auth import-cookies cookies.json
auth logout Clear saved cookies and cached browser profile notebooklm auth logout
auth refresh One-shot SIDTS rotation poke (for OS schedulers) notebooklm auth refresh
auth refresh --quiet Refresh; suppress success output notebooklm auth refresh --quiet
doctor Check environment health notebooklm doctor
doctor --fix Auto-fix detected issues notebooklm doctor --fix
doctor --json Output diagnostics as JSON notebooklm doctor --json
completion <shell> Print shell completion script (bash/zsh/fish) notebooklm completion zsh > ~/.zfunc/_notebooklm

Profile Commands (`notebooklm profile `)

Command Description Example
list List all profiles notebooklm profile list
create <name> Create a new profile notebooklm profile create work
switch <name> Set the active profile notebooklm profile switch work
delete <name> Delete a profile notebooklm profile delete old
rename <old> <new> Rename a profile notebooklm profile rename old new

Language Commands (`notebooklm language `)

Command Description Example
list List all supported languages notebooklm language list
get Show current language setting notebooklm language get
get --local Show local config only (skip server sync) notebooklm language get --local
set <code> Set language for artifact generation notebooklm language set zh_Hans
set <code> --local Set local config only (skip server sync) notebooklm language set ja --local

Note: Language is a GLOBAL setting that affects all notebooks in your account.

Notebook Commands

Command Description Example
list List all notebooks notebooklm list
list --json Output as JSON notebooklm list --json
list --limit N Show at most N notebooks (default: unlimited) notebooklm list --limit 10
list --no-truncate Do not truncate the Title column notebooklm list --no-truncate
use Set active notebook (verifies existence by default) notebooklm use abc123
use --force Skip the existence check (offline / debugging) notebooklm use abc123 --force
use --json Emit {active_notebook_id, success, verified, notebook} envelope notebooklm use abc --json
create <title> Create notebook (does not change active context) notebooklm create "Research"
create <title> --use Create notebook and make it the active context notebooklm create "Research" --use
create <title> --json JSON envelope; with --use includes active_notebook_id notebooklm create "X" --use --json
delete -n Delete notebook (uses current notebook if -n omitted) notebooklm delete -n abc123
delete -n -y Skip confirmation notebooklm delete -n abc123 -y
delete -n --json Emit {notebook_id, success} envelope (plus context_cleared: true when deleting the active notebook); requires -y (refuses to prompt in JSON mode) notebooklm delete -n abc123 -y --json
rename <title> Rename current notebook notebooklm rename "New Title"
summary Get AI summary notebooklm summary
summary --topics Include suggested topics notebooklm summary --topics

Chat Commands

Command Description Example
ask <question> Ask a question (auto-resumes the last conversation) notebooklm ask "What is this about?"
ask - Read question from stdin (Unix - convention) echo "what is X?" | notebooklm ask -
ask --prompt-file PATH Read question from file (use - for stdin) notebooklm ask --prompt-file q.txt
ask -c Continue a specific conversation notebooklm ask -c conv_abc "..."
ask --new DESTRUCTIVE. Permanently delete the notebook's current server-side conversation (turns are not recoverable) and start fresh on the next ask. Prompts for confirmation with the conversation's short id; pass --yes/-y to skip the prompt. --json implies --yes so scripted callers don't hang on stdin. Closes the workaround documented in #659. notebooklm ask --new -y "start fresh"
ask --new --yes (alias -y) Skip the --new destructive-delete confirmation prompt. notebooklm ask --new --yes "..."
ask -s Limit to specific source IDs (repeatable) notebooklm ask "Summarize" -s src1 -s src2
ask --json Get answer with source references notebooklm ask "Explain X" --json
ask --request-timeout N Per-invocation HTTP request/read timeout in seconds for chat. Defaults to the library chat timeout. --timeout is a back-compat alias for the same flag. notebooklm ask "long prompt" --request-timeout 120
ask --save-as-note Save response as a note. When the answer contains [N] citations, the saved note preserves interactive hover-anchored citation links matching the NotebookLM web UI's "Save to note" behavior (issue #660). Answers without citations fall back to a plain-text note. notebooklm ask "Explain X" --save-as-note
ask --save-as-note --note-title Save response with custom note title. The NotebookLM server may apply smart-title generation for citation-rich saves and override the requested title; the success message reflects what the server actually stored. notebooklm ask "Explain X" --save-as-note --note-title "Title"
suggest-prompts Get AI-suggested prompts for the notebook (each a title plus a ready-to-send instruction for ask) notebooklm suggest-prompts
suggest-prompts --mode N Select the suggestion surface (1-10, default 4): 1=audio deep-dive, 2=audio brief, 3=video explainer, 4=chat questions, 5=audio critique, 6=audio debate, 8=quiz, 9=flashcards, 10=video short. Out-of-range exits 1. notebooklm suggest-prompts --mode 8
suggest-prompts --query TEXT Free-text steer for the kind of prompts to suggest notebooklm suggest-prompts --query "key risks"
suggest-prompts -s Limit to specific source IDs (repeatable; defaults to all sources) notebooklm suggest-prompts -s src1 -s src2
suggest-prompts --json Machine-readable output ({notebook_id, suggestions, count}) notebooklm suggest-prompts --json
configure --mode Set predefined chat mode (default, learning-guide, concise, detailed) notebooklm configure --mode learning-guide
configure --persona Set custom persona prompt (up to 10,000 chars) notebooklm configure --persona "Act as a tutor"
configure --response-length Response verbosity (default, longer, shorter) notebooklm configure --response-length longer
configure --json Machine-readable output notebooklm configure --mode concise --json
history View conversation history notebooklm history
history -l N Maximum number of Q&A turns to show notebooklm history -l 5
history --json Machine-readable output notebooklm history --json
history --clear Clear local conversation cache notebooklm history --clear
history --save Save history as a note notebooklm history --save
history --save -t "Title" Save history with custom title notebooklm history --save -t "Summary"
history --show-all Show full Q&A content (not preview) notebooklm history --show-all
history --no-truncate Disable the 50-char preview cap on the Question/Answer columns in the table view (the existing -l/--limit flag is unchanged: it caps the number of Q&A turns fetched server-side) notebooklm history --no-truncate

Setting just one of configure --persona / --response-length merges with the current settings — the field you omit is preserved, not reset. A bare configure with no flags resets all custom chat settings to their defaults. (A --mode preset cannot be combined with --persona/--response-length.)

Source Commands (`notebooklm source `)

Supported source types: URLs, YouTube videos, files (PDF, text, Markdown, Word, audio, video, images), Google Drive documents, and pasted text.

Command Arguments Options Example
list - --json, --limit N, --no-truncate, --label, --status source list --limit 20 --no-truncate
add <content> URL/file/text (use - for stdin) --title, --type, --timeout, --follow-symlinks, --allow-internal (URL sources only), --json (file-source --mime-type overrides extension inference — see detailed section) source add "https://..." --timeout 90
add-drive <title> Drive file ID, title --mime-type [google-doc|google-slides|google-sheets|pdf], --json source add-drive abc123 "Doc" --mime-type google-slides
add-drive-file Drive file ID or share URL --title, --wait, --json source add-drive-file abc123 --title "Notes" --wait
add-research [query] Search query (or --prompt-file - for stdin) --mode [fast|deep], --from [web|drive], --import-all, --cited-only, --no-wait, --timeout, --prompt-file PATH source add-research "AI" --mode deep --no-wait
get Source ID --json source get src123
fulltext Source ID --json, -o FILE, --force, --no-clobber, -f [text|markdown] source fulltext src123 -f markdown -o out.md (-f markdown requires the markdown extra: pip install "notebooklm-py[markdown]" — full extras matrix: docs/installation.md#optional-extras-matrix)
guide Source ID --json source guide src123
stale Source ID --exit-on-stale, --json source stale src123 (exit 0 on success; pass --exit-on-stale for the back-compat inverted predicate — see exit codes)
wait Source ID --timeout, --interval, --json source wait src123 --timeout 300 --interval 5
clean - --dry-run, -y/--yes, --json source clean --dry-run
rename <title> Source ID, new title --json source rename src123 "New Name"
refresh Source ID --json source refresh src123
delete Source ID -y/--yes, --json source delete src123 -y
delete-by-title <title> Exact source title -y/--yes, --json source delete-by-title "My Source"

All source subcommands also accept -n/--notebook ID (resolves via flag > NOTEBOOKLM_NOTEBOOK env > active context).

source delete accepts only full source IDs or unique partial-ID prefixes. To delete by exact source title, use source delete-by-title "<title>".

source clean automatically removes duplicate, error, and access-blocked sources; combine with --dry-run to preview the candidate set first.

source fulltext -o FILE auto-renames an existing output path by default (FILE -> FILE (2), etc.) instead of overwriting it. Pass --force to overwrite intentionally, or --no-clobber to fail if the path exists.

source stale reports whether a URL/Drive source needs a refresh. By default it follows the standard CLI exit convention (0 on success, 1 on error); branch on the JSON stale/fresh fields (or stdout text) for the freshness verdict. Pass --exit-on-stale to opt into the back-compat inverted predicate (0 = stale, 1 = fresh) for shell idioms like if notebooklm source stale --exit-on-stale ID; then refresh; fi.

source list also accepts --label to list only the sources in a given label (a saved selection). The selector resolves a label id (or partial prefix) or an exact label name; see Label Commands.

source list --status <state> restricts the listing to one ingestion status — ready, processing, error, preparing, or unknown. The choices are derived from SourceStatus, so they cannot drift from the labels the Status column renders, and the filter is applied inside the fetch, so count in --json always matches the rows shown. It composes with --label.

Finding orphaned sources (`--status preparing`)

A file add that fails after its source row is registered leaves that row in place deliberately — it is the evidence of what happened, and it still counts against the notebook's source quota. The row sits at preparing, not error, so the status a caller reaches for first finds nothing (#2138):

bash
notebooklm source list --status preparing        # the reconciliation query
notebooklm source delete                     # once you have confirmed it is stuck

Rows that are genuinely mid-upload also report preparing, so this filter cannot by itself tell "abandoned" from "in flight" — re-run it a minute apart and act only on rows that persist. Nothing is deleted automatically; that posture is deliberate (see #2110).

When the failing add raised in your own process, you do not need to search at all: the exception carries the retained row's id directly, via getattr(exc, "source_id", None).

Drive-backed sources in `source list` / `source get`

Both commands emit the same --json source row:

json
{
  "id": "ef72c03c-…", "title": "Rubisco Research", "type": "google_docs", "url": null,
  "status": "ready", "status_id": 2, "created_at": "2026-01-23T18:42:00",
  "drive_document_id": "1oAk_INJ…", "drive_status": "deleted", "is_drive_degraded": true
}

(source list prefixes each row with a 1-based index.)

  • drive_document_id — the Google Drive file id. A Drive source carries no url, so this is the only field tying it back to the file it was created from; source add-drive matches on it to stay idempotent.
  • drive_status — Drive-side health: inaccessible / syncing / active / deleted / gen_ai_access_denied, or unknown for a code this client cannot map. null means the row made no Drive-health claim at all, which is a different answer from "unknown".
  • is_drive_degraded — true only when the backend explicitly reported a non-healthy Drive state. false means "nothing degraded was reported" — equally true for a non-Drive source, for active, and for an unreadable code — not "the file is confirmed present".

All three keys are present on every row (null/false on non-Drive sources), so jq '.sources[] | select(.drive_status == "deleted")' needs no // empty guard.

Which rows are Drive-backed? drive_document_id and drive_status are decoded from structurally independent parts of the response, so either one being non-null means the row is Drive-backed — neither alone is the authoritative test. In particular, the common case is an id with no health claim ({"drive_document_id": "1oAk…", "drive_status": null}), so filtering on drive_status != null will miss most Drive sources.

Unlike the MCP/REST surfaces, the CLI ships no raw Drive status code beside the label. It would carry no extra information (an unmappable code is replaced by a client-side sentinel before it reaches output), and its code space collides adversarially with status_id's — 2/3 mean ready/error for ingestion but syncing/active for Drive, so a consumer reasoning by analogy would select exactly the wrong rows.

`status` and `drive_status` are different axes

status reports NotebookLM's own ingestion, which completes and stays complete after the Drive file is deleted or unshared. A source therefore reads "status": "ready" while "drive_status": "deleted" — answers grounded on it may be stale.

Human (non---json) output reflects this without adding a column:

  • source list appends the Drive verdict to the Status cell — ready (drive: deleted) — but only for a row the backend reports as degraded (is_drive_degraded), whatever that row's ingestion status is. An unreadable code (unknown) is not flagged here; it is not evidence of degradation, and the table should not cry wolf on protocol drift. Note that Rich sizes columns table-wide, so a single annotated row does re-flow the whole table; that cost is only paid when something is actually wrong.
  • source get adds Drive File ID: and Drive Status: lines, each shown only when that field is present.

Cross-surface naming. MCP and REST spell this axis drive_status (raw code) + drive_status_label (string); the CLI uses drive_status for the string, matching its own long-standing status (label) / status_id (code) pairing. So select(.drive_status == "deleted") is right for the CLI and wrong for MCP/REST, where that key holds an integer. Both surfaces resolve the label through the same mapping helper, so the vocabulary never diverges — only the key name.

Label Commands (`notebooklm label `)

Source labels group a notebook's sources into topic buckets. A `` argument accepts a label id (or partial prefix) or an exact label name; an ambiguous name lists the matching ids so you can disambiguate.

Command Arguments Options Example
list - --json label list -n nb123
sources Label id or name --json label sources Papers
generate - --scope [all|unlabeled], -y/--yes, --json label generate --scope all -y
create <name> Label name --emoji 📄, --json label create "Papers" --emoji 📄
rename <new_name> Label ref, new name --json label rename Papers "Research Papers"
emoji Label ref, emoji --json label emoji Papers 📚
add ... Label ref, one+ source ids --json label add Papers src123 src456
remove ... Label ref, one+ source ids --json label remove Papers src123
delete ... One+ label refs -y/--yes, --json label delete Papers -y

All label subcommands accept -n/--notebook ID (resolves via flag > NOTEBOOKLM_NOTEBOOK env > active context).

label generate --scope all (wipes and regenerates every label with new ids) and label delete are destructive and require -y/--yes to confirm (or an interactive prompt). label generate --scope unlabeled (the default) only labels currently-unlabeled sources and needs no confirmation. label add appends sources (existing members survive; labels may overlap) and label remove un-assigns sources from the label only — the sources stay in the notebook (and in any other label). label delete removes the label only — its sources become unlabeled, not deleted. source_id arguments to label add/label remove accept partial-prefix matching like every other source-id command.

Collection Commands (`notebooklm collection `)

Collections group whole notebooks into named, account-level buckets (playlist-style) — the sibling of label, which groups sources within a notebook. A notebook can belong to multiple collections. A `` argument accepts a collection id (or partial prefix) or an exact collection name; an ambiguous name lists the matching ids so you can disambiguate.

Command Arguments Options Example
list - --json collection list
notebooks Collection id or name --json collection notebooks "Research Q3"
create <name> Collection name --json collection create "Research Q3"
rename <new_name> Collection ref, new name --json collection rename "Research Q3" "Research Q4"
add <notebook_id>... Collection ref, one+ notebook ids --json collection add "Research Q3" nb123 nb456
remove <notebook_id>... Collection ref, one+ notebook ids --json collection remove "Research Q3" nb123
delete ... One+ collection refs -y/--yes, --json collection delete "Research Q3" -y

Collections are account-level, so — unlike label — the collection commands take no -n/--notebook option; their membership arguments are notebooks. collection add appends notebooks (existing members survive; a notebook may belong to multiple collections) and collection remove un-assigns a notebook from that collection only — the notebook is not deleted and stays in any other collection. collection delete (destructive, requires -y/--yes) removes the collection only, never its member notebooks. notebook_id arguments to collection add/collection remove accept partial-prefix matching like every other notebook-id command.

Research Commands (`notebooklm research `)

Command Arguments Options Example
status - -n/--notebook, --json research status
wait - -n/--notebook, --timeout, --interval, --import-all, --cited-only, --json research wait --import-all --cited-only
import - -n/--notebook, --run-id, --cited-only, --timeout, --max-sources, --allow-duplicate, --json research import
cancel RUN_ID -n/--notebook, --json research cancel <run_id>

Generate Commands (`notebooklm generate `)

All generate commands (except the synchronous mind-map) accept the same uniform polling-flag surface:

  • -n, --notebook ID to target a specific notebook (also resolved from NOTEBOOKLM_NOTEBOOK / active context)
  • -s, --source ID to select specific sources (repeatable)
  • --json for machine-readable output (returns task_id and status)
  • --wait / --no-wait to block until completion (default: --no-wait — returns immediately with a task_id)
  • --timeout SECONDS to cap the --wait budget (defaults: 1200s for audio, 1800s for video, 3600s for cinematic-video, and 300s for quiz/flashcards/slide-deck/infographic/data-table/report/revise-slide). No-op without --wait.
  • --interval SECONDS to tune polling cadence (default: 2). No-op without --wait.
  • --retry N to automatically retry on rate limits with exponential backoff
  • --prompt-file PATH to read the description/query from a file (or - for stdin) instead of the command line. Mutually exclusive with the positional argument; useful for long prompts that exceed shell length limits.

Language-aware generate commands (audio, video, cinematic-video, report, infographic, slide-deck, data-table, mind-map) also support:

  • --language LANG to override output language (precedence: --language > NOTEBOOKLM_HL env > config > 'en')

quiz, flashcards, and revise-slide do not accept --language.

Command Arguments Type-specific options Example
audio [description] Instructions --format [deep-dive|brief|critique|debate], --length [short|default|long] generate audio "Focus on history"
video [description] Instructions --format [explainer|brief|cinematic|short], --style [auto|custom|classic|whiteboard|kawaii|anime|watercolor|retro-print|heritage|paper-craft] (not for --format cinematic/short), --style-prompt TEXT (required with --style custom; rejected with --format cinematic/short) generate video "Explainer for kids"
cinematic-video [description] Instructions Alias for video --format cinematic generate cinematic-video "Documentary about quantum physics"
slide-deck [description] Instructions --format [detailed|presenter], --length [default|short] generate slide-deck
revise-slide <description> Revision instructions -a/--artifact (required), --slide N (required) generate revise-slide "Move title up" --artifact --slide 0
quiz [description] Instructions --difficulty [easy|medium|hard], --quantity [fewer|standard|more] generate quiz --difficulty hard
flashcards [description] Instructions --difficulty [easy|medium|hard], --quantity [fewer|standard|more] generate flashcards
infographic [description] Instructions --orientation [landscape|portrait|square], --detail [concise|standard|detailed], --style [auto|sketch-note|professional|bento-grid|editorial|instructional|bricks|clay|anime|kawaii|scientific] generate infographic
data-table <description> Instructions (uniform options only) generate data-table "compare concepts"
mind-map - --kind [interactive|note-backed], --instructions TEXT (no --wait / --timeout / --interval / --retry / --prompt-file) generate mind-map --kind interactive
report [description] Instructions --format [briefing-doc|study-guide|blog-post|custom], --append TEXT (no effect with --format custom) generate report --format study-guide

Two kinds of mind map (issue #1256). NotebookLM has two distinct mind-map objects. generate mind-map --kind interactive (the default) builds the interactive kind — a studio artifact (the one the web app now creates) that is polled to completion. generate mind-map --kind note-backed builds the note-backed kind — a JSON node tree stored as a note, generated synchronously. Both produce the same {mind_map, note_id, kind} output, appear in artifact list --type mind-map, and download via download mind-map. --instructions is a free-text prompt that steers generation: the interactive kind applies it reliably (it travels in the CREATE_ARTIFACT prompt slot the server honors and that artifact get-prompt reads back); the note-backed kind passes it through to GENERATE_MIND_MAP, but the server may not always act on it.

Artifact Commands (`notebooklm artifact `)

Command Arguments Options Example
list - --type [all|audio|video|slide-deck|quiz|flashcard|infographic|data-table|mind-map|report|fantasy-map|file], --limit N, --no-truncate, --json artifact list --type audio --limit 5
get Artifact ID --json artifact get art123
get-prompt Artifact ID --json artifact get-prompt art123
rename <title> Artifact ID, title --json artifact rename art123 "Title"
delete Artifact ID -y/--yes, --json artifact delete art123 -y
export Artifact ID --title TEXT (required), --type [docs|sheets], --json artifact export art123 --title "My Doc" --type sheets
poll <task_id> Task ID (from generate <type>) --json artifact poll task123
wait Artifact ID (from artifact list) --timeout (default: 300), --interval (default: 2), --json artifact wait art123 --timeout 600
retry Artifact ID (from artifact list) --wait, --timeout (default: 300), --interval (default: 2), --json artifact retry art123 --wait
suggestions - --json artifact suggestions

All artifact subcommands also accept -n/--notebook ID.

Note: artifact delete on a Mind Map clears its content rather than removing the artifact entry — Mind Maps are stored alongside notes and Google may garbage-collect cleared entries later. The CLI prints a Cleared mind map: message instead of Deleted artifact: when this happens.

Common confusion: poll vs wait ID kind. Both commands accept the same identifier — the API returns one ID that serves as both the generation task_id (during creation) and the artifact_id (once the row appears in artifact list). The split is operational, not semantic:

  • artifact poll <task_id> — single non-blocking status check. Pass the raw task_id returned by notebooklm generate <type> straight through. poll does not prefix-match against artifact list, so it works immediately after a generate call returns, before the artifact has appeared in any list response.
  • artifact wait <artifact_id> — blocks (with exponential backoff) until the artifact is completed, failed, or --timeout elapses. Accepts a full UUID or a unique prefix that resolves against artifact list via the standard partial-ID resolver.

Rule of thumb: just generated something? use poll. Found it in artifact list? use wait. You can pass the same string to both — the kind label is about lifecycle stage, not about a different identifier format.

artifact retry <artifact_id> re-runs generation for a failed artifact in place (the web UI "Retry" button) — the artifact is not deleted first and the same ID is preserved. Accepts a full UUID or a unique prefix. By default it kicks off the retry and returns immediately (Retry started: ); pass --wait to block until the retried generation reaches a terminal state. A synchronous refusal (rate limit / quota / not-retryable) exits non-zero with a typed error rather than reporting a started task. A retry may itself fail again provider-side, in which case you can run artifact retry again against the same ID.

Download Commands (`notebooklm download `)

Every download subcommand accepts the same selection / safety / output flag set: -n/--notebook ID, -a/--artifact ID, --all, --latest (default), --earliest, --name TEXT (fuzzy title match), --dry-run, --force, --no-clobber (don't overwrite existing files: a single download fails, --all skips them; default is auto-rename), and --json.

Command Arguments Type-specific options Example
audio [path] Output path (none) download audio --all
video [path] Output path (none) download video --latest
cinematic-video [path] Output path Alias for download video; cinematic and standard videos share the same artifact type download cinematic-video ./documentary.mp4
slide-deck [path] Output path --format [pdf|pptx] (default: pdf) download slide-deck ./slides.pdf
infographic [path] Output path (none) download infographic ./info.png
report [path] Output path (none) download report ./report.md
mind-map [path] Output path (none) download mind-map ./map.json
data-table [path] Output path (none) download data-table ./data.csv
quiz [path] Output path --format [json|markdown|html] (default: json) download quiz --format markdown quiz.md
flashcards [path] Output path --format [json|markdown|html] (default: json) download flashcards cards.json

Note Commands (`notebooklm note `)

Command Arguments Options Example
list - --json note list --json
create [content] Note content (or - for stdin) --content TEXT (or - for stdin; mutex with positional), -t/--title TEXT, --json cat notes.md | note create -
get Note ID --json note get note123
save Note ID --title, --content, --json note save note123 --title "Updated title"
rename <title> Note ID, title --json note rename note123 "Title"
delete Note ID -y/--yes, --json note delete note123 -y

All note subcommands also accept -n/--notebook ID.

source get / artifact get / note get exit 1 on not-found (BREAKING). All three get commands now exit 1 when the requested ID does not resolve to an existing item, matching the rest of the CLI's user-error convention. Under --json the failure body is the standard typed error envelope ({"error": true, "code": "NOT_FOUND", "message": "...", "id": "...", "notebook_id": "..."}); without --json the message is written to stderr. The previous behavior was exit 0 with a "not found" line on stdout. The pre-existing "no partial-ID match" branch (raised by _resolve_partial_id as a ClickException) was already exit 1 and is unchanged. See CLI Exit-Code Convention for migration guidance.

Metadata Command

Export notebook metadata and a simplified source list.

bash
notebooklm metadata [OPTIONS]

Options:

  • -n, --notebook ID - Specify notebook (uses current if not set)
  • --json - Output as JSON for scripts

Examples:

bash
notebooklm metadata
notebooklm metadata -n abc123 --json

Skill Commands (`notebooklm skill `)

Manage NotebookLM agent skill integration.

Command Description Example
install Install/update the skill for claude, .agents, or both skill install --target all
status Check installed targets and version info skill status --scope project
uninstall Remove one or more installed targets skill uninstall --target agents
show Display the packaged skill or an installed target skill show --target source
package Build a Claude-uploadable skill archive (chat/Cowork) skill package -o dist/

Defaults:

  • skill install uses --scope user --target all
  • claude maps to .claude/skills/notebooklm/SKILL.md
  • agents maps to .agents/skills/notebooklm/SKILL.md
  • show --target source prints the canonical packaged skill file
  • Project-scope installs support --dry-run, --no-clobber, and --force; these flags are rejected for user-scope installs.

The packaged wheel includes the repo-root SKILL.md, so the same skill content powers notebooklm skill install, GitHub discovery, and npx skills add teng-lin/notebooklm-py.

Codex does not use the skill subcommand. In this repository it reads the root AGENTS.md file and invokes the notebooklm CLI or Python API directly.

MCP Commands (`notebooklm mcp `)

Install the NotebookLM MCP server block into supported MCP client configs.

Command Description Example
install <client> Configure claude-desktop, claude-code, cursor, or windsurf mcp install claude-desktop
install <client> --config-path PATH Write a non-standard config path mcp install cursor --config-path ./mcp.json

The installer writes a notebooklm server entry that launches notebooklm-mcp
through uvx. Re-running is idempotent and preserves unrelated servers in the
same config file. See MCP server guide.

Agent Commands (`notebooklm agent `)

Show bundled instructions for supported agent environments.

Command Description Example
show codex Print the Codex repository guidance agent show codex
show claude Print the bundled Claude Code skill template agent show claude

agent show codex prefers the root AGENTS.md file when running from a source checkout, so the CLI mirrors the same instructions Codex sees in the repository.

Features Beyond the Web UI

These CLI capabilities are not available in NotebookLM's web interface:

Feature Command Description
Batch downloads download <type> --all Download all artifacts of a type at once
Quiz/Flashcard export download quiz --format json Export as JSON, Markdown, or HTML
Mind map extraction download mind-map Export hierarchical JSON for visualization tools
Data table export download data-table Download structured tables as CSV
Slide deck as PPTX download slide-deck --format pptx Download as editable .pptx (web UI only offers PDF)
Slide revision generate revise-slide "prompt" --artifact --slide N Modify individual slides with a natural-language prompt
Report template append generate report --format study-guide --append "..." Append instructions to built-in templates
Source fulltext source fulltext Retrieve the indexed text content of any source
Save chat to note ask "..." --save-as-note / history --save Save Q&A answers or full conversation as notebook notes
Programmatic sharing share commands Manage permissions without the UI

Detailed Command Reference

Authentication: `login`

Authenticate with Google NotebookLM via browser.

Python equivalent: load saved credentials with NotebookLMClient.from_storage(...) and use it as an async context manager. The CLI's interactive browser-login flow has no Python counterpart — run notebooklm login once to seed storage_state.json, then drive the API from Python.

bash
notebooklm login [OPTIONS]

By default, opens a Chromium browser with a persistent profile. Complete the Google login in the browser window — the CLI detects the redirect back to either personal host (notebook.google.com or notebooklm.google.com) and saves the session automatically (no terminal keystroke required). The wait window is 5 minutes by default; if login is not detected before then, the command exits with a retry hint. Use --browser-timeout to extend that human-interaction window, --browser msedge for Microsoft Edge, or --browser-cookies to import cookies from an already-logged-in browser without launching Playwright.

Options:

  • --storage PATH - Where to save storage_state.json (default: $NOTEBOOKLM_HOME/profiles//storage_state.json)
  • --browser [chromium|msedge|chrome] - Browser to use for login (default: chromium). Use chrome for system Google Chrome (workaround when bundled Chromium crashes, e.g. macOS 15+); use msedge for Microsoft Edge. Note: only chromium is auto-installed by the CLI on first login (~170 MB Chromium download); --browser msedge and --browser chrome require the corresponding browser to be already installed on your system.
  • --browser-timeout SECONDS - Human-interaction window for ordinary headed login and browser-assisted master-token bootstrap (default: 300).
  • --browser-cookies <auto|chrome|edge|firefox|safari|brave|arc|...> - Read cookies from an installed browser instead of launching Playwright. Pass an explicit browser name, or auto to let rookiepy auto-detect. For Chromium-family user profiles, use chrome:: (for example chrome::Profile 1 or brave::Work) to extract from one profile explicitly. For Firefox Multi-Account Containers, use firefox::<container-name> to extract from a single container, or firefox::none for the no-container default — unscoped firefox merges every container's cookies (and emits a warning when that's happening). Requires pip install "notebooklm-py[cookies]" (full extras matrix: docs/installation.md#optional-extras-matrix).
  • --account EMAIL - Pick a signed-in Google account by email when several are present in the browser. Saves to the active profile by default; use --profile-name for a separate named profile or --storage for an exact path. Only valid with --browser-cookies.
  • --all-accounts - Extract every Google account signed in to the browser into separate profiles named from each account email. Only valid with --browser-cookies.
  • --update - With --all-accounts: when an account's natural profile name (e.g. alice for [email protected]) already exists but has no account metadata, update that profile in place instead of creating a suffixed alice-2. Profiles that already bind a different email still get a suffix to avoid clobbering. Only valid with --all-accounts.
  • --profile-name NAME - Write a targeted --account import to this named profile instead of the active profile. Only valid with --browser-cookies.
  • --fresh - Start with a clean browser session (deletes the cached browser profile). Use to switch Google accounts. With explicit --storage, a pre-existing browser sidecar is deleted only when it carries NotebookLM's ownership marker or is the canonical legacy/named-profile layout; arbitrary unowned directories are refused. Has no effect with --browser-cookies.
  • --include-domains LABEL[,LABEL...] - Opt in to extracting sibling-product cookies (default: required Google auth/Drive cookies only). Supported labels: youtube, docs, myaccount, mail, all. Pass labels comma-separated or repeat the flag.

Master-token (headless) options — mint/refresh web cookies from a durable Google master token, no per-session browser. Requires pip install "notebooklm-py[headless]". Full guide: installation.md#d-headless-server-or-ci.

  • --master-token - Bootstrap headless auth. Requires --account EMAIL. A visible browser opens Google's EmbeddedSetup to capture the single-use oauth_token (needs [browser]), or pass it with --oauth-token. Exchanges it for a durable master token (saved 0600 at master_token.json), mints cookies into storage_state.json, and verifies by listing notebooks.
  • --master-token-refresh - Legacy forced re-mint; prefer notebooklm auth refresh. It still unconditionally replaces storage_state.json from the stored master token and preserves CLI context.
  • --oauth-token VALUE - Provide the single-use EmbeddedSetup oauth_token manually (headless boxes without [browser]).
  • --cdp-url URL - Capture oauth_token by attaching to a running Chrome over CDP (e.g. http://localhost:9222) instead of launching a browser.
  • --android-id HEX - Override the per-install Android id (default: generated and persisted with the token). ⚠️ The master token is a full-account, durable credential — use a dedicated/throwaway account only.
  • --force - With --master-token, overwrite even if the target profile already holds a session for a different account. Without it, a mismatched --account is refused (use a dedicated -p instead) so account B's mint can't silently clobber account A's profile.

Examples:

bash
# Default (Chromium)
notebooklm login

# Use Microsoft Edge (for orgs that require Edge for SSO)
notebooklm login --browser msedge

# Reuse cookies from your already-logged-in Chrome session
notebooklm login --browser-cookies chrome
notebooklm login --browser-cookies 'chrome::Profile 1'  # one Chromium profile

# Auto-detect any supported browser via rookiepy
notebooklm login --browser-cookies auto

# Firefox Multi-Account Containers: target one container
notebooklm login --browser-cookies 'firefox::Work'
notebooklm login --browser-cookies 'firefox::none'  # no-container default

# Populate a named profile via cookie import
notebooklm --profile work login --browser-cookies chrome

# Pick a specific browser account by email
notebooklm login --browser-cookies chrome --account [email protected]

# Extract every signed-in browser account into separate profiles
notebooklm login --browser-cookies chrome --all-accounts

# Force a clean browser session before logging in
notebooklm login --fresh

# Headless master-token auth (one browser sign-in, then no per-session browser)
notebooklm login --master-token --account [email protected]
notebooklm login --master-token --account [email protected] --oauth-token "$OAUTH_TOKEN"  # headless box
notebooklm login --master-token-refresh   # legacy: force a re-mint unconditionally

Notes on --browser-cookies:

  • Honors --profile / NOTEBOOKLM_PROFILE and writes to that profile's storage_state.json.
  • Without --account or --all-accounts, imports the selected browser/profile's default Google account into the target profile.
  • For Chromium-family browsers, unscoped chrome, brave, edge, etc. fan out across populated user profiles when account selection is needed. Use chrome:: to read exactly one profile; directory names such as Default and Profile 1 are stable across UI renames.
  • Use notebooklm auth inspect --browser to see available account emails before a targeted import; pass -v to show the Chromium profile directory each account came from.

Authentication: `auth import-cookies`

Import authentication cookies from a JSON file (or stdin) and persist them to the active profile's storage_state.json — a file-backed, persistent alternative to the env-var-based NOTEBOOKLM_AUTH_JSON for users who can obtain cookies as JSON but find the browser/Playwright login flow difficult.

⚠️ These are full-account credentials. A JSON cookie export grants the same access as being logged in. Only import a file you exported yourself, keep it private, and delete it after import. Be especially cautious with third-party browser cookie-export extensions.

bash
notebooklm auth import-cookies JSON_PATH [OPTIONS]

Accepts either a Playwright storage_state object ({"cookies": [...]}) or a bare JSON list of cookie objects (the shape most browser cookie-export tools produce). Use - to read JSON from stdin. Common export fields are normalized (e.g. expirationDate → expires), __Secure-/__Host- cookies are forced Secure, and any storage_state origins (localStorage/sessionStorage) are dropped.

Imported cookies are filtered through the same domain allowlist used by browser login and validated locally for the Tier 1 NotebookLM-required cookies. The required __Secure-1PSIDTS entry must also be live and RFC 6265-routable to the accounts.google.com/RotateCookies endpoint. If any secondary-binding cookie is present, the supplied set must form a usable binding — OSID, or APISID+SAPISID together with bare LSID; a completely absent Tier 2 set is preserved after a warning for compatibility with unablated account flows. The result is written atomically with private (0o600) permissions. Invalid input never overwrites an existing session, and an existing storage_state.json is first copied to storage_state.json.bak so a stale import can be rolled back (one step — each run overwrites the previous .bak).

Incompatible with NOTEBOOKLM_AUTH_JSON: unset that env var first, or the command exits with an error (it does not silently fall back to the env auth).

Options:

  • --include-domains LABEL (repeatable) - Opt in to persisting sibling-product cookies (default: required Google auth/Drive/NotebookLM domains only). Pass labels comma-separated or repeat the flag (e.g. --include-domains youtube,docs or --include-domains youtube --include-domains docs). Same labels as login: youtube, docs, myaccount, mail, all.
  • --include-optional - Persist all optional sibling-product cookie domains.
  • --json - Emit a JSON result (storage_path, cookie_count, backup_path).
  • --quiet - Suppress success output.

Examples:

bash
# Import a bare cookie list exported by a browser tool
notebooklm auth import-cookies cookies.json

# Import a Playwright storage_state into a named profile
notebooklm -p work auth import-cookies playwright-storage-state.json

# Pipe JSON from stdin
cat cookies.json | notebooklm auth import-cookies -

Session: `use`

Set the active notebook for subsequent commands.

bash
notebooklm use [OPTIONS] <notebook_id>

By default, the notebook must exist on the server; a typo or unreachable backend results in a non-zero exit and the saved context is left untouched. Pass --force to bypass verification (offline / debugging).

Supports partial ID matching:

bash
notebooklm use abc  # Matches abc123def456...

Options:

  • --force - Skip the existence check and persist the notebook ID even if verification fails.
  • --json - Emit {"active_notebook_id": "", "success": true, "verified": true|false, "notebook": {...}}. verified: false covers the --force path.

When credentials are expired, use routes through the canonical "Not logged in" handler instead of a generic "Could not verify" message; under --json you get the standard {"error": true, "code": "AUTH_REQUIRED", ...} envelope.

Session: `status`

Show current context (active notebook and conversation).

bash
notebooklm status [OPTIONS]

Options:

  • --paths - Show resolved configuration file paths
  • --json - Output as JSON (useful for scripts)

Examples:

bash
# Basic status
notebooklm status

# Show where config files are located
notebooklm status --paths
# Output shows home_dir, storage_path, context_path, browser_profile_dir

# JSON output for scripts
notebooklm status --json

With --paths:

text
Configuration Paths
┏━━━━━━━━━━━━━━━━━┳━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━┳━━━━━━━━━━━━━━━━━┓
┃ File            ┃ Path                         ┃ Source          ┃
┡━━━━━━━━━━━━━━━━━╇━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━╇━━━━━━━━━━━━━━━━━┩
│ Home Directory  │ /home/user/.notebooklm      │ default         │
│ Storage State   │ .../storage_state.json      │                 │
│ Context         │ .../context.json            │                 │
│ Browser Profile │ .../browser_profile         │                 │
└─────────────────┴──────────────────────────────┴─────────────────┘

Language: `list`, `get`, `set`

Manage the output language for artifact generation (audio, video, etc.).

Important: Language is a GLOBAL setting that affects all notebooks in your account.

bash
# List all supported languages with native names
notebooklm language list

# Show current language setting (syncs from server)
notebooklm language get

# Set language to Simplified Chinese
notebooklm language set zh_Hans

# Set language to Japanese
notebooklm language set ja

Options for get:

  • --local - Skip server sync, show local config only
  • --json - Output as JSON

Options for set:

  • --local - Save to local config only, skip server sync
  • --json - Output as JSON

Common language codes:

Code Language
en English
zh_Hans 中文(简体) - Simplified Chinese
zh_Hant 中文(繁體) - Traditional Chinese
ja 日本語 - Japanese
ko 한국어 - Korean
es Español - Spanish
fr Français - French
de Deutsch - German
pt_BR Português (Brasil) - Brazilian Portuguese

Run notebooklm language list for all 80+ supported languages.

Share: `status`, `public`, `view-level`, `add`, `update`, `remove`

Manage notebook sharing settings and user permissions.

bash
# Show current sharing status and shared users
notebooklm share status

# Enable public link sharing (anyone with link can view)
notebooklm share public --enable

# Disable public sharing
notebooklm share public --disable

# Set what viewers can access
notebooklm share view-level full   # Full notebook (chat, sources, notes)
notebooklm share view-level chat   # Chat interface only

# Share with specific users
notebooklm share add [email protected]                        # Add as viewer (default)
notebooklm share add [email protected] --permission editor    # Add as editor
notebooklm share add [email protected] -m "Check this out!"   # With message
notebooklm share add [email protected] --no-notify            # Skip email notification

# Update user permission
notebooklm share update [email protected] --permission editor

# Remove user access
notebooklm share remove [email protected]
notebooklm share remove [email protected] -y   # Skip confirmation

Options (all commands):

  • -n, --notebook ID - Specify notebook (uses current if not set, supports partial IDs)
  • --json - Output as JSON

Permission levels:

Level Access
viewer Read-only access (default)
editor Can edit notebook content

View levels:

Level Viewers can see
full Chat, sources, and notes
chat Chat interface only

Label: `list`, `sources`, `generate`, `create`, `rename`, `emoji`, `add`, `remove`, `delete`

Manage source labels — AI-generated (or manually named) topic groupings of a notebook's sources.

bash
# List labels (with member ids + resolved source titles)
notebooklm label list

# Expand a label to its source objects
notebooklm label sources Papers

# AI-group sources into topic labels (the UI's "Reorganize")
notebooklm label generate                       # --scope unlabeled (default, safe)
notebooklm label generate --scope all -y        # destructive: wipe + regenerate every label

# Create an empty, manually-named label
notebooklm label create "Papers" --emoji 📄

# Rename a label (preserves its emoji) / set its emoji
notebooklm label rename Papers "Research Papers"
notebooklm label emoji Papers 📚

# Add source(s) to a label (append; existing members preserved)
notebooklm label add Papers src123 src456

# Remove source(s) from a label (un-assign only; source stays in the notebook)
notebooklm label remove Papers src123

# Delete one or more labels (the label only, not its sources)
notebooklm label delete Papers -y

Options (all commands):

  • -n, --notebook ID - Specify notebook (uses current if not set, supports partial IDs)
  • --json - Output as JSON

Confirmation gates:

  • label generate --scope all wipes and regenerates every label with new ids — requires -y/--yes (or an interactive prompt).
  • label delete requires -y/--yes (or an interactive prompt).

Name-or-ID resolution: `` arguments accept a label id (or partial prefix) or an exact label name. An ambiguous name lists the matching ids (with emoji + source count) so you can re-run with the id.

Selecting sources by label: notebooklm source list --label lists only the sources in a label — a read-only saved-selection filter.

Authentication: `auth check`

Diagnose authentication issues by validating storage file, cookies, and optionally testing token fetch.

bash
notebooklm auth check [OPTIONS]

Options:

  • --test - Also test token fetch from NotebookLM (makes network request)
  • --passive - With --test, validate read-only: never run NOTEBOOKLM_REFRESH_CMD, rotate cookies, or write to disk. Use for unattended readiness/health checks that must not mutate state or race real work. No effect without --test.
  • --json - Output as JSON (useful for scripts)

Examples:

bash
# Quick local validation
notebooklm auth check

# Full validation with network test
notebooklm auth check --test

# Read-only readiness probe (no refresh cmd, no cookie write) for a health check
notebooklm auth check --test --passive

# JSON output for automation
notebooklm auth check --json

Checks performed:

  1. Storage file exists and is readable
  2. JSON structure is valid
  3. Required cookies (SID + __Secure-1PSIDTS) are present (the Tier 1 MINIMUM_REQUIRED_COOKIES set). The loader also evaluates the warning-only secondary-binding check — OSID, or APISID+SAPISID together with bare LSID — but auth check does not mark a Tier 2 warning as failure; see auth-cookie-lifecycle.md
  4. Cookie domains are correct (.google.com vs regional)
  5. (With --test) Token fetch succeeds

Output shows:

  • Authentication source (file path or environment variable)
  • Which cookies were found and from which domains
  • Detailed cookie breakdown by domain (highlighting key auth cookies)
  • Token lengths when using --test

Use cases:

  • Debug "Not logged in" errors
  • Verify auth setup in CI/CD environments
  • Check if cookies are from correct domain (regional vs .google.com)
  • Diagnose NOTEBOOKLM_AUTH_JSON environment variable issues

Exit codes: auth check exits 0 only when every executed check passes
and non-zero (1) when any executed check fails — in both text and --json
modes. A skipped check does not count as a failure: without --test, the token
fetch is skipped, so the exit status reflects only the local cookie checks.
Unattended monitors should therefore rely on the exit code (not on parsing the
table). To gate readiness on a real token fetch, run notebooklm auth check --test and check the exit status. See CLI Exit-Code Convention.

Authentication: `auth refresh`

One-shot keepalive and recovery: open a file-backed session, trigger the layer-1 SIDTS rotation poke against accounts.google.com, persist rotated cookies, and exit. If storage is absent but the exact sibling master_token.json exists, it mints the initial file and passively validates it once; --verify reuses that result. Existing cookies that redirect to Google sign-in automatically try a sibling master-token re-mint; --allow-headless permits layer-3 browser recovery first. Healthy storage stays on the cheap path. When a Playwright storage state lacks in-band notebooklm.account metadata, auth refresh also repairs it if account discovery is unambiguous. It does not replace existing metadata; use login --browser-cookies --account EMAIL to re-bind a profile that already points at the wrong account. Designed for direct use or OS scheduling (launchd / systemd / cron / Task Scheduler / k8s CronJob).

bash
notebooklm auth refresh [OPTIONS]

Options:

  • --browser-cookies , --browser-cookie - Re-extract cookies from an installed browser and match the current profile's account (the unified in-band storage_state.json record — a pre-v0.5.0 profile's account, if it's still only in the legacy sibling context.json, is promoted in-band automatically on read). This repairs account routing when browser account order changes after another account logs out. Accepts the same scoped syntax as login: chrome:: for one Chromium profile, and firefox::<container-name> or firefox::none for one Firefox container.
  • --include-domains LABEL[,LABEL...] - Forward to the browser-cookie reader (only meaningful with --browser-cookies). Same syntax as notebooklm login --include-domains.
  • --quiet, -q - Suppress success output; print only on error (cron-friendly)
  • --verify - After refreshing, run a read-only passive token fetch to confirm the resulting cookies actually authenticate; exit non-zero if they still fail. A missing-storage master-token bootstrap always performs this validation and --verify reuses its result. Especially valuable with --browser-cookies, which rewrites the cookie jar but does not otherwise verify it.
  • --allow-headless - Permit layer-3 browser recovery for this invocation when stored cookies are fully expired. Uses the storage-bound persisted browser profile or loopback NOTEBOOKLM_HEADLESS_REAUTH_CDP_URL, and does not launch or attach unless the ordinary refresh fails. Incompatible with --browser-cookies.
  • --json - Emit one structured success or error document. On success the keys are status, storage_path, and verified.

Cadence: 15-20 minutes is the recommended interval. Tighter is wasteful (the 60 s mtime guard would skip it anyway); significantly looser may cross the __Secure-1PSIDTS server-side validity window for your account/region.

Requires file-backed authentication. auth refresh refuses NOTEBOOKLM_AUTH_JSON because inline JSON has no writable backing store. A root --storage PATH is an explicit file-backed override and wins over that environment variable for the invocation.

Exit codes:

  • 0 - the auth path completed without raising. The rotation POST is best-effort: exit 0 also covers (a) the 60 s mtime guard skipping the POST, (b) NOTEBOOKLM_DISABLE_KEEPALIVE_POKE=1 being set, (c) another process holding the cross-process rotate lock, and (d) a transient httpx error during the POST being caught and logged at DEBUG. Treat exit 0 as "no error" rather than "rotation occurred." For verification, enable NOTEBOOKLM_LOG_LEVEL=DEBUG and check for the RotateCookies log line.
  • 1 - a typed command failure occurred (for example, an incompatible option, a failed master-token mint, or a failed post-refresh passive token fetch). The OS scheduler's next firing is the retry mechanism; this command does not retry in-process.
  • 2 - an unexpected error occurred. Missing storage without a sibling token and malformed existing storage retain this existing contract; neither bootstraps.

Examples:

bash
# One-shot refresh against the default profile
notebooklm auth refresh

# Refresh a named profile (works with --profile / NOTEBOOKLM_PROFILE)
notebooklm --profile work auth refresh

# Permit browser-backed L3 only if ordinary recovery reaches a login redirect
notebooklm --profile work auth refresh --allow-headless

# Re-extract from Chrome and repair account routing if browser account order changed
notebooklm --profile work auth refresh --browser-cookies chrome
notebooklm --profile work auth refresh --browser-cookies 'chrome::Profile 1'

# Quiet variant for cron / systemd
notebooklm --profile work auth refresh --quiet

Pairs with:

  • NOTEBOOKLM_REFRESH_CMD for in-process auth-expiry recovery (covers the case where auth refresh itself fails because cookies have already expired beyond rotation)
  • keepalive=<seconds> on NotebookLMClient for in-process long-lived workers (no OS scheduler needed)

See Troubleshooting for full per-OS scheduler recipes (launchd plist, systemd user timer, cron, Task Scheduler, k8s CronJob).

Authentication: `auth inspect`

List Google accounts visible to a browser's cookie store. Read-only — never writes to disk. Use this before notebooklm login --browser-cookies --account to see which account emails are available. Chromium-family browsers fan out across populated user profiles by default; use chrome:: to inspect only one profile.

bash
notebooklm auth inspect [OPTIONS]

Options:

  • --browser TEXT - Browser to read cookies from (chrome, firefox, brave, edge, safari, arc, ...). auto picks the first one rookiepy can read. Use chrome:: for one Chromium profile, or firefox::<container-name> / firefox::none for one Firefox container. Requires pip install "notebooklm-py[cookies]".
  • --include-domains LABEL[,LABEL...] - Opt in to enumerating accounts via sibling-product cookies (same syntax as notebooklm login --include-domains). By default this command consults only required Google auth cookies, which is sufficient for account discovery on every tested path.
  • --json - Output as JSON

Examples:

bash
notebooklm auth inspect --browser chrome
notebooklm auth inspect --browser 'chrome::Profile 1' --json
notebooklm auth inspect --browser firefox --json

Authentication: `auth logout`

Log out by clearing saved authentication. Removes both the saved cookie file (storage_state.json) and the cached browser profile. With explicit --storage, an arbitrary unowned browser sidecar is left intact; canonical managed profile layouts are still removed. After logout, run notebooklm login to authenticate with a different Google account.

bash
notebooklm auth logout

Examples:

bash
notebooklm auth logout                        # Clear auth for active profile
notebooklm -p work auth logout                # Clear auth for 'work' profile
notebooklm --storage A.json auth logout       # Clear the override auth file

Session: `completion`

Print the shell completion script for bash, zsh, or fish. Pipe the output into a file your shell sources at startup; once the script is loaded, Click handles the _NOTEBOOKLM_COMPLETE env-var protocol automatically.

bash
notebooklm completion

Install (one-time):

bash
# zsh — drop the script anywhere on $fpath; the file MUST be named _notebooklm
notebooklm completion zsh > ~/.zfunc/_notebooklm
# (ensure ~/.zfunc is on $fpath in ~/.zshrc, then `autoload -Uz compinit && compinit`)

# bash — source from ~/.bashrc
notebooklm completion bash > ~/.notebooklm-complete.bash
echo 'source ~/.notebooklm-complete.bash' >> ~/.bashrc

# fish — drop into the system completions directory
notebooklm completion fish > ~/.config/fish/completions/notebooklm.fish

ID-aware tab completion: Once the script is installed, -n/--notebook, -s/--source, and -a/--artifact complete from the active profile's live IDs:

bash
notebooklm ask -n <TAB>            # lists notebook IDs (filtered by what you've typed)
notebooklm ask -s <TAB>            # lists sources in the active notebook
notebooklm download audio -a <TAB> # lists artifacts in the active notebook

Note: commands that take a positional source / artifact / notebook ID (e.g. source delete , artifact get , use ) currently complete only after the option / argument is fully typed — Click's shell_complete= is attached to the flag-style -n/--notebook, -s/--source, -a/--artifact declarations enumerated below.

For -s and -a the active notebook is resolved with the same precedence the command body uses: -n/--notebook flag value already on the line > NOTEBOOKLM_NOTEBOOK env var > the persisted notebooklm use context. With no resolvable notebook (and on any auth / network failure), the completer returns no suggestions silently — it never prints a traceback into your terminal.

Print-only by design: the command never writes to your shell config; you decide where the script lands. This keeps the install path discoverable and avoids surprising shutdowns of existing completion setups.

Source: `add` — how an argument is classified

With no --type, source add decides in this order:

  1. URL-shaped (contains ://) → a url or youtube source.
  2. The path exists on disk → a file source, uploaded. This is an existence
    check, not an extension check, so a real file is uploaded whatever it is
    named — deck.pptx, ./deck.pptx and notes all work.
  3. Otherwise → a text source, ingesting the argument itself as content.

Step 3 is where a typo bites: source add dekc.pptx adds a source whose entire
content is the string dekc.pptx. To make that visible, an argument that looks
like a file but does not exist is added with a warning:

console
$ notebooklm source add dekc.pptx
warning: 'dekc.pptx' looks like a path but does not exist; ingesting as inline
text. Pass --type text to suppress this warning, or check the path for typos.

An argument looks like a file when it contains a slash, or when its extension is
one the upload accepts — .pdf .txt .md .markdown .doc .docx .pptx
.rtf .odt .csv .tsv .epub — or is HTML-family (.html .htm .xhtml
.xht, which the upload endpoint rejects with convert-first guidance), or is
.ppt (file-shaped, but legacy PowerPoint has never been proven uploadable, so
it earns the warning without being routed to the uploader). That list is derived
from the single upload-support declaration the Drive download-and-upload router
also reads, so a newly supported file type earns its warning at the same time it
becomes uploadable, rather than drifting behind it (#2202).

Pass --type text to opt out of detection entirely, or --type file to require
the upload path (which then fails loudly on a missing file instead of falling
back to text).

File-source uploads reject symlinks by default. If the path you pass (or any ancestor directory) is a symbolic link, source add refuses the upload rather than silently following it — a workspace symlink could otherwise exfiltrate the file it points at (e.g. ~/Downloads/foo.pdf -> /etc/passwd). Pass --follow-symlinks to opt in explicitly.

URL sources reject internal hosts (localhost, loopback, private IP ranges,
and link-local addresses) by default so the CLI cannot be used as an SSRF
trampoline. Pass --allow-internal only for a deliberate local NotebookLM test;
non-HTTP(S) schemes remain rejected even with the flag.

Python equivalent: client.sources.add_file(nb_id, path, title=...). The symlink gate is a CLI-only safeguard; callers using the Python API are responsible for resolving symbolic links before passing the path.

bash
# Default — symlink rejected with: "Path is a symlink; pass --follow-symlinks to follow it explicitly."
notebooklm source add ./link-to-doc.pdf --type file

# Opt-in: resolve the symlink, then upload the resolved target
notebooklm source add ./link-to-doc.pdf --type file --follow-symlinks

The same gate applies on the explicit --type file path (no auto-detect), so typing the source type as file does not bypass the check.

Source: `add` `--mime-type` (file sources)

The --mime-type flag on notebooklm source add (file-source path) is a
supported parameter. It overrides the filename-extension inference and
sets the content-type header used for the resumable upload handshake. Omit it
to let the MIME type be inferred from the file extension.

bash
# Inferred from the extension (typical case — no flag needed)
notebooklm source add ./report.pdf --type file

# Override inference (e.g. an extensionless export, or a misnamed file)
notebooklm source add ./report.bin --type file --mime-type application/pdf

Note: The same --mime-type flag on notebooklm source add-drive
(Google Drive sources) selects between google-doc / google-slides /
google-sheets / pdf Drive document types.

The <title> you pass to source add-drive is ignored by NotebookLM for
native Drive imports — the backend re-derives the display title from live Drive
metadata, so the source keeps its Drive name regardless. Run
notebooklm source rename "<title>" after the add if you need a specific
title.

Historical: an earlier release treated the file-source --mime-type as a
deprecated no-op. It was re-wired to set the upload content-type and is no
longer deprecated; the NOTEBOOKLM_QUIET_DEPRECATIONS notice it used to
emit is gone.

Source: `add-research`

Perform AI-powered research and add discovered sources to the notebook.

Python equivalent: client.research.start(nb_id, query, source="web", mode="deep") followed by client.research.wait_for_completion(...) / client.research.import_sources(...).

bash
notebooklm source add-research [query] [OPTIONS]

Options:

  • -n, --notebook ID - Notebook ID (uses current if not set)
  • --mode [fast|deep] - Research depth (default: fast)
  • --from [web|drive] - Search source (default: web)
  • --import-all - Automatically import all found sources (works with blocking mode)
  • --cited-only - With --import-all, import only cited sources
  • --no-wait - Start research and return immediately (non-blocking)
  • --timeout SECONDS - Per-phase seconds budget for (a) the research-completion poll loop and (b) the --import-all retry loop (default: 1800). Each phase gets the full budget independently, so worst-case total wall time is up to 2× this value. Matches research wait --timeout semantics. Before 0.4.2 the in-line poll was hardcoded to 5 minutes, so deep research that ran longer was silently abandoned and left an "Add sources?" modal hanging in the NotebookLM web UI — bump --timeout for long deep-research runs.
  • --prompt-file PATH - Read query from a file (or - for stdin) instead of the positional argument

Note: --mode deep is only supported with --from web (the default). Combining --mode deep --from drive is rejected by the backend with ValidationError("Deep Research only supports Web sources.") — for Drive, stick with --mode fast.

Examples:

bash
# Fast web research (blocking)
notebooklm source add-research "Quantum computing basics"

# Drive research (fast mode only — Drive does not support --mode deep)
notebooklm source add-research "Project Alpha" --from drive

# Non-blocking deep research for agent workflows (web only — see note above)
notebooklm source add-research "AI safety papers" --mode deep --no-wait

# Import only cited sources for tighter relevance
notebooklm source add-research "topic" --import-all --cited-only

# Bounded import-retry budget for large result sets
notebooklm source add-research "AI papers" --mode deep --import-all --timeout 3600

# Read long query from file (or stdin via '-')
notebooklm source add-research --prompt-file research_query.txt --mode deep
echo "very long query..." | notebooklm source add-research --prompt-file -

Research: `status`

Check research status for the current notebook (non-blocking).

Python equivalent: client.research.poll(nb_id).

bash
notebooklm research status [OPTIONS]

Options:

  • -n, --notebook ID - Notebook ID (uses current if not set)
  • --json - Output as JSON

Output states:

  • No research running - No active research session
  • Research in progress - Deep research is still running
  • Research completed - Shows query, found sources, and summary

Examples:

bash
# Check status
notebooklm research status

# JSON output for scripts/agents
notebooklm research status --json

Research: `wait`

Wait for research to complete (blocking).

Python equivalent: client.research.wait_for_completion(nb_id), then optionally call client.research.import_sources(...) (matches the CLI's --import-all / --cited-only behavior).

bash
notebooklm research wait [OPTIONS]

Options:

  • -n, --notebook ID - Notebook ID (uses current if not set)
  • --timeout SECONDS - Per-phase budget (default: 1800, matching source add-research). Deep runs regularly exceed the former 300s default — 374s live, 358s in the research_deep_poll_long cassette; fast runs settle in seconds, so the cap only ever binds on deep. With --import-all the poll loop and the import-retry loop each get the full budget independently, so worst-case wall time is up to 2× this value.
  • --interval SECONDS - Seconds between status checks (default: 5)
  • --import-all - Import all found sources when done
  • --cited-only - With --import-all, import only cited sources
  • --json - Output as JSON

Examples:

bash
# Basic wait
notebooklm research wait

# Wait longer for deep research
notebooklm research wait --timeout 600

# Wait and auto-import sources
notebooklm research wait --import-all

# Import only cited sources
notebooklm research wait --import-all --cited-only

# JSON output for agent workflows
notebooklm research wait --json --import-all

Use case: Primarily for LLM agents that need to wait for non-blocking deep research started with source add-research --no-wait.

Research: `import`

Import a completed research run's sources — without blocking.

Python equivalent: client.research.import_sources_with_verification(nb_id, run_id, sources), after polling the run to completed yourself.

bash
notebooklm research import [OPTIONS]

Options:

  • -n, --notebook ID - Notebook ID (uses current if not set)
  • --run-id ID - Run to import. Omit it and the notebook's single research run is used; when a
    notebook has more than one run this errors rather than guessing which you meant, so pass
    the id (research status shows it)
  • --cited-only - Import only report-cited sources (all of them, if no citation resolves — cited_only_fallback says which happened)
  • --timeout SECONDS - Seconds budget for the import retry loop (default: 1800)
  • --max-sources N - Import at most N sources (applied after --cited-only narrows)
  • --allow-duplicate - Re-add sources whose URL is already in the notebook
  • --json - Output as JSON

Never waits for the run. This is the counterpart to research wait --import-all: if the run is still in progress (or failed, or found nothing), the command exits 1 with an explanation instead of polling. The import RPC itself is not instant — IMPORT_RESEARCH commonly outlives a single client timeout on deep payloads and is retried with reconciliation, bounded by --timeout (default 1800s, same vocabulary as research wait --timeout). That makes the fully composable flow expressible for the first time — you own the cadence:

bash
notebooklm source add-research "AI safety" --mode deep --no-wait   # returns immediately
notebooklm research status                                         # your loop, your interval
notebooklm research import                                         # imports, returns

Idempotent — with two documented limits. A source whose URL is already in the notebook is reported as already-present rather than duplicated, so a repeat import reads as "0 new, N already present" instead of looking like a no-op. Under --json that split is imported / imported_sources versus already_present / already_present_sources, and status is already_imported when nothing new landed.

The dedupe is by URL against a snapshot taken just before the import, so: a deep run's report row has no URL and is re-imported on every run, and if the snapshot call itself fails the filter is skipped entirely (the import still proceeds). Both are properties of import_sources_with_verification, shared with research wait --import-all and the MCP tool. If you interrupt an import, check source list before re-running it rather than assuming the re-run is a no-op.

Examples:

bash
# Import the notebook's current completed run
notebooklm research import

# Pin a specific run and take only the cited sources
notebooklm research import --run-id <run_id> --cited-only

# Cap the import, JSON output for agent workflows
notebooklm research import --max-sources 10 --json

Research: `cancel`

Cancel an in-flight research run.

Python equivalent: client.research.cancel(nb_id, run_id).

bash
notebooklm research cancel RUN_ID [OPTIONS]

Arguments:

  • RUN_ID - The run's poll-level id — the task_id shown by research status. For deep research this is the report_id returned by source add-research, not the deep start task_id (which is a sessionId and will not cancel anything).

Options:

  • -n, --notebook ID - Notebook ID (uses current if not set)
  • --json - Output as JSON

Examples:

bash
# Cancel a run (find the run id with `research status`)
notebooklm research cancel <run_id>

# JSON output for agent workflows
notebooklm research cancel <run_id> --json

Fire-and-forget: the server reports neither success nor failure for a cancel and does not validate the run id, so this command cannot confirm the cancel took effect. Run research status afterward — a cancelled in-progress run shows as failed.

Generate: `audio`

Generate an audio overview (podcast).

Python equivalent: client.artifacts.generate_audio(nb_id, ...).

bash
notebooklm generate audio [description] [OPTIONS]

Options:

  • -n, --notebook ID - Notebook ID (uses current if not set)
  • --format [deep-dive|brief|critique|debate] - Podcast format (default: deep-dive)
  • --length [short|default|long] - Duration (default: default)
  • --language LANG - Output language (precedence: --language > NOTEBOOKLM_HL env > config > 'en')
  • -s, --source ID - Limit to specific source IDs (repeatable, uses all if not specified)
  • --wait / --no-wait - Wait for completion (default: --no-wait)
  • --timeout SECONDS - Maximum seconds to wait (default: 1200; no-op without --wait)
  • --interval SECONDS - Seconds between status checks (default: 2; no-op without --wait)
  • --retry N - Retry N times with exponential backoff on rate limit
  • --json - Output as JSON (returns task_id and status)
  • --prompt-file PATH - Read description from a file (or - for stdin); mutually exclusive with positional argument

Examples:

bash
# Basic podcast (starts async, returns immediately)
notebooklm generate audio

# Debate format with custom instructions
notebooklm generate audio "Compare the two main viewpoints" --format debate

# Generate and wait for completion
notebooklm generate audio "Focus on key points" --wait

# Generate using only specific sources
notebooklm generate audio -s src_abc -s src_def

# JSON output for scripting/automation
notebooklm generate audio --json
# Output: {"task_id": "abc123...", "status": "pending"}

# Read long instructions from a file
notebooklm generate audio --prompt-file instructions.txt --format debate

Generate: `video`

Generate a video overview. Use --format cinematic for AI-generated documentary footage (Veo 3); cinematic videos ignore --style and take ~30-40 min (requires AI Ultra). For non-cinematic formats, see generate cinematic-video for the alias subcommand.

Python equivalent: client.artifacts.generate_video(nb_id, ...).

bash
notebooklm generate video [description] [OPTIONS]

Options:

  • -n, --notebook ID - Notebook ID (uses current if not set)
  • --format [explainer|brief|cinematic|short] - Video format (short is a vertical short-form video with a fixed style)
  • --style [auto|custom|classic|whiteboard|kawaii|anime|watercolor|retro-print|heritage|paper-craft] - Visual style (not supported with --format cinematic or short)
  • --style-prompt TEXT - Custom visual style prompt (required when --style custom; rejected with --format cinematic or short)
  • --language LANG - Output language (precedence: --language > NOTEBOOKLM_HL env > config > 'en')
  • -s, --source ID - Limit to specific source IDs (repeatable, uses all if not specified)
  • --wait / --no-wait - Wait for completion (default: --no-wait)
  • --timeout SECONDS - Maximum seconds to wait (default: 1800; --format cinematic defaults to 3600; no-op without --wait)
  • --interval SECONDS - Seconds between status checks (default: 2; no-op without --wait)
  • --retry N - Retry N times with exponential backoff on rate limit
  • --json - Output as JSON (returns task_id and status)
  • --prompt-file PATH - Read description from a file (or - for stdin); mutually exclusive with positional argument

Examples:

bash
# Kid-friendly explainer
notebooklm generate video "Explain for 5 year olds" --style kawaii

# Custom visual style
notebooklm generate video --style custom --style-prompt "hand-drawn diagrams"

# Cinematic format (Veo 3)
notebooklm generate video --format cinematic "documentary overview"

# Professional style
notebooklm generate video --style classic --wait

# Generate from specific sources only
notebooklm generate video -s src_123 -s src_456

# JSON output for scripting/automation
notebooklm generate video --json

Note: Passing a non-cinematic --format to the cinematic-video alias exits 2 with a UsageError. Use generate video --format <other> for non-cinematic formats.

Generate: `revise-slide`

Revise an individual slide in an existing slide deck using a natural-language prompt.

Python equivalent: client.artifacts.revise_slide(nb_id, artifact_id, slide_index, instructions).

bash
notebooklm generate revise-slide [description] --artifact  --slide N [OPTIONS]

Required Options:

  • -a, --artifact ID - The slide deck artifact ID to revise
  • --slide N - Zero-based index of the slide to revise (0 = first slide)

Optional:

  • -n, --notebook ID - Notebook ID (uses current if not set)
  • --wait / --no-wait - Wait for completion (default: --no-wait)
  • --timeout SECONDS - Maximum seconds to wait (default: 300; no-op without --wait)
  • --interval SECONDS - Seconds between status checks (default: 2; no-op without --wait)
  • --retry N - Retry N times with exponential backoff on rate limit
  • --json - Machine-readable output
  • --prompt-file PATH - Read description from a file (or - for stdin); mutually exclusive with positional argument

Examples:

bash
# Revise the first slide
notebooklm generate revise-slide "Move the title up" --artifact art123 --slide 0

# Revise the fourth slide and wait for completion
notebooklm generate revise-slide "Remove taxonomy table" --artifact art123 --slide 3 --wait

Note: The slide deck must already be fully generated before using revise-slide. Use artifact list to find the artifact ID.


Generate: `report`

Generate a text report (briefing doc, study guide, blog post, or custom).

Python equivalent: client.artifacts.generate_report(nb_id, report_format=..., ...).

bash
notebooklm generate report [description] [OPTIONS]

Options:

  • -n, --notebook ID - Notebook ID (uses current if not set)
  • --format [briefing-doc|study-guide|blog-post|custom] - Report format (default: briefing-doc)
  • --append TEXT - Append extra instructions to the built-in prompt (no effect with --format custom)
  • --language LANG - Output language (precedence: --language > NOTEBOOKLM_HL env > config > 'en')
  • -s, --source ID - Limit to specific source IDs (repeatable, uses all if not specified)
  • --wait / --no-wait - Wait for completion (default: --no-wait)
  • --timeout SECONDS - Maximum seconds to wait (default: 300; no-op without --wait)
  • --interval SECONDS - Seconds between status checks (default: 2; no-op without --wait)
  • --retry N - Retry N times with exponential backoff on rate limit
  • --json - Output as JSON
  • --prompt-file PATH - Read description from a file (or - for stdin); mutually exclusive with positional argument

Examples:

bash
notebooklm generate report --format study-guide
notebooklm generate report "Executive summary for stakeholders" --format briefing-doc

# Generate report from specific sources
notebooklm generate report --format study-guide -s src_001 -s src_002

# Custom report with description (auto-selects custom format)
notebooklm generate report "Create a white paper analyzing the key trends"

# Append instructions to a built-in format
notebooklm generate report --format study-guide --append "Target audience: beginners"
notebooklm generate report --format briefing-doc --append "Focus on AI trends, keep it under 2 pages"

# Read a long custom prompt from file (auto-selects custom format)
notebooklm generate report --prompt-file custom_report.txt

Artifact: `list`, `get`, `get-prompt`, `rename`, `delete`, `export`, `poll`, `wait`, `retry`, `suggestions`

Manage existing artifacts (audio, video, slide decks, quizzes, reports, etc.). Every subcommand resolves the notebook via the standard precedence (-n/--notebook flag > NOTEBOOKLM_NOTEBOOK env > active context).

Python equivalent: client.artifacts.list/get/get_prompt/rename/delete/poll_status/wait_for_completion/retry_failed/suggest_reports(...) for management; export_report / export_data_table / export(...) for export.

bash
notebooklm artifact <list|get|get-prompt|rename|delete|export|poll|wait|retry|suggestions> [OPTIONS]

Common options (all subcommands):

  • -n, --notebook ID - Notebook ID (uses current if not set; supports partial IDs)

Per-subcommand options:

Subcommand Required arguments Options
list (none) --type [all|audio|video|slide-deck|quiz|flashcard|infographic|data-table|mind-map|report|fantasy-map|file], --limit N (default: unlimited), --no-truncate, --json
get ARTIFACT_ID --json
get-prompt ARTIFACT_ID --json
rename ARTIFACT_ID NEW_TITLE --json
delete ARTIFACT_ID -y/--yes (skip confirmation), --json
export ARTIFACT_ID --title TEXT (required), --type [docs|sheets] (default: docs), --json
poll TASK_ID (from generate <type>) --json
wait ARTIFACT_ID --timeout SECONDS (default: 300), --interval SECONDS (default: 2), --json
retry ARTIFACT_ID --wait (block until terminal), --timeout SECONDS (default: 300), --interval SECONDS (default: 2), --json
suggestions (none) --json

Examples:

bash
# Filter artifact list to one notebook and one type, JSON for scripting
notebooklm artifact list --notebook nb_abc --type audio --json

# Inspect a single artifact (partial ID OK)
notebooklm artifact get art123 --json

# Show the prompt an artifact was generated from
notebooklm artifact get-prompt art123 --json

# Rename an artifact
notebooklm artifact rename art123 "Final cut"

# Delete without prompting
notebooklm artifact delete art123 -y --json

# Export a report to Google Docs (title is required)
notebooklm artifact export art123 --title "Climate briefing" --type docs

# Poll a task immediately after a generate call returns
notebooklm generate audio --json   # -> { "task_id": "task_abc...", "status": "..." }
notebooklm artifact poll task_abc

# Block until the artifact finishes (or 600s elapses)
notebooklm artifact wait art123 --timeout 600 --json

# Retry a failed artifact in place (UI "Retry"); --wait blocks until terminal
notebooklm artifact retry art123 --wait

# Get AI-suggested report topics for the active notebook
notebooklm artifact suggestions --json

poll vs wait: The API returns one identifier that doubles as both task_id (right after generate) and artifact_id (once it appears in artifact list). poll is a one-shot status check that does not prefix-match against artifact list (use it immediately after generate); wait blocks until terminal status and accepts partial IDs that resolve against artifact list. See the quick-reference note above for full details.

Download: `audio`, `video`, `slide-deck`, `infographic`, `report`, `mind-map`, `data-table`

Download generated artifacts to your local machine.

Python equivalent: the per-type client.artifacts.download_audio/video/slide_deck/infographic/report/mind_map/data_table(nb_id, path, ...) methods.

bash
notebooklm download <type> [OUTPUT_PATH] [OPTIONS]

Artifact Types and Output Formats:

Type Default Extension Description
audio .m4a Audio overview (podcast) — AAC audio in an MP4 container
video .mp4 Video overview
slide-deck .pdf or .pptx Slide deck as PDF (default) or PowerPoint
infographic .png Infographic image
report .md Report as Markdown (Briefing Doc, Study Guide, etc.)
mind-map .json Mind map as JSON tree structure
data-table .csv Data table as CSV (UTF-8 with BOM for Excel)

Options:

  • --all - Download all artifacts of this type
  • --latest - Download only the most recent artifact (default if no ID/name provided)
  • --earliest - Download only the oldest artifact
  • --name NAME - Download artifact with matching title (supports partial matches)
  • -a, --artifact ID - Select specific artifact by ID (supports partial IDs)
  • --dry-run - Show what would be downloaded without actually downloading
  • --force - Overwrite existing files
  • --no-clobber - Don't overwrite existing files (a single download fails; --all skips them; default is auto-rename)
  • --format [pdf|pptx] - Slide deck format (slide-deck command only, default: pdf)
  • --json - Output result in JSON format

Examples:

bash
# Download the latest podcast
notebooklm download audio ./podcast.m4a

# Download all infographics
notebooklm download infographic --all

# Download a specific slide deck by name
notebooklm download slide-deck --name "Final Presentation"

# Download slide deck as PPTX (editable PowerPoint)
notebooklm download slide-deck --format pptx

# Preview a batch download
notebooklm download audio --all --dry-run

# Download a report as markdown
notebooklm download report ./study-guide.md

# Download mind map as JSON
notebooklm download mind-map ./concept-map.json

# Download data table as CSV (opens in Excel)
notebooklm download data-table ./research-data.csv

Download: `quiz`, `flashcards`

Download quiz questions or flashcard decks in various formats.

Python equivalent: client.artifacts.download_quiz(nb_id, path, output_format="json|markdown|html") and download_flashcards(...) with the same signature.

bash
notebooklm download quiz [OUTPUT_PATH] [OPTIONS]
notebooklm download flashcards [OUTPUT_PATH] [OPTIONS]

Options:

  • -n, --notebook ID - Notebook ID (uses current context if not set)
  • --format FORMAT - Output format: json (default), markdown, or html
  • -a, --artifact ID - Select specific artifact by ID

Output Formats:

  • JSON - Structured data preserving full API fields (answerOptions, rationale, isCorrect, hint)
  • Markdown - Human-readable format with checkboxes for correct answers
  • HTML - Raw HTML as returned from NotebookLM

Examples:

bash
# Download quiz as JSON
notebooklm download quiz quiz.json

# Download quiz as markdown
notebooklm download quiz --format markdown quiz.md

# Download flashcards as JSON (normalizes f/b keys to front/back)
notebooklm download flashcards cards.json

# Download flashcards as markdown
notebooklm download flashcards --format markdown cards.md

# Download flashcards as raw HTML
notebooklm download flashcards --format html cards.html

Note: `list`, `create`, `get`, `save`, `rename`, `delete`

Read, create, and update notebook notes (the "Notes" panel in the web UI). Mind-map artifacts surface as notes in the underlying API but are filtered out of note list — use artifact list --type mind-map for those.

Python equivalent: client.notes.list/create/get/update/delete(...). The CLI's note save and note rename both map to client.notes.update(...) (title and/or content). Mind-map helpers (list_mind_maps, delete_mind_map) live on the same API.

bash
notebooklm note <list|create|get|save|rename|delete> [OPTIONS]

Common options (all subcommands):

  • -n, --notebook ID - Notebook ID (uses current if not set; supports partial IDs)
  • --json - Machine-readable output

Per-subcommand options:

Subcommand Required arguments Type-specific options
list (none) —
create [CONTENT] (positional or stdin via -) --content TEXT (or - for stdin; mutually exclusive with positional), -t/--title TEXT (default: "New Note")
get NOTE_ID —
save NOTE_ID --title TEXT, --content TEXT
rename NOTE_ID NEW_TITLE —
delete NOTE_ID -y/--yes (skip confirmation)

Examples:

bash
# List notes in the active notebook
notebooklm note list --json

# Create a titled note from a positional string
notebooklm note create "Quick thought" -t "Idea: research follow-up"

# Pipe markdown from a file or another command (use `-` to read stdin)
cat draft.md | notebooklm note create -

# Equivalent --content form
cat draft.md | notebooklm note create --content -

# Update an existing note (partial ID OK; only the fields you pass change)
notebooklm note save note_abc --title "Updated title"
notebooklm note save note_abc --content "New body"

# Rename without touching content
notebooklm note rename note_abc "Renamed"

# Delete without prompting
notebooklm note delete note_abc -y --json

Profile: `list`, `create`, `switch`, `delete`, `rename`

Manage named profiles under $NOTEBOOKLM_HOME/profiles/<name>/. Each profile owns its own storage_state.json, context.json, and browser profile, so multiple Google accounts can coexist. The active profile is selected by -p/--profile, NOTEBOOKLM_PROFILE, or the default_profile field in $NOTEBOOKLM_HOME/config.json (in that precedence). See configuration.md for the full profile model.

bash
notebooklm profile <list|create|switch|delete|rename> [OPTIONS]

Per-subcommand options:

Subcommand Required arguments Options
list (none) --json
create NAME --json
switch NAME --json
delete NAME --yes/-y (skip prompt; --confirm is a deprecated alias; the active default profile cannot be deleted), --json
rename OLD_NAME NEW_NAME --json

Examples:

bash
# Enumerate profiles (active flag included in JSON)
notebooklm profile list --json

# Create a profile shell, then authenticate into it
notebooklm profile create work
notebooklm -p work login

# Make 'work' the default for subsequent commands
notebooklm profile switch work

# Rename a profile (does not change `storage_state.json` contents)
notebooklm profile rename work work-old

# Delete a non-active profile without prompting
notebooklm profile delete old-account --yes

Note: profile delete refuses to remove the currently active default profile. Switch to a different profile first (notebooklm profile switch <other>) and then delete.

Skill: `install`, `status`, `uninstall`, `show`, `package`

Manage the bundled NotebookLM agent-skill template. The skill lives in SKILL.md at the repository root; installing it materializes a copy under one of:

  • .claude/skills/notebooklm/SKILL.md (Claude Code, --target claude)
  • .agents/skills/notebooklm/SKILL.md (universal agent skill directory, --target agents)

--scope selects whether to write into the user's home (~/.claude/skills/..., ~/.agents/skills/...) or the current project (./.claude/skills/..., ./.agents/skills/...).

skill package takes neither --scope nor --target: it builds a ZIP archive (zip root: notebooklm/SKILL.md, version-stamped) for upload via Claude Settings → Capabilities — the supported hand-off for sandboxed agent environments such as Claude Cowork, which cannot run skill install against a local directory.

bash
notebooklm skill  [OPTIONS]

Options matrix (defaults: --scope user --target all):

Subcommand --scope choices --target choices Default --target Other options
install user, project all, claude, agents all --dry-run, --no-clobber, --force (project scope)
status user, project all, claude, agents all --json
uninstall user, project all, claude, agents all —
show user, project source, claude, agents source —
package — — — -o/--output PATH (default ./notebooklm-skill.zip; an existing directory — or a PATH ending in a separator — gets the default filename inside it), --force, --json

skill show --target source prints the packaged SKILL.md straight out of the wheel (the canonical content); the other show targets read the materialized copy from disk.

Project-scope install hardening:

  • --dry-run prints the target files and actions without writing.
  • --no-clobber creates missing targets but skips differing existing files.
  • --force overwrites differing project targets.
  • These flags are project-scope only; --scope user preserves the historical always-overwrite behavior.

Examples:

bash
# Install both targets for the current user (default scope+target)
notebooklm skill install

# Install only the Claude Code target into the current project
notebooklm skill install --scope project --target claude --dry-run
notebooklm skill install --scope project --target claude --force

# Inspect what's installed in the user-scope agents directory
notebooklm skill status --scope user --target agents

# Print the packaged skill source (e.g. for piping into another agent's loader)
notebooklm skill show --target source

# Print the installed Claude target verbatim
notebooklm skill show --scope project --target claude

# Remove all installed targets from the current project
notebooklm skill uninstall --scope project --target all

# Build the uploadable archive in the current directory (./notebooklm-skill.zip)
notebooklm skill package

# Write to an explicit file, or drop the default filename into a directory
notebooklm skill package --output dist/notebooklm-v0.8.zip
notebooklm skill package --output dist/

# Overwrite an existing archive, or emit a machine-readable result
notebooklm skill package --output dist/ --force
notebooklm skill package --json

skill package refuses to overwrite an existing archive unless --force is passed. With --json it prints {"path", "version", "entries", "size_bytes"} on success and the standard error envelope on failure.

Codex does not consume the skill subcommand. In this repository it reads the root AGENTS.md file and invokes the notebooklm CLI or Python API directly.

Source: `add-drive`

Add a Google Drive document, slide deck, sheet, or PDF as a source. The Drive --mime-type selects which Drive document type to import (Google Doc / Slides / Sheets / PDF). This is distinct from the file-source --mime-type documented above, which sets the resumable-upload content-type for a locally-uploaded file.

By-reference vs upload-only: NotebookLM's Drive import only ingests Google-native Docs/Slides/Sheets + PDF by reference — the four --mime-type choices above. An upload-only file that merely lives in Drive (e.g. epub/docx/txt/md/rtf/odt/csv/tsv) cannot be imported this way; use source add-drive-file instead, which downloads it server-side (using your session) and uploads it — no local download step needed.

Python equivalent: client.sources.add_drive(nb_id, file_id, title, mime_type=...).

bash
notebooklm source add-drive [OPTIONS] FILE_ID TITLE

Options:

  • -n, --notebook ID - Notebook ID (uses current if not set; supports partial IDs)
  • --mime-type [google-doc|google-slides|google-sheets|pdf] - Drive document type (default: google-doc)
  • --json - Output as JSON

Examples:

bash
# Default — treat the Drive file as a Google Doc
notebooklm source add-drive 1AbcD...XyZ "Project Brief"

# Import as Google Slides
notebooklm source add-drive 1AbcD...XyZ "Quarterly Deck" --mime-type google-slides

# Import a Drive-hosted PDF
notebooklm source add-drive 1AbcD...XyZ "Whitepaper" --mime-type pdf --json

Source: `add-drive-file`

Add an upload-only Google Drive file (epub/docx/pptx/txt/md/rtf/odt/csv/tsv/pdf) by id or share URL. NotebookLM's native Drive import (source add-drive) only ingests Google-native Docs/Slides/Sheets + PDF by reference; for every other Drive-hosted file type, this command downloads the file server-side (using your session) and uploads it through the resumable-upload path — a Drive PDF can go either way.

bash
notebooklm source add-drive-file [OPTIONS] DOCUMENT_ID

Options:

  • -n, --notebook ID - Notebook ID (uses current if not set; supports partial IDs)
  • --title TEXT - Custom title (default: the file's Drive name)
  • --wait - Wait for processing to finish
  • --json - Output as JSON

Examples:

bash
# Download + upload an upload-only Drive file (e.g. a .docx)
notebooklm source add-drive-file 1AbcD...XyZ

# Custom title, wait for processing to complete
notebooklm source add-drive-file 1AbcD...XyZ --title "Meeting Notes" --wait

Source: `stale`, `clean`

source stale is a shell-friendly predicate that reports whether a URL/Drive source needs a refresh; source clean removes duplicate, error, or access-blocked sources in bulk.

Python equivalent: client.sources.check_freshness(nb_id, source_id) returns the same staleness verdict. There is no single bulk "clean" call — combine client.sources.list(...) with a filter on duplicate/error/blocked status and call client.sources.delete(...) per match.

bash
notebooklm source stale [OPTIONS] SOURCE_ID
notebooklm source clean [OPTIONS]

stale exit codes (standard by default; opt-in inverted predicate via --exit-on-stale):

  • Default: 0 on success regardless of freshness, 1 on error. Branch on the JSON stale/fresh fields (or stdout text) for the verdict.
  • With --exit-on-stale: 0 if stale (needs refresh), 1 if fresh — for back-compat with the if … ; then refresh; fi shell idiom.
  • See CLI Exit-Code Convention for details.

stale options: -n/--notebook ID, --exit-on-stale, --json.

clean options: -n/--notebook ID, --dry-run (preview the candidate set), -y/--yes (skip the confirmation prompt), --json.

Examples:

bash
# Refresh a single stale URL source if needed (back-compat shell idiom)
if notebooklm source stale --exit-on-stale src_abc; then
  notebooklm source refresh src_abc
fi

# Default semantics: branch on JSON output instead
if [[ "$(notebooklm source stale src_abc --json | jq -r .stale)" == "true" ]]; then
  notebooklm source refresh src_abc
fi

# Preview what `clean` would remove
notebooklm source clean --dry-run

# Remove duplicates/errors/blocked sources without prompting
notebooklm source clean -y --json

Common Workflows

Research → Podcast

Find information on a topic and create a podcast about it.

bash
# 1. Create a notebook for this research
notebooklm create "Climate Change Research" --use --json
# Output includes: {"active_notebook_id": "...", "notebook": {...}}

# 2. Add a starting source
notebooklm source add "https://en.wikipedia.org/wiki/Climate_change"

# 3. Research more sources automatically (blocking; --import-all retry budget defaults to 1800s)
notebooklm source add-research "climate change policy 2024" --mode deep --import-all

# 4. Generate a podcast
notebooklm generate audio "Focus on policy solutions and future outlook" --format debate --wait

# 5. Download the result
notebooklm download audio ./climate-podcast.m4a

Research → Podcast (Non-blocking with Subagent)

For LLM agents, use non-blocking mode to avoid timeout:

bash
# 1. Create notebook and set it active
notebooklm create "Climate Change Research" --use

# 2. Add initial source
notebooklm source add "https://en.wikipedia.org/wiki/Climate_change"

# 3. Start deep research (non-blocking)
notebooklm source add-research "climate change policy 2024" --mode deep --no-wait
# Returns immediately

# 4. In a subagent, wait for research and import
notebooklm research wait --import-all
# Blocks until complete, then imports sources

# 5. Continue with podcast generation...

Research commands:

  • research status - Check if research is in progress, completed, or not running
  • research wait --import-all - Block until research completes, then import sources

Document Analysis → Study Materials

Upload documents and create study materials.

bash
# 1. Create notebook
notebooklm create "Exam Prep"
notebooklm use 

# 2. Add your documents
notebooklm source add "./textbook-chapter.pdf"
notebooklm source add "./lecture-notes.pdf"

# 3. Get a summary
notebooklm summary

# 4. Generate study materials
notebooklm generate quiz --difficulty hard --wait
notebooklm generate flashcards --wait
notebooklm generate report --format study-guide --wait

# 5. Ask specific questions
notebooklm ask "Explain the key concepts in chapter 3"
notebooklm ask "What are the most likely exam topics?"

YouTube → Quick Summary

Turn a YouTube video into notes.

bash
# 1. Create notebook and add video
notebooklm create "Video Notes"
notebooklm use 
notebooklm source add "https://www.youtube.com/watch?v=VIDEO_ID"

# 2. Get summary
notebooklm summary

# 3. Ask questions
notebooklm ask "What are the main points?"
notebooklm ask "Create bullet point notes"

# 4. Generate a quick briefing doc
notebooklm generate report --format briefing-doc --wait

Bulk Import

Add multiple sources at once.

bash
# Set active notebook
notebooklm use 

# Add multiple URLs
notebooklm source add "https://example.com/article1"
notebooklm source add "https://example.com/article2"
notebooklm source add "https://example.com/article3"

# Add multiple local files (use a loop)
for f in ./papers/*.pdf; do
  notebooklm source add "$f"
done

Doctor: `doctor`

Check profile setup, auth status, and migration.

Diagnoses common issues with profiles, authentication, and directory structure. Use --fix to automatically repair detected problems.

bash
notebooklm doctor [OPTIONS]

Options:

  • --fix - Attempt to fix detected issues (e.g. missing directories, broken configurations)
  • --json - Output diagnostic results as a JSON structure for scripting/automation

Examples:

bash
# Check profile and authentication health
notebooklm doctor

# Auto-repair environment issues
notebooklm doctor --fix

# Print diagnostics in machine-readable format
notebooklm doctor --json

Agent: `agent show`

Show bundled instructions for supported agent environments.

This command displays tailored instructions for different LLM agents (Codex or Claude Code) to help them understand how to use this CLI programmatically.

bash
notebooklm agent show [OPTIONS] {codex|claude}

Examples:

bash
# Show instructions for Codex
notebooklm agent show codex

# Show instructions for Claude Code
notebooklm agent show claude

Note: agent show codex prefers the root AGENTS.md file when running from a source checkout, so the CLI mirrors the same instructions Codex sees in the repository.


Tips for LLM Agents

When using this CLI programmatically:

  1. Three ways to specify notebooks: (a) notebooklm use to set persistent context, (b) NOTEBOOKLM_NOTEBOOK= env var for the calling shell, or (c) -n directly on each command. Resolution precedence: -n/--notebook flag > NOTEBOOKLM_NOTEBOOK env > active context > error.

  2. Generation commands are async by default (except mind-map):

    • mind-map: Synchronous, completes instantly (no --wait option)
    • All others: Return immediately with task ID (default: --no-wait)

    Avoid --wait for LLM agents—all async operations can take minutes to 30+ minutes. Use artifact wait in a background task or inform the user to check back later. Long-waits (generate <kind> --wait, artifact wait, source wait) honor SIGINT cleanly: Ctrl-C exits 130 with a "Resume with: notebooklm artifact poll <task_id>" hint (or notebooklm source wait on the source path) instead of a Python traceback. Under --json the cancellation surfaces as {"error": true, "code": "CANCELLED", "resume_hint": "..."}.

  3. Partial IDs work: notebooklm use abc matches any notebook ID starting with "abc". The same prefix-match resolves -n/--notebook, -s/--source, and -a/--artifact arguments across the CLI.

  4. Check status: Use notebooklm status to see the current active notebook and conversation.

  5. Auto-detection: source add auto-detects content type:

    • URLs starting with http → web source
    • YouTube URLs → video transcript extraction
    • File paths → file upload (PDF, text, Markdown, Word, audio, video, images)
    • Path-shaped strings that don't exist on disk (e.g. ./missin.md) still ingest as inline text but emit a stderr warning so a typo doesn't silently masquerade as a successful upload. Pass --type text to suppress the warning.
  6. Stdin pipelines: Four surfaces accept the canonical Unix - placeholder for "read from stdin": notebooklm ask -, notebooklm ask --prompt-file -, notebooklm note create - (or --content -), and notebooklm source add - (forces text-source path; bypasses path-shaped detection). Same convention applies to the various generate <kind> --prompt-file - flags.

  7. Quiet output for CI/cron: Pass --quiet (root-level) to suppress status output and INFO/WARN log records; errors still surface and structured --json payloads are still emitted. --quiet is mutually exclusive with -v/-vv (combining the two raises UsageError with exit 2). For long-running keepalive loops, notebooklm auth refresh --quiet is the subcommand-scoped equivalent.

  8. Error handling: Commands exit with non-zero status on failure (1 for user/library errors, 2 for system/unexpected errors per CLI Exit-Code Convention). With --json, failures surface as a typed envelope {"error": true, "code": "<TYPED_CODE>", "message": "..."} on stdout; without --json, error messages go to stderr.

  9. Deep research: Use --no-wait with source add-research --mode deep to avoid blocking. Then use research wait --import-all in a subagent to wait for completion.

  10. Shell completion: notebooklm completion prints a completion script that enables ID-aware tab completion for -n/--notebook, -s/--source, and -a/--artifact from your active profile's live IDs. See the completion section for install snippets.


File: docs/configuration.md

Configuration

Status: Active
Last Updated: 2026-08-05

This guide covers storage locations, environment settings, and configuration options for notebooklm-py.

File Locations

All data is stored under ~/.notebooklm/ by default, organized by profile:

text
~/.notebooklm/
├── config.json           # Global config: default_profile, language
├── profiles/
│   ├── default/          # Default profile (auto-created)
│   │   ├── storage_state.json    # Authentication cookies and session
│   │   ├── context.json          # CLI context (active notebook, conversation)
│   │   └── browser_profile/      # Persistent Chromium profile
│   ├── work/             # Named profile example
│   │   ├── storage_state.json
│   │   ├── context.json
│   │   └── browser_profile/
│   └── personal/
│       └── ...

config.json stores process-wide settings — the persisted default profile
name (under the default_profile key) and the configured interface language
(language). It is global, not per-profile (see
src/notebooklm/paths.py and src/notebooklm/cli/language_cmd.py).

Legacy layout: If upgrading from a pre-profile version, the first run auto-migrates flat files into profiles/default/. The migration runs under a single-writer filelock rooted at ~/.notebooklm/.migration.lock, so concurrent CLI invocations (e.g., container start-up races) cannot interleave copies — the loser of the lock re-checks the completion marker and no-ops (see src/notebooklm/migration.py). The legacy flat layout continues to work as a fallback.

You can relocate all files by setting NOTEBOOKLM_HOME:

bash
export NOTEBOOKLM_HOME=/custom/path
# All files now go to /custom/path/profiles//

Storage State (`storage_state.json`)

Contains the authentication data extracted from your browser session:

json
{
  "cookies": [
    {
      "name": "SID",
      "value": "...",
      "domain": ".google.com",
      "path": "/",
      "expires": 1234567890,
      "httpOnly": true,
      "secure": true,
      "sameSite": "Lax"
    },
    ...
  ],
  "origins": [],
  "notebooklm": {
    "version": 1,
    "account": {
      "authuser": 0,
      "email": "[email protected]"
    }
  }
}

Cookie requirements (empirically validated via single-, pair-, and three-way ablation; see auth-cookie-lifecycle.md; enforced by _validate_required_cookies() in _auth/cookie_policy.py):

  • Tier 1 — strictly required (raises on absence): SID AND __Secure-1PSIDTS. SID is the only individually-required cookie (__Secure-1PSIDTS is removable on its own because Google can re-mint it via RotateCookies), but the pair-wise check uncovered that as soon as __Secure-1PSIDTS and any one other auth cookie are both missing, Google rejects with Authentication expired or invalid. The library therefore enforces both up-front. Authoritative value: MINIMUM_REQUIRED_COOKIES in _auth/cookie_policy.py.
  • Tier 2 — secondary binding (logs a warning if absent): either OSID is present, or APISID and SAPISID are present together with bare LSID (the LSID conjunct is required — the pair alone fails, per the three-way ablation in #1977). Without this, even valid Tier 1 cookies can't authenticate the homepage GET. Logged rather than raised so unverified edge-case flows (e.g. Workspace SSO) aren't broken by a too-strict client check.

In practice: extract the full cookie set via notebooklm login and don't try to subset it. Partial extractions (a known failure mode of browser-cookies tooling under Chrome 127+ App-Bound Encryption) are the leading suspect for "auth expires immediately" reports — see #371.

The optional notebooklm.account block records the Google account route for
multi-account sessions. New notebooklm login runs write it when the active
account can be discovered safely. Older Playwright-created files can be repaired
with notebooklm auth refresh when the storage state maps to one visible
account; if several accounts are visible, use
notebooklm login --browser-cookies --account EMAIL to bind the
intended account explicitly. auth refresh only repairs absent metadata; if
metadata exists but points at the wrong account, re-bind explicitly with the
browser-cookie login path.

Override location:

bash
notebooklm --storage /path/to/storage_state.json list

Context File (`context.json`)

Stores the current CLI context, such as the active notebook:

json
{
  "notebook_id": "abc123def456",
  "title": "Quarterly review notes",
  "is_owner": true,
  "role": "owner",
  "created_at": "2026-05-01T17:43:21Z"
}

Field summary:

  • notebook_id — currently selected notebook, written by notebooklm use and read by every command that takes -n/--notebook.
  • title, is_owner, role, created_at — optional notebook metadata captured at selection time so status / display commands don't need an extra round-trip. Omitted when the CLI didn't have the values to write (see set_current_notebook in src/notebooklm/cli/context.py). role is "owner" / "editor" / "viewer"; is_owner is the role == "owner" shorthand and is retained for backward compatibility. Contexts written before role existed carry only is_owner, and status falls back to it.

This file is managed automatically by notebooklm use, notebooklm clear, and the auth commands.

Browser Profile (`browser_profile/`)

A persistent Chromium user data directory used during notebooklm login.

Why persistent? Google blocks automated login attempts. A persistent profile makes the browser appear as a regular user installation, avoiding bot detection.

To reset: Delete the browser_profile/ directory and run notebooklm login again.

With --storage , the browser profile is isolated with that storage file.
The conventional storage_state.json uses a browser_profile/ directory beside
it; any normal-length custom filename uses .browser_profile. For example,
A.json and B.json use A.json.browser_profile/ and
B.json.browser_profile/ respectively. If that filename would exceed the
255-byte component limit, the canonical storage path maps to a short stable
storage-<hash>.browser_profile/ name; relative and absolute aliases map to the
same directory.

Browser directories newly created for explicit storage contain a
.notebooklm-owned marker. login --fresh refuses to recursively delete an
unowned sidecar, and auth logout skips it and reports the preserved path.
Canonical named-profile and legacy browser directories remain managed even when
their storage_state.json is selected explicitly with --storage; an arbitrary
same-named directory outside NOTEBOOKLM_HOME remains unowned. A marker-only
directory does not count as a reusable L3 browser session.

Upgrading: Existing custom-storage browser sessions are not migrated. You
may need one interactive sign-in per storage file. Do not copy or delete the
old shared browser profile unless its account/profile ownership is known.

Master Token (`master_token.json`)

Written only by notebooklm login --master-token (the [headless] extra). Holds
a durable Google master token (mode 0600) that mints/refreshes the profile's
storage_state.json cookies with no per-session browser. When present beside a
profile's storage_state.json, an expired session re-mints from it
automatically. If storage is absent, notebooklm auth refresh mints it from the
exact sibling token and passively validates once. The legacy login refresh flag
remains an unconditional forced re-mint.

json
{"version": 1, "email": "...", "android_id": "<hex>", "master_token": "aas_et/..."}

⚠️ Full-account, durable credential — larger blast radius than
storage_state.json; dedicated/throwaway account only. See
installation.md#alternative-master-token-auth-no-cookie-file-to-ship-survives-expiry.

Environment Variables

Variable Description Default
NOTEBOOKLM_HOME Base directory for all files ~/.notebooklm
NOTEBOOKLM_PROFILE Active profile name default
NOTEBOOKLM_AUTH_JSON Inline authentication JSON (for CI/CD) -
NOTEBOOKLM_NOTEBOOK Default notebook ID for commands without -n/--notebook -
NOTEBOOKLM_HL Default interface/output language code (e.g. en, ja, zh_Hans) en
NOTEBOOKLM_BASE_URL Gemini Notebook base URL. Constrained to https://notebook.google.com (default) or https://notebooklm.google.com (pre-rebrand personal, still served) or https://notebooklm.cloud.google.com (enterprise) https://notebook.google.com
NOTEBOOKLM_BL bl (build label) URL parameter for the chat streaming endpoint; override when chasing a regression tied to a specific frontend build snapshot built-in default in _env.DEFAULT_BL (drift-monitored nightly)
NOTEBOOKLM_TRANSPORT HTTP transport backend: httpx (default) or curl_cffi (opt-in browser-TLS impersonation; requires the curl_cffi package). Use curl_cffi where the default transport is TLS-fingerprint-blocked. httpx
NOTEBOOKLM_LOG_LEVEL Logging level: DEBUG, INFO, WARNING, ERROR WARNING
NOTEBOOKLM_DEBUG_RPC Legacy: Enable RPC debug logging (use LOG_LEVEL=DEBUG instead) false
NOTEBOOKLM_DEBUG Show untruncated RPC response bodies in error messages instead of the default 80-char preview (verbose; intended for deep debugging) 0
NOTEBOOKLM_STRICT_DECODE Retired (ignored since v0.7.0). Strict decoding is now the only mode: safe_index always raises UnknownRPCMethodError on schema drift. The former 0 warn-and-fallback opt-out was removed. (ignored)
NOTEBOOKLM_RPC_OVERRIDES JSON object mapping RPCMethod enum names to RPC ID strings (community self-patch when Google rotates a method ID; e.g. {"LIST_NOTEBOOKS":"AbC123"}) -
NOTEBOOKLM_REFRESH_CMD Optional command (argv list, or shell string with _USE_SHELL=1) invoked when auth refresh is required. Must exit 0 after writing a refreshed storage_state.json; the parent reloads from disk -
NOTEBOOKLM_REFRESH_CMD_USE_SHELL Opt the NOTEBOOKLM_REFRESH_CMD subprocess back into shell=True execution. Default shell=False (argv list) — set to the literal 1 (only "1" is honored — not true/yes/on) when the refresh command requires shell metacharacters 0
NOTEBOOKLM_REFRESH_CMD_MIDSESSION Opt in (literal 1) to running NOTEBOOKLM_REFRESH_CMD mid-session (the L2.5 rung), not only at cold start. Off by default for one release so operators whose commands assume cold-start-only invocation are not surprised inside long-lived servers; flips to default-on a later release (ADR-0030) 0
NOTEBOOKLM_REFRESH_CMD_LOG_OUTPUT Opt in (literal 1) to routing the refresh command's captured stdout/stderr to the redacting DEBUG logger. Off by default — the default DEBUG line carries only basename + exit code + byte counts, so promoting the rung into long-lived servers does not widen exposure of whatever the command prints 0
NOTEBOOKLM_REFRESH_PROFILE Child-process hint set for NOTEBOOKLM_REFRESH_CMD; names the resolved profile being refreshed resolved profile
NOTEBOOKLM_REFRESH_STORAGE_PATH Child-process hint set for NOTEBOOKLM_REFRESH_CMD; path to the storage_state.json file the command must rewrite resolved storage path
NOTEBOOKLM_DISABLE_KEEPALIVE_POKE Disable the proactive accounts.google.com/RotateCookies poke that refreshes __Secure-1PSIDTS ahead of expiry 0
NOTEBOOKLM_PROMOTION_EXIT_TIMEOUT Seconds the process waits at exit for an in-flight one-time migration of a pre-v0.x context.json account into storage_state.json. It is a ceiling, not a delay: a finished migration exits immediately. Raise it on a machine where the write is slow (antivirus, network storage); set 0 to never wait. An incomplete wait is always reported at WARNING, so note that --quiet (which raises the floor to ERROR) suppresses that signal. Non-numeric, negative, and non-finite values are refused with a warning and the default is used; a finite value above the platform join limit is clamped. 30
NOTEBOOKLM_HEADLESS_REAUTH Opt in to layer-3 headless re-auth during cold construction and automatic refresh paths. Explicit from_storage(allow_headless=True), client.refresh_auth(allow_headless=True), or auth refresh --allow-headless does not require this env var. 0
NOTEBOOKLM_HEADLESS_REAUTH_CDP_URL Optional loopback Chrome DevTools endpoint for layer-3 headless re-auth, e.g. http://127.0.0.1:9222. Non-loopback endpoints are ignored for credential safety. -
NOTEBOOKLM_MCP_TRANSPORT MCP server transport for notebooklm-mcp: stdio or http stdio
NOTEBOOKLM_MCP_HOST MCP HTTP transport bind host; non-loopback refused unless NOTEBOOKLM_MCP_ALLOW_EXTERNAL_BIND=1 127.0.0.1
NOTEBOOKLM_MCP_PORT MCP HTTP transport bind port 9420
NOTEBOOKLM_MCP_ALLOW_EXTERNAL_BIND Allow MCP HTTP transport to bind a non-loopback host. Use only behind a trusted proxy. 0
NOTEBOOKLM_MCP_OAUTH_PASSWORD Password gating the self-hosted OAuth authorization server that lets claude.ai connect to the remote MCP server (≥16 chars). Set together with NOTEBOOKLM_MCP_OAUTH_BASE_URL; both unset → bearer-only. -
NOTEBOOKLM_MCP_OAUTH_BASE_URL Bare public HTTPS origin (no path) the self-hosted OAuth endpoints (/authorize, /token, /.well-known/*) mount under. Required with NOTEBOOKLM_MCP_OAUTH_PASSWORD; partial/weak/non-HTTPS config refuses to start. -
NOTEBOOKLM_MCP_PUBLIC_URL Public base URL for the remote MCP file upload/download signed-URL side-channel (falls back to NOTEBOOKLM_MCP_OAUTH_BASE_URL). Unset → source_add type=file / artifact_download return a "not configured" error. -
NOTEBOOKLM_MCP_TRUST_PROXY Trust the proxy-set CF-Connecting-IP header as the self-hosted-OAuth login-throttle key. Only enable behind a trusted proxy (e.g. the Cloudflare tunnel); default off keys on the socket peer. 0
NOTEBOOKLM_MCP_STRICT_IDS Strict IDs-only mode for MCP tools: require a full canonical id for every notebook/source/note/artifact reference and reject names, titles, and short id prefixes (fail-fast, deterministic automation). 0
NOTEBOOKLM_SERVER_TOKEN Bearer token required by every REST /v1 request. The REST server refuses to start without it. -
NOTEBOOKLM_SERVER_HOST REST server bind host; non-loopback refused unless NOTEBOOKLM_SERVER_ALLOW_EXTERNAL_BIND=1 127.0.0.1
NOTEBOOKLM_SERVER_PORT REST server bind port 8000
NOTEBOOKLM_SERVER_ALLOW_EXTERNAL_BIND Allow REST server to bind a non-loopback host. Use only behind a trusted proxy. 0
NOTEBOOKLM_SERVER_SOURCE_MUTATION_CONCURRENCY Max concurrent REST source create/rename/delete/batch handlers. 4
NOTEBOOKLM_SERVER_SOURCE_WAIT_CONCURRENCY Max concurrent REST source wait handlers. 4
NOTEBOOKLM_SERVER_GENERATION_CONCURRENCY Max concurrent REST artifact generation/retry handlers. 2
NOTEBOOKLM_SERVER_DOWNLOAD_CONCURRENCY Max concurrent REST artifact download handlers. 2
NOTEBOOKLM_SERVER_RESEARCH_CONCURRENCY Max concurrent REST research start/cancel/import handlers. 2
NOTEBOOKLM_SERVER_CHAT_CONCURRENCY Max concurrent REST blocking chat ask handlers. 4
NOTEBOOKLM_QUIET_DEPRECATIONS Suppress the project's public-API DeprecationWarnings (the one-off warnings routed through warn_deprecated, e.g. awaiting from_storage(...)). Set to a truthy value (1 / true / yes / on, case-insensitive) to silence them; see docs/deprecations.md. (warnings emitted)
NOTEBOOKLM_FUTURE_ERRORS Retired (removed in v0.8.0; ignored). It was the v0.7.0 forward-compat preview gate for the v0.8.0 error contract; now that every break it staged is the default, the flag is a no-op — setting it has no effect. See docs/deprecations.md. (ignored)
NOTEBOOKLM_VCR_RECORD_ERRORS Synthetic-error injection mode for VCR test cassettes (429, 5xx, expired_csrf) -

Public config API vs internal resolvers

src/notebooklm/_env.py owns internal environment/default resolution for
runtime behavior. It reads process environment variables and contains internal
defaults such as DEFAULT_BL for the chat streaming build label.

notebooklm.config is the stable public import surface. It intentionally
re-exports only the supported endpoint/language helpers:
DEFAULT_BASE_URL, ENTERPRISE_BASE_HOST, get_base_host, get_base_url,
get_default_language, and PERSONAL_BASE_HOST. Existing imports
from notebooklm.config remain supported; internal-only _env names should
not be imported by downstream code.

Env vars and precedence

Every NOTEBOOKLM_* variable read by the library and CLI, in one place. CLI
flags always win over env vars; env vars win over persisted profile config /
context; built-in defaults are the last fallback. The "Resolved by" column
points at the canonical resolver so the precedence rule for each variable can
be audited from one location.

Variable Purpose Resolution order (highest → lowest) Resolved by
NOTEBOOKLM_PROFILE Active profile name. Selects which ~/.notebooklm/profiles/<name>/ directory backs storage and context. -p/--profile flag → NOTEBOOKLM_PROFILE → default_profile from ~/.notebooklm/config.json → default paths.resolve_profile
NOTEBOOKLM_AUTH_JSON Inline storage_state.json payload for CI/CD; bypasses on-disk profile storage entirely. --storage flag → NOTEBOOKLM_AUTH_JSON → profile-aware storage_state.json → legacy fallback auth.load_auth_from_storage
NOTEBOOKLM_HOME Base directory for all per-profile files. NOTEBOOKLM_HOME → ~/.notebooklm paths.get_home_dir
NOTEBOOKLM_HL Default interface/output language for generate <kind> and the hl query parameter on every batchexecute RPC. --language flag → NOTEBOOKLM_HL → language value from global ~/.notebooklm/config.json (NOT per-profile) → en language.resolve_hl
NOTEBOOKLM_LOG_LEVEL DEBUG/INFO/WARNING/ERROR floor for the notebooklm package logger. --quiet flag (forces ERROR) → -v/-vv flags (force INFO/DEBUG) → NOTEBOOKLM_DEBUG_RPC=1 (forces DEBUG) → NOTEBOOKLM_LOG_LEVEL → WARNING _logging.configure_logging + notebooklm_cli.cli
NOTEBOOKLM_DEBUG_RPC Legacy alias that sets the package logger to DEBUG. Prefer NOTEBOOKLM_LOG_LEVEL=DEBUG for new code. (See NOTEBOOKLM_LOG_LEVEL.) _logging.configure_logging
NOTEBOOKLM_NOTEBOOK Default notebook ID when no -n/--notebook flag is passed. Composes with notebooklm use so per-shell overrides do not clobber the persisted active-notebook context. -n/--notebook flag → NOTEBOOKLM_NOTEBOOK → active context (from notebooklm use) → error cli.helpers.require_notebook (Click also reads it natively via cli/options.py:notebook_option's envvar=)
NOTEBOOKLM_RPC_OVERRIDES JSON object mapping RPCMethod enum names to RPC ID strings (e.g. {"LIST_NOTEBOOKS": "AbC123"}). Overrides runtime RPC IDs — community self-patch when Google rotates a method ID. Empty string / unset disables the mechanism; invalid JSON or non-object payloads emit a WARNING and are ignored. Process env, evaluated per RPC resolve (cached on the raw env string). notebooklm.rpc.overrides._parse_rpc_overrides
NOTEBOOKLM_QUIET_DEPRECATIONS Suppress the project's public-API DeprecationWarnings — the one-off warnings routed through src/notebooklm/_deprecation.py::warn_deprecated (e.g. awaiting from_storage(...)). Set to a truthy value (1 / true / yes / on) to silence them. See docs/deprecations.md. (warnings emitted) _deprecation._deprecations_quiet / deprecations_quiet
NOTEBOOKLM_FUTURE_ERRORS Retired (removed in v0.8.0; ignored). It was the v0.7.0 forward-compat preview gate for the v0.8.0 error contract (ADR-0019 / umbrella #1346). Now that every break it staged — get() raising *NotFoundError, the attribute-only typed returns, the removed interval= alias, the bool→None returns, the refusal-raises, and the mutate-existing fail-loud — is the default, the flag is a no-op: setting it has no effect. See docs/deprecations.md. (ignored) —
NOTEBOOKLM_STRICT_DECODE Retired (ignored since v0.7.0). Strict decoding is the only mode — safe_index always raises UnknownRPCMethodError on schema drift. The former 0 warn-and-fallback opt-out was removed; setting the variable has no effect. (ignored) —
NOTEBOOKLM_BASE_URL Gemini Notebook base URL. Constrained to https://notebook.google.com (default) or https://notebooklm.google.com (pre-rebrand personal host, still served — the documented rollback lever; if auth fails after switching, re-run notebooklm login --fresh) or https://notebooklm.cloud.google.com (enterprise); other schemes/hosts/paths raise ValueError. Process env on every base-URL lookup. _env.get_base_url
NOTEBOOKLM_BL bl (build label) URL parameter sent on the chat streaming endpoint (ChatAPI.ask). Pins the frontend build the request is attributed to. The built-in _env.DEFAULT_BL is watched by the nightly canary's build-label lane, which compares it against the label Google actually serves; an override here does not change that verdict. Process env on every chat stream call; whitespace-only falls back to _env.DEFAULT_BL. _env.get_default_bl
NOTEBOOKLM_DEBUG When 1, RPC error messages include the full untruncated response body instead of the default 80-char preview. Verbose; intended for deep debugging only. Process env on each error formatting call. exceptions._truncate_response_preview
NOTEBOOKLM_REFRESH_CMD Optional command invoked when auth refresh is required. Must exit 0 after writing a refreshed storage_state.json; the parent reloads cookies from disk. Stdout/stderr are not parsed (only surfaced in the non-zero-exit error message). Parsing honors NOTEBOOKLM_REFRESH_CMD_USE_SHELL. Process env on each refresh subprocess spawn. auth refresh-spawn helper (constant NOTEBOOKLM_REFRESH_CMD_ENV in notebooklm.auth)
NOTEBOOKLM_REFRESH_CMD_USE_SHELL Opt the optional NOTEBOOKLM_REFRESH_CMD subprocess back into shell=True. Default shell=False parses the command with shlex.split and invokes it as an argv list (safer; resists shell-injection footguns when the env var is sourced from CI configs or container env files). Process env on each refresh subprocess spawn. auth refresh-spawn helper (constant NOTEBOOKLM_REFRESH_CMD_USE_SHELL_ENV in notebooklm.auth)
NOTEBOOKLM_REFRESH_CMD_MIDSESSION Opt in (literal 1) to firing NOTEBOOKLM_REFRESH_CMD mid-session (the L2.5 rung of the unified ladder), not just at cold start. Default off for one release — the rung was cold-start-only before, and enabling it inside a long-lived server changes when the operator's command runs; flips to default-on a later release. See ADR-0030. Read on the mid-session recovery path. auth._run_refresh_cmd (constant NOTEBOOKLM_REFRESH_CMD_MIDSESSION_ENV in notebooklm.auth)
NOTEBOOKLM_REFRESH_CMD_LOG_OUTPUT Opt in (literal 1) to routing the refresh command's captured stdout/stderr into the redacting DEBUG logger. Default off: because the mid-session promotion widens exposure of whatever the command prints inside long-lived servers, the default DEBUG line carries only basename + exit code + byte counts. Process env / logging path on each refresh subprocess spawn. auth._run_refresh_cmd (constant NOTEBOOKLM_REFRESH_CMD_LOG_OUTPUT_ENV in notebooklm.auth)
NOTEBOOKLM_REFRESH_PROFILE Child env var injected into NOTEBOOKLM_REFRESH_CMD; names the resolved NotebookLM profile that is being refreshed. Refresh scripts may read it, but setting it in the parent shell does not select the profile. Set by auth refresh-spawn helper from the resolved profile. auth._run_refresh_cmd
NOTEBOOKLM_REFRESH_STORAGE_PATH Child env var injected into NOTEBOOKLM_REFRESH_CMD; points to the storage_state.json file the command must rewrite before exiting 0. Refresh scripts may read it, but setting it in the parent shell does not select storage. Set by auth refresh-spawn helper from the explicit storage path or profile-aware storage path. auth._run_refresh_cmd
NOTEBOOKLM_DISABLE_KEEPALIVE_POKE When 1, disable the proactive accounts.google.com/RotateCookies poke that refreshes __Secure-1PSIDTS ahead of expiry. Useful when running behind a proxy that rejects the extra request, or in offline test fixtures. Process env on every keepalive check. auth keepalive guards (constant NOTEBOOKLM_DISABLE_KEEPALIVE_POKE_ENV in notebooklm.auth)
NOTEBOOKLM_PROMOTION_EXIT_TIMEOUT Seconds the process waits at exit for an in-flight one-time migration of a pre-v0.x context.json account into storage_state.json. A ceiling shared by all outstanding writers, not a delay — a finished migration exits immediately. 0 never waits. Unset, empty, or whitespace-only falls back to the default; non-numeric, negative, and non-finite (inf/nan) values are refused with a WARNING and the default is used; a finite value above threading.TIMEOUT_MAX is clamped. An incomplete wait is always reported at WARNING (so --quiet, which forces ERROR, suppresses it). Process env, read once per process at exit → 30.0 _auth.profile_migration._promotion_exit_timeout
NOTEBOOKLM_HEADLESS_REAUTH Opt in to layer-3 headless re-auth for cold construction and automatic refresh paths. Explicit Python/CLI allow_headless flags do not require the env var. Literal 1 enables; all other values disabled. _auth.headless_reauth.headless_reauth_env_enabled
NOTEBOOKLM_HEADLESS_REAUTH_CDP_URL Optional Chrome DevTools Protocol endpoint for layer-3 headless re-auth. Must be loopback (127.0.0.1, ::1, or localhost); remote endpoints are ignored because CDP is account-equivalent. Explicit function argument → env var → no CDP arm. _auth.headless_reauth.resolve_cdp_url
NOTEBOOKLM_MCP_TRANSPORT Default transport for notebooklm-mcp: stdio or http. CLI --transport wins. --transport flag → env var → stdio mcp.__main__._build_parser
NOTEBOOKLM_MCP_HOST HTTP bind host for notebooklm-mcp --transport http. Non-loopback refused unless NOTEBOOKLM_MCP_ALLOW_EXTERNAL_BIND=1. --host flag → env var → 127.0.0.1 mcp.__main__._build_parser / _serving.check_bind_allowed
NOTEBOOKLM_MCP_PORT HTTP bind port for notebooklm-mcp --transport http. --port flag → env var → 9420 mcp.__main__._build_parser / _resolve_port
NOTEBOOKLM_MCP_ALLOW_EXTERNAL_BIND Allow MCP HTTP transport to bind a non-loopback host. Use only behind a trusted proxy. Literal 1 enables; all other values disabled. mcp.__main__._check_http_bind_allowed → _serving.check_bind_allowed
NOTEBOOKLM_MCP_TRUST_PROXY Trust the proxy-set CF-Connecting-IP header as the self-hosted-OAuth login-throttle key. Enable only behind a trusted proxy (e.g. the Cloudflare tunnel); default off keys the throttle on the socket peer. Literal 1 enables; all other values disabled. mcp._oauth.get_oauth_config / _client_ip
NOTEBOOKLM_MCP_STRICT_IDS Strict IDs-only mode: MCP notebook/source/note/artifact references must be a full canonical id; names, titles, and short id prefixes are rejected before any list call (deterministic automation). Off by default → default name/prefix/title resolution is unchanged. Literal 1 enables; all other values disabled. mcp._resolve._strict_ids_enabled
NOTEBOOKLM_SERVER_TOKEN Bearer token required by every REST /v1 request. The server refuses to start when unset/empty. --token flag → env var → startup failure server.__main__._check_token_configured / server._auth.require_auth
NOTEBOOKLM_SERVER_HOST REST server bind host. Non-loopback refused unless NOTEBOOKLM_SERVER_ALLOW_EXTERNAL_BIND=1. --host flag → env var → 127.0.0.1 server.__main__._build_parser / _serving.check_bind_allowed
NOTEBOOKLM_SERVER_PORT REST server bind port. --port flag → env var → 8000 server.__main__._build_parser / _resolve_port
NOTEBOOKLM_SERVER_ALLOW_EXTERNAL_BIND Allow REST server to bind a non-loopback host. Use only behind a trusted proxy. Literal 1 enables; all other values disabled. server.__main__._check_bind_allowed → _serving.check_bind_allowed
NOTEBOOKLM_SERVER_SOURCE_MUTATION_CONCURRENCY Max concurrent REST source create/rename/delete/batch handlers. Env var → 4; blank uses default; must be integer >=1. server._limits.ServerLimiters.from_env
NOTEBOOKLM_SERVER_SOURCE_WAIT_CONCURRENCY Max concurrent REST source wait handlers. Env var → 4; blank uses default; must be integer >=1. server._limits.ServerLimiters.from_env
NOTEBOOKLM_SERVER_GENERATION_CONCURRENCY Max concurrent REST artifact generation/retry handlers. Env var → 2; blank uses default; must be integer >=1. server._limits.ServerLimiters.from_env
NOTEBOOKLM_SERVER_DOWNLOAD_CONCURRENCY Max concurrent REST artifact download handlers. Env var → 2; blank uses default; must be integer >=1. server._limits.ServerLimiters.from_env
NOTEBOOKLM_SERVER_RESEARCH_CONCURRENCY Max concurrent REST research start/cancel/import handlers. Env var → 2; blank uses default; must be integer >=1. server._limits.ServerLimiters.from_env
NOTEBOOKLM_SERVER_CHAT_CONCURRENCY Max concurrent REST blocking chat ask handlers. Env var → 4; blank uses default; must be integer >=1. server._limits.ServerLimiters.from_env
NOTEBOOKLM_VCR_RECORD_ERRORS Synthetic-error injection mode for VCR test cassettes. Lowercase-normalized; valid values are 429 (rate limit), 5xx (server error), or expired_csrf (CSRF token expiration). Used to record synthetic error cassettes under VCR. Process env on each request, evaluated by ErrorInjectionMiddleware to intercept and synthesize failures. _error_injection._get_error_injection_mode

Boolean handling. NOTEBOOKLM_DEBUG_RPC treats 1 / true / yes
(case-insensitive) as truthy; everything else is falsy.
NOTEBOOKLM_STRICT_DECODE is ignored as of v0.7.0 (strict decoding is the
only mode); no value changes decoder behavior.
NOTEBOOKLM_QUIET_DEPRECATIONS treats 1 / true / yes / on
(case-insensitive) as truthy; any other value (including unset) leaves
deprecation warnings enabled. It silences the project's public-API
DeprecationWarnings — the get()-returns-None warning and deprecated
keyword aliases (e.g. ResearchAPI.wait_for_completion's legacy interval=) —
without changing their behavior.
NOTEBOOKLM_NOTEBOOK is treated as unset when empty or whitespace-only so a
bare export NOTEBOOKLM_NOTEBOOK= does not block notebooklm use /
-n/--notebook from resolving.

The --quiet global flag. notebooklm --quiet <subcommand> suppresses
status prose and raises the notebooklm package logger floor to ERROR for
the duration of one invocation, so cron and CI logs stay clean while real
failures still surface. Structured --json payloads are still emitted. It is
mutually exclusive with -v/-vv — combining the two raises a UsageError
(exit 2) since the resolved log levels conflict (ERROR vs INFO/DEBUG).
For shell-wide / always-on log suppression, use NOTEBOOKLM_LOG_LEVEL.

NOTEBOOKLM_HOME

Relocates all configuration files to a custom directory:

bash
export NOTEBOOKLM_HOME=/custom/path

# All files now go here:
# /custom/path/profiles//storage_state.json
# /custom/path/profiles//context.json
# /custom/path/profiles//browser_profile/

Use cases:

  • Per-project isolation
  • Custom storage locations

NOTEBOOKLM_PROFILE

Selects the active profile without changing the persisted default:

bash
export NOTEBOOKLM_PROFILE=work
notebooklm list   # Uses ~/.notebooklm/profiles/work/

Equivalent to passing -p work on every command. The CLI flag takes precedence over the env var.

The persisted default is read from the default_profile key of
~/.notebooklm/config.json (set via notebooklm profile switch <name>). When
neither a -p/--profile flag nor NOTEBOOKLM_PROFILE is set, paths.resolve_profile
falls back to this value (and finally to "default" if config.json doesn't
exist or has no default_profile key).

NOTEBOOKLM_AUTH_JSON

Provides authentication inline without writing files. Ideal for CI/CD:

bash
export NOTEBOOKLM_AUTH_JSON='{"cookies":[...]}'
notebooklm list  # Works without any file on disk

Precedence:

  1. --storage CLI flag (highest)
  2. NOTEBOOKLM_AUTH_JSON environment variable
  3. Profile-aware path: $NOTEBOOKLM_HOME/profiles//storage_state.json
  4. ~/.notebooklm/profiles/default/storage_state.json (default)
  5. ~/.notebooklm/storage_state.json (legacy fallback)

Note: Cannot run notebooklm login when NOTEBOOKLM_AUTH_JSON is set.

`NOTEBOOKLM_REFRESH_CMD`

Optional. If set, this command is invoked when an auth refresh is required —
replacing the default browser-cookie path. The contract is exit-code based:

  1. The command must exit 0.
  2. On exit, it must have written a refreshed storage_state.json at the
    path in NOTEBOOKLM_REFRESH_STORAGE_PATH. The parent sets this child
    env var to the explicit storage path or the resolved profile-aware
    storage path before spawning the command.

The parent then reloads cookies from disk and retries the token fetch. Stdout
and stderr are not parsed — they are only captured for inclusion in the
error message when the command exits non-zero. Read by the
NOTEBOOKLM_REFRESH_CMD_ENV constant in notebooklm.auth.
The child process also receives NOTEBOOKLM_REFRESH_PROFILE with the resolved
profile name.

See also NOTEBOOKLM_REFRESH_CMD_USE_SHELL to opt back into shell=True
parsing.

By default the command fires only at active cold start — client construction
/ AuthTokens.from_storage, which run the recovery ladder when the stored
cookies are dead. Passive readiness probes do NOT invoke it: auth check --test --passive (and other passive token fetches) use the strict, no-recovery
loader and never enter the refresh path, so a passive check reports expiry
without spawning the command. Set NOTEBOOKLM_REFRESH_CMD_MIDSESSION=1 to also
fire it mid-session (the L2.5 rung of the unified recovery ladder), e.g.
inside a long-lived server that has been running past cookie expiry. This is opt-in for
one release
and flips to default-on afterward; enable it only once you have
confirmed your command is safe to invoke while the client is live. When you need
to see what the command printed, set NOTEBOOKLM_REFRESH_CMD_LOG_OUTPUT=1 to
route its captured stdout/stderr into the redacting DEBUG logger — the
default DEBUG line records only the command basename, exit code, and byte
counts. See ADR-0030 for the ladder design.

NOTEBOOKLM_HL

Sets the default interface/output language used by the client. The value is
passed as the hl query parameter on every batchexecute RPC call and is used by
the generate audio|video|slide-deck|infographic|data-table|mind-map|report
commands. In the Python API, omitted language on ArtifactsAPI.generate_*
keeps the historical "en" artifact default; pass language=None to opt in to
this NOTEBOOKLM_HL resolver.

bash
export NOTEBOOKLM_HL=ja
notebooklm generate audio "deep dive"   # Japanese audio overview

Surrounding whitespace is stripped; an empty or whitespace-only value falls
back to en. For the generate commands, the resolution order is:

  1. --language CLI flag
  2. NOTEBOOKLM_HL environment variable
  3. language value from the global ~/.notebooklm/config.json (set via
    notebooklm language set <code>). The language is stored once per
    NOTEBOOKLM_HOME, not per profile — switching notebooklm -p work
    does not switch the configured language. See
    src/notebooklm/cli/language_cmd.py for the resolver and
    src/notebooklm/paths.py for the storage location.
  4. en (built-in default)

NOTEBOOKLM_QUIET_DEPRECATIONS

Suppresses the project's public-API DeprecationWarnings while you migrate. Set
it to a truthy value (1, true, yes, or on, case-insensitive) to silence
them; any other value (including unset) leaves them enabled.

It gates the one-off deprecation warnings routed through
src/notebooklm/_deprecation.py::warn_deprecated — e.g. awaiting
NotebookLMClient.from_storage(...) instead of using the async with form. (The
v0.7.0 error-contract runways it also gated — the get()-returns-None warning,
the wait_for_completion(interval=...) alias, and the dict-subscript bridge — all
completed their removal in v0.8.0, so those warnings no longer exist; see
deprecations.md.)

The helper that reads this variable is
notebooklm._deprecation._deprecations_quiet (public alias
deprecations_quiet).

bash
# Silence the project's public-API deprecation warnings while migrating
export NOTEBOOKLM_QUIET_DEPRECATIONS=1

Note: this variable does not affect source add --mime-type /
client.sources.add_file(mime_type=...) — mime_type is a supported
parameter and emits no warning.

NOTEBOOKLM_FUTURE_ERRORS (removed in v0.8.0)

Removed. This was the v0.7.0 forward-compat preview gate for the v0.8.0 error
contract (ADR-0019 / umbrella
#1346): setting it made
the v0.7.0 warn-runways adopt their v0.8.0 raise-target early so you could test
forward-compatibility before the breaking flips shipped. v0.8.0 makes every one
of those flips the default — get() raising *NotFoundError on a miss, the
attribute-only typed returns, the removed interval= alias, the bool→None
returns, the refusal-raises, and the mutate-existing fail-loud — so the flag and
its resolver were deleted. Setting NOTEBOOKLM_FUTURE_ERRORS now has no
effect.
Remove it from your environment / CI config. See
deprecations.md for the full Removed-in-v0.8.0 table.

Timeouts

Most batchexecute RPCs issued by the client (whether through NotebookLMClient
or any of the CLI commands) use a 30-second HTTP request timeout by default,
with a tighter 10-second connection-establishment timeout. The shorter
connect timeout helps surface network-level issues quickly while the read
timeout accommodates slow server responses. The timeout is exposed as a
constructor argument on NotebookLMClient (timeout=) for callers that need to
tune it per-workload — see the DEFAULT_TIMEOUT / DEFAULT_CONNECT_TIMEOUT
constants in src/notebooklm/_runtime/config.py.

The chat streaming endpoint (ChatAPI.ask) also exposes a separate per-read
silence window (chat_timeout=). Left unset it is max(180 s, timeout=) —
180 seconds because shared notebooks can be slow to send the first streamed chat
byte, floored at timeout= so a larger configured budget still reaches chat (see
below); fast metadata RPCs stay
on the normal 30-second timeout. A chat read timeout means the server
sent no stream bytes for that window, either before the first byte or between
chunks; it does not mean total generation time exceeded 30 seconds. Pass
chat_timeout=None to inherit the normal client timeout for chat. The CLI
ask --request-timeout N flag overrides both values for that invocation.

IMPORT_RESEARCH (research.import_sources) has a third window: the server
ingests every requested entry before answering one RPC, so the window scales
with batch size — 60 s + 3 s per requested source, capped at 240 s — and is
tunable outright via import_research_timeout=.

How the per-RPC windows compose with `timeout=`

timeout= is the base read budget for every RPC. The two built-in per-RPC
windows above are defaults, not caps: they only ever lengthen that base,
never shorten it. So NotebookLMClient(auth, timeout=600) really does buy 600
seconds everywhere, including chat and IMPORT_RESEARCH (before this rule landed
they silently clamped such a client back to 180 s / 240 s — issue #2205).

Construction chat window IMPORT_RESEARCH window (1 source)
NotebookLMClient(auth) 180 s 63 s
NotebookLMClient(auth, timeout=600) 600 s 600 s
NotebookLMClient(auth, timeout=600, chat_timeout=10) 10 s 600 s
NotebookLMClient(auth, import_research_timeout=900) 180 s 900 s

An explicitly passed chat_timeout= / import_research_timeout= is the
caller's final word and is used as given — including a value below timeout=,
so deliberately fast failure stays expressible. Only the untouched defaults
compose. Both kwargs read identically:

value meaning
unset the built-in window, floored at timeout=
a number exactly that window, replacing the built-in and the floor
None inherit timeout= verbatim, with no per-RPC window at all

A non-positive or non-finite value for either raises ValueError at
construction rather than silently producing a window that times out instantly.

Independently, an IMPORT_RESEARCH attempt made by
import_sources_with_verification is clamped to whatever is left of that call's
own max_elapsed retry budget, so a late retry cannot be granted a window
larger than the budget it has left. Note what that does and does not promise:
every timeout here is an httpx slot, and the read slot is an inactivity limit
between socket reads — so max_elapsed bounds when a new attempt may start,
not the wall-clock duration of one already in flight. Enforcing the latter would
mean cancelling an in-flight non-idempotent POST, which risks duplicated sources
the client can no longer see. When less than 10 seconds of that budget remains — too little for an
attempt to outlast connection establishment — the loop stops instead of sending
one: IMPORT_RESEARCH is non-idempotent, so an attempt whose result the client
cannot observe can still commit sources server-side and duplicate them. The
first attempt is exempt, so max_elapsed=0 still means "try once".

Decoder strictness

NotebookLM's batchexecute responses are obfuscated, undocumented, and reshaped
by Google without notice. The decoder uses a shared safe_index helper to walk
nested response payloads. When it can't descend (an index is out of range, or
the value at a step isn't indexable), it raises
UnknownRPCMethodError (a subclass of DecodingError / RPCError) with
structured method_id, path, source, and data_at_failure attributes.

Strict decoding is the only mode. The legacy NOTEBOOKLM_STRICT_DECODE=0
warn-and-return-None opt-out (which emitted a DeprecationWarning through
v0.5.0/v0.6.0) was retired in v0.7.0; the env var is now ignored. The
strict contract surfaces real schema drift (Google rotating a response shape)
as a typed exception instead of a silent None return, so callers that
previously treated None as a sentinel must handle UnknownRPCMethodError.

The same UnknownRPCMethodError is also raised by decode_response() when the
batchexecute response contains RPC IDs but not the one the call requested
(typically a sign that Google rotated the method ID).

Background and rationale for the flip: see
docs/adr/0011-schema-validation-policy.md.

CLI Options

Global Options

Option Description Default
--storage PATH Path to storage_state.json $NOTEBOOKLM_HOME/profiles//storage_state.json
-p, --profile NAME Use a named profile Active profile or default
-v, --verbose Enable verbose output (-v for INFO, -vv for DEBUG) -
--quiet Suppress status output and INFO/WARN logs (only errors survive). Mutually exclusive with -v. -
--version Show version -
--help Show help -

Viewing Configuration

See where your configuration files are located:

bash
notebooklm status --paths

Output:

text
Configuration Paths
┏━━━━━━━━━━━━━━━━━━┳━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━┳━━━━━━━━━━━┓
┃ File             ┃ Path                                     ┃ Source    ┃
┡━━━━━━━━━━━━━━━━━━╇━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━╇━━━━━━━━━━━┩
│ Profile          │ default                                  │ default   │
│ Home Directory   │ /home/user/.notebooklm                   │ default   │
│ Profile Directory│ /home/user/.notebooklm/profiles/default  │           │
│ Storage State    │ .../profiles/default/storage_state.json  │           │
│ Context          │ .../profiles/default/context.json        │           │
│ Browser Profile  │ .../profiles/default/browser_profile     │           │
└──────────────────┴──────────────────────────────────────────┴───────────┘

Session Management

Session Lifetime

Authentication sessions are tied to Google's cookie expiration:

  • Sessions typically last several days to weeks
  • Google may invalidate sessions for security reasons
  • Rate limiting or suspicious activity can trigger earlier expiration

Refreshing Sessions

Automatic Refresh: CSRF tokens and session IDs are automatically refreshed when authentication errors are detected. This handles most "session expired" errors transparently.

Manual Re-authentication: If your session cookies have fully expired (automatic refresh won't help), re-authenticate:

bash
notebooklm login

Multiple Accounts

Profiles (recommended): Use named profiles to manage multiple Google accounts under a single home directory:

bash
# Create and authenticate profiles
notebooklm profile create work
notebooklm -p work login
notebooklm -p work list

notebooklm profile create personal
notebooklm -p personal login
notebooklm -p personal list

# Switch the active profile
notebooklm profile switch work
notebooklm list   # Uses work profile

# List all profiles
notebooklm profile list

# Use env var for session-wide override
export NOTEBOOKLM_PROFILE=personal
notebooklm list   # Uses personal profile

Each profile stores its own storage_state.json, context.json, and browser_profile/ under ~/.notebooklm/profiles/<name>/.

Alternative: NOTEBOOKLM_HOME still works for full directory-level isolation:

bash
export NOTEBOOKLM_HOME=~/.notebooklm-work
notebooklm login

One-off override with --storage:

bash
notebooklm --storage /path/to/storage_state.json list

When --storage is set, two different context files are used —
they are NOT the same file:

  • Notebook / conversation context lives at a suffixed file
    .context.json (storage_path.with_suffix(storage_path.suffix + ".context.json"),
    see paths.get_context_path). Two --storage invocations against different
    files cannot see each other's selected notebook, and neither pollutes the
    default profile context.
  • Account-routing metadata (the notebooklm.account object — authuser
    index and optional email) lives in-band inside the selected
    storage_state.json. This keeps copied files and NOTEBOOKLM_AUTH_JSON
    secrets bound to the same Google account route as the original profile.
  • Persistent browser state lives beside the selected storage file. A path
    named storage_state.json uses browser_profile/; other filenames append
    .browser_profile to the full filename when it fits, or use a stable
    canonical-path hash when it does not. Login, headless re-auth, doctor's L3
    readiness row, status --paths, and logout all resolve the same
    storage-specific directory. Recursive deletion by login --fresh or logout
    requires either the ownership marker or a canonical managed profile layout.

Run notebooklm --storage status --paths to see exactly which
storage, context, and browser-profile paths are in use.

CI/CD Configuration

Use NOTEBOOKLM_AUTH_JSON for secure, file-free authentication:

yaml
jobs:
  notebook-task:
    runs-on: ubuntu-latest
    steps:
      - uses: actions/checkout@v4

      - name: Setup Python
        uses: actions/setup-python@v5
        with:
          python-version: '3.12'

      - name: Install notebooklm-py
        run: pip install notebooklm-py

      # Pre-flight: fail fast and loud on missing/expired auth.
      # `auth check --json` returns exit 0 even when status is "error"; --test makes the network
      # call needed to detect expired cookies, and the `jq -e` flag converts a non-"ok" status
      # into a non-zero exit code so the runner step actually fails.
      - name: Verify auth (fail-fast on expired cookies)
        env:
          NOTEBOOKLM_AUTH_JSON: ${{ secrets.NOTEBOOKLM_AUTH_JSON }}
        run: notebooklm auth check --test --json | jq -e '.status == "ok"'

      - name: List notebooks
        env:
          NOTEBOOKLM_AUTH_JSON: ${{ secrets.NOTEBOOKLM_AUTH_JSON }}
        run: notebooklm list

Benefits:

  • No file writes needed
  • Secret stays in memory only
  • Clean, simple workflow

Obtaining the Secret Value

  1. Run notebooklm login locally
  2. If the file was created by an older Playwright login and lacks notebooklm.account, run notebooklm auth refresh to repair single-account states or re-login with notebooklm login --browser-cookies --account EMAIL for multi-account states
  3. Copy the contents of ~/.notebooklm/profiles/default/storage_state.json (the canonical write location; the legacy ~/.notebooklm/storage_state.json is only read as a fallback)
  4. Add as a GitHub repository secret named NOTEBOOKLM_AUTH_JSON (see installation.md#d-headless-server-or-ci for trailing-newline + ephemeral-runner refresh notes)

Alternative: File-Based Auth

If you prefer file-based authentication:

yaml
- name: Setup NotebookLM auth
  run: |
    mkdir -p ~/.notebooklm/profiles/default
    echo "${{ secrets.NOTEBOOKLM_AUTH_JSON }}" > ~/.notebooklm/profiles/default/storage_state.json
    chmod 600 ~/.notebooklm/profiles/default/storage_state.json

- name: List notebooks
  run: notebooklm list

For profile-specific CI auth:

yaml
- name: Setup work profile auth
  run: |
    mkdir -p ~/.notebooklm/profiles/work
    echo "${{ secrets.WORK_AUTH_JSON }}" > ~/.notebooklm/profiles/work/storage_state.json
    chmod 600 ~/.notebooklm/profiles/work/storage_state.json

- name: List notebooks (work)
  run: notebooklm -p work list

Session Expiration

CSRF tokens are automatically refreshed during API calls. However, the underlying session cookies still expire. For long-running CI pipelines:

  • Update the NOTEBOOKLM_AUTH_JSON secret every 1-2 weeks
  • Monitor for persistent auth failures (these indicate cookie expiration)

Debugging

Enable Verbose Output

Some commands support verbose output via Rich console:

bash
# Most errors are printed to stderr with details
notebooklm list 2>&1 | cat

Enable RPC Debug Logging

bash
NOTEBOOKLM_DEBUG_RPC=1 notebooklm list

Logger namespace compatibility

Runtime transport and middleware logs still use the historical
notebooklm._core logger key via CORE_LOGGER_NAME. That name is a
compatibility logging contract for existing filters and tests; it does not
mean the deleted _core.py module or a concrete Session owner still exists.

Check Authentication

Verify your session is working:

bash
# Should list notebooks or show empty list
notebooklm list

# If you see "Unauthorized" or redirect errors, re-login
notebooklm login

Check Configuration Paths

bash
# See where files are being read from
notebooklm status --paths

Network Issues

The CLI uses httpx for HTTP requests. Common issues:

  • Timeout: Google's API can be slow; large operations may time out
  • SSL errors: Ensure your system certificates are up to date
  • Proxy: Set standard environment variables (HTTP_PROXY, HTTPS_PROXY) if needed

Platform Notes

macOS

Works out of the box. Chromium is downloaded automatically by Playwright.

Linux

For Playwright system dependencies and the Chromium install on Debian/Ubuntu, see docs/installation.md#platform-notes (and troubleshooting.md#linux if you hit TypeError: onExit is not a function).

Windows

Works with PowerShell or CMD. Use backslashes for paths:

powershell
notebooklm --storage C:\Users\Name\.notebooklm\storage_state.json list

Or set environment variable:

powershell
$env:NOTEBOOKLM_HOME = "C:\Users\Name\custom-notebooklm"
notebooklm list

WSL

Browser login opens in the Windows host browser. The storage file is saved in the WSL filesystem.

Headless Servers & Containers

Playwright is only required for the notebooklm login command. All other operations use standard HTTP requests via httpx.

For the install + auth-bootstrap recipe (run notebooklm login on a workstation, copy storage_state.json to the server, set NOTEBOOKLM_AUTH_JSON), see the canonical Persona D guide: docs/installation.md#d-headless-server-or-ci.