Status: Active
Last Updated: 2026-03-02
Source of Truth: src/notebooklm/rpc/types.py
Purpose: Complete reference for RPC methods, UI selectors, and payload structures
Note: Payload structures extracted from actual implementation in
src/notebooklm/. Each payload includes a reference to its source file.
| RPC ID | Method | Purpose | Implementation |
|---|---|---|---|
wXbhsf |
LIST_NOTEBOOKS | List all notebooks | _notebooks.py |
CCqFvf |
CREATE_NOTEBOOK | Create new notebook | _notebooks.py |
rLM1Ne |
GET_NOTEBOOK | Get notebook details + sources | _notebooks.py |
s0tc2d |
RENAME_NOTEBOOK | Rename, chat config, share access | _notebooks.py, _chat.py |
WWINqb |
DELETE_NOTEBOOK | Delete a notebook | _notebooks.py |
izAoDd |
ADD_SOURCE | Add URL/text/YouTube source | _sources.py |
o4cbdc |
ADD_SOURCE_FILE | Register uploaded file | _sources.py |
tGMBJ |
DELETE_SOURCE | Delete a source | _sources.py |
b7Wfje |
UPDATE_SOURCE | Rename source | _sources.py |
tr032e |
GET_SOURCE_GUIDE | Get source summary | _sources.py |
R7cb6c |
CREATE_ARTIFACT | Unified artifact generation | _artifacts.py |
gArtLc |
LIST_ARTIFACTS | List artifacts in a notebook | _artifacts.py |
V5N4be |
DELETE_ARTIFACT | Delete artifact | _artifacts.py |
hPTbtc |
GET_CONVERSATION_ID | Get most recent conversation ID | _chat.py |
khqZz |
GET_CONVERSATION_TURNS | Get Q&A turns for a conversation | _chat.py |
CYK0Xb |
CREATE_NOTE | Create a note (placeholder) | _notes.py |
cYAfTb |
UPDATE_NOTE | Update note content/title | _notes.py |
AH0mwd |
DELETE_NOTE | Delete a note | _notes.py |
cFji9 |
GET_NOTES_AND_MIND_MAPS | List notes and mind maps | _notes.py |
yyryJe |
GENERATE_MIND_MAP | Mind map generation | _artifacts.py |
VfAZjd |
SUMMARIZE | Get notebook summary | _notebooks.py |
FLmJqe |
REFRESH_SOURCE | Refresh URL/Drive source | _sources.py |
yR9Yof |
CHECK_SOURCE_FRESHNESS | Check if source needs refresh | _sources.py |
Ljjv0c |
START_FAST_RESEARCH | Start fast research | _research.py |
QA9ei |
START_DEEP_RESEARCH | Start deep research | _research.py |
e3bVqc |
POLL_RESEARCH | Poll research status | _research.py |
LBwxtb |
IMPORT_RESEARCH | Import research results | _research.py |
rc3d8d |
RENAME_ARTIFACT | Rename artifact | _artifacts.py |
Krh3pd |
EXPORT_ARTIFACT | Export to Docs/Sheets | _artifacts.py |
RGP97b |
SHARE_ARTIFACT | Toggle notebook sharing | _notebooks.py |
QDyure |
SHARE_NOTEBOOK | Set notebook visibility (restricted/public) | _notebooks.py |
JFMDGd |
GET_SHARE_STATUS | Get notebook share settings | _sharing.py |
ciyUvf |
GET_SUGGESTED_REPORTS | Get AI-suggested report formats | _artifacts.py |
v9rmvd |
GET_INTERACTIVE_HTML | Fetch quiz/flashcard HTML content | _artifacts.py |
fejl7e |
REMOVE_RECENTLY_VIEWED | Remove notebook from recent list | _notebooks.py |
ZwVcOc |
GET_USER_SETTINGS | Get user settings including output language | _settings.py |
hT54vc |
SET_USER_SETTINGS | Set user settings (e.g., output language) | _settings.py |
| Code | Type | Used By |
|---|---|---|
| 1 | Audio | Audio Overview |
| 2 | Report | Briefing Doc, Study Guide, Blog Post |
| 3 | Video | Video Overview |
| 4 | Quiz/Flashcards | Quiz (variant=2), Flashcards (variant=1) |
| 5 | Mind Map | Mind Map |
| 7 | Infographic | Infographic |
| 8 | Slide Deck | Slide Deck |
| 9 | Data Table | Data Table |
Selectors are provided as Python lists of fallback options. Try each in order:
async def try_selectors(page, selectors: list[str], action="click", timeout=5000):
"""Try multiple selectors until one works."""
for selector in selectors:
try:
element = page.locator(selector)
if action == "click":
await element.click(timeout=timeout)
elif action == "fill":
return element
return True
except Exception:
continue
raise Exception(f"None of the selectors worked: {selectors}")
# Example usage
await try_selectors(page, HOME_SELECTORS["create_notebook"])HOME_SELECTORS = {
"create_notebook": [
"button:has-text('Create new')",
"mat-card[role='button']:has-text('Create new notebook')",
],
"notebook_card": [
"mat-card:has(button:has-text('more_vert'))",
"mat-card[role='button']:has(h3)",
],
"notebook_menu": [
"button[aria-label*='More options']",
],
}Source: _notebooks.py::list()
params = [
None, # 0
1, # 1: Fixed value
None, # 2
[2], # 3: Fixed flag
]Source: _notebooks.py::create()
params = [
title, # 0: Notebook title
None, # 1
None, # 2
[2], # 3: Fixed flag
[1], # 4: Fixed flag
]Source: _notebooks.py::delete()
params = [
[notebook_id], # 0: Single-nested notebook ID
[2], # 1: Fixed flag
]Source: _notebooks.py::get()
params = [
notebook_id, # 0
None, # 1
[2], # 2: Fixed flag
None, # 3
0, # 4: Fixed value
]Source: _notebooks.py::remove_from_recent()
Remove a notebook from the recently viewed list (doesn't delete the notebook).
params = [notebook_id] # Just the notebook ID
# No source_path needed
await rpc_call(
RPCMethod.REMOVE_RECENTLY_VIEWED,
params,
allow_null=True,
)
# Response: None (no return value)SOURCES_SELECTORS = {
"add_sources": [
"button:has-text('+ Add sources')",
"button:has-text('Add sources')",
],
"source_card": ".single-source-container",
"source_menu": "button[aria-label*='More options']",
"remove_source": "button:has-text('Remove source')",
"rename_source": "button:has-text('Rename source')",
}
ADD_SOURCE_MODAL = {
"modal": "[role='dialog']",
"website_tab": "button:has-text('Website')",
"url_input": "textarea[placeholder*='links']",
"copied_text_tab": "button:has-text('Copied text')",
"submit_button": "button:has-text('Insert')",
}Source: _sources.py::_add_url_source()
# URL goes at position [2] in an 8-element array
params = [
[[None, None, [url], None, None, None, None, None]], # 0: Source config
notebook_id, # 1: Notebook ID
[2], # 2: Source type flag
None, # 3
None, # 4
]Source: _sources.py::add_text()
# [title, content] at position [1] in an 8-element array
params = [
[[None, [title, content], None, None, None, None, None, None]], # 0
notebook_id, # 1
[2], # 2
None, # 3
None, # 4
]Source: _sources.py::_add_youtube_source()
# YouTube URL at position [7] in an 11-element array (different from regular URL!)
params = [
[[None, None, None, None, None, None, None, [url], None, None, 1]], # 0
notebook_id, # 1
[2], # 2
[1, None, None, None, None, None, None, None, None, None, [1]], # 3: Extra config
]Source: _sources.py::add_drive()
# Drive source structure - single-wrapped (not double!)
source_data = [
[file_id, mime_type, 1, title], # 0: File info
None, None, None, None, None, # 1-5: Padding
None, None, None, None, # 6-9: Padding
1, # 10: Trailing flag
]
params = [
[source_data], # 0: Single-wrapped (NOT [[source_data]])
notebook_id, # 1: Notebook ID
[2], # 2: Source type flag
[1, None, None, None, None, None, None, None, None, None, [1]], # 3: Config
]Note: The nesting level is critical. Web UI sends [source_data] (single wrap), not [[source_data]] (double wrap).
Source: _sources.py::delete()
IMPORTANT: notebook_id is passed via source_path, NOT in params!
params = [[[source_id]]] # Triple-nested!
# Called with:
await rpc_call(
RPCMethod.DELETE_SOURCE,
params,
source_path=f"/notebook/{notebook_id}", # <-- notebook_id here
)Source: _sources.py::rename()
# Different structure: None at [0], source_id at [1], title triple-nested at [2]
params = [
None, # 0
[source_id], # 1: Single-nested source ID
[[[new_title]]], # 2: Triple-nested title
]Source: _sources.py::get_guide()
# Quadruple-nested source ID!
params = [[[[source_id]]]]CHAT_SELECTORS = {
"message_input": [
"textarea[placeholder='Start typing...']",
"textarea[aria-label='Query box']",
],
"send_button": "button[aria-label='Submit']",
"configure_button": "button[aria-label='Configure notebook']",
"chat_history": "[role='log']",
"message_bubble": [
".to-user-container", # AI messages
".from-user-container", # User messages
],
}
CHAT_CONFIG = {
"modal": "configure-notebook-settings",
"goal_default": "button[aria-label='Default button']",
"goal_learning_guide": "button[aria-label*='Learning Guide']",
"goal_custom": "button[aria-label='Custom button']",
"length_shorter": "button[aria-label*='Shorter']",
"length_longer": "button[aria-label*='Longer']",
"save_button": "button:has-text('Save')",
}Chat queries use a separate streaming endpoint, not batchexecute:
POST /_/LabsTailwindUi/data/google.internal.labs.tailwind.orchestration.v1.LabsTailwindOrchestrationService/GenerateFreeFormStreamed
Source: _notebooks.py::rename()
# Just rename, no chat config
params = [
notebook_id, # 0
[[None, None, None, [None, new_title]]], # 1: Nested title at [[[3][1]]]
]Source: _chat.py::configure()
# Chat goal codes (ChatGoal enum)
CHAT_GOAL_DEFAULT = 1
CHAT_GOAL_CUSTOM = 2
CHAT_GOAL_LEARNING_GUIDE = 3
# Response length codes (ChatResponseLength enum)
CHAT_LENGTH_DEFAULT = 1
CHAT_LENGTH_LONGER = 4
CHAT_LENGTH_SHORTER = 5
# Build goal array
goal_array = [goal_value] # e.g., [1] for DEFAULT
# For CUSTOM: goal_array = [2, custom_prompt]
chat_settings = [goal_array, [response_length_value]]
params = [
notebook_id, # 0
[[None, None, None, None, None, None, None, chat_settings]], # 1: Settings at [[[7]]]
]Source: _chat.py::get_conversation_id()
Returns the most recent conversation ID for a notebook. The server always returns
exactly one ID regardless of the limit param. Use GET_CONVERSATION_TURNS to
fetch the actual messages for the returned conversation.
params = [
[], # 0: Empty sources array
None, # 1
notebook_id, # 2
1, # 3: Limit (server ignores this; always returns one ID)
]Response: [[[conv_id]]] — single entry list containing the conversation ID.
Source: _chat.py::get_conversation_turns()
Returns the Q&A turns for a specific conversation. Turns are ordered newest-first.
params = [
[], # 0: Empty
None, # 1
None, # 2
conversation_id, # 3
limit, # 4: Max turns to return (e.g., 2 for latest Q&A pair)
]Response turn structure:
turn[2] == 1: User question — text is atturn[3]turn[2] == 2: AI answer — text is atturn[4][0][0]
STUDIO_SELECTORS = {
"artifact_button": ".create-artifact-button-container",
"customize_icon": ".option-icon", # Click THIS for customization!
"add_note": "button:has-text('Add note')",
"artifact_list": ".artifact-library-container",
"artifact_row": ".artifact-item-button",
"artifact_menu": ".artifact-more-button",
}
ARTIFACT_MENU = {
"rename": "button:has-text('Rename')",
"download": "button:has-text('Download')",
"delete": "button:has-text('Delete')",
}# ✅ Click edit icon for customization dialog
await page.locator(".create-artifact-button-container:has-text('Audio') .option-icon").click()
# ❌ Clicking full button starts generation with defaults (skips customization!)
await page.locator(".create-artifact-button-container:has-text('Audio')").click()All artifact types use R7cb6c with different content type codes and nested configs.
Source: _artifacts.py
Source: _artifacts.py::generate_audio()
source_ids_triple = [[[sid]] for sid in source_ids] # [[[s1]], [[s2]], ...]
source_ids_double = [[sid] for sid in source_ids] # [[s1], [s2], ...]
params = [
[2], # 0: Fixed
notebook_id, # 1
[
None, # [0]
None, # [1]
1, # [2]: ArtifactTypeCode.AUDIO
source_ids_triple, # [3]
None, # [4]
None, # [5]
[
None,
[
instructions, # Focus/instructions text
length_code, # 1=SHORT, 2=DEFAULT, 3=LONG
None,
source_ids_double,
language, # "en"
None,
format_code, # 1=DEEP_DIVE, 2=BRIEF, 3=CRITIQUE, 4=DEBATE
],
], # [6]
], # 2: Source config
]Source: _artifacts.py::generate_video()
params = [
[2],
notebook_id,
[
None, # [0]
None, # [1]
3, # [2]: ArtifactTypeCode.VIDEO
source_ids_triple, # [3]
None, # [4]
None, # [5]
None, # [6]
None, # [7]
[
None,
None,
[
source_ids_double,
language, # "en"
instructions,
None,
format_code, # 1=EXPLAINER, 2=BRIEF
style_code, # 1=AUTO, 2=CUSTOM, 3=CLASSIC, 4=WHITEBOARD, etc.
],
], # [8]
],
]Source: _artifacts.py::generate_report()
params = [
[2],
notebook_id,
[
None, # [0]
None, # [1]
2, # [2]: ArtifactTypeCode.REPORT
source_ids_triple, # [3]
None, # [4]
None, # [5]
None, # [6]
[
None,
[
title, # "Briefing Doc" / "Study Guide" / etc.
description, # Short description
None,
source_ids_double,
language, # "en"
prompt, # Detailed generation prompt
None,
True,
],
], # [7]
],
]Source: _artifacts.py::generate_quiz()
params = [
[2],
notebook_id,
[
None, # [0]
None, # [1]
4, # [2]: ArtifactTypeCode.QUIZ_FLASHCARD
source_ids_triple, # [3]
None, # [4]
None, # [5]
None, # [6]
None, # [7]
None, # [8]
[
None,
[
2, # Variant: 2=quiz, 1=flashcards
None,
instructions,
None,
None,
None,
None,
[quantity_code, difficulty_code], # quantity: 1=FEWER, 2=STANDARD
], # difficulty: 1=EASY, 2=MEDIUM, 3=HARD
], # [9]
],
]Source: _artifacts.py::generate_flashcards()
params = [
[2],
notebook_id,
[
None, # [0]
None, # [1]
4, # [2]: ArtifactTypeCode.QUIZ_FLASHCARD
source_ids_triple, # [3]
None, # [4]
None, # [5]
None, # [6]
None, # [7]
None, # [8]
[
None,
[
1, # Variant: 1=flashcards (vs 2=quiz)
None,
instructions,
None,
None,
None,
[difficulty_code, quantity_code], # Note: reversed order from quiz!
],
], # [9]
],
]Source: _artifacts.py::generate_infographic()
# Orientation: 1=LANDSCAPE, 2=PORTRAIT, 3=SQUARE
# Detail: 1=CONCISE, 2=STANDARD, 3=DETAILED
params = [
[2],
notebook_id,
[
None, # [0]
None, # [1]
7, # [2]: ArtifactTypeCode.INFOGRAPHIC
source_ids_triple, # [3]
None, None, None, None, None, None, None, None, None, None, # [4-13]
[
None,
[instructions, language, None, orientation_code, detail_code],
], # [14]
],
]Source: _artifacts.py::generate_slide_deck()
# Format: 1=DETAILED_DECK, 2=PRESENTER_SLIDES
# Length: 1=DEFAULT, 2=SHORT
params = [
[2],
notebook_id,
[
None, # [0]
None, # [1]
8, # [2]: ArtifactTypeCode.SLIDE_DECK
source_ids_triple, # [3]
None, None, None, None, None, None, None, None, None, None, None, None, # [4-15]
[[instructions, language, format_code, length_code]], # [16]
],
]Source: _artifacts.py::generate_data_table()
params = [
[2],
notebook_id,
[
None, # [0]
None, # [1]
9, # [2]: ArtifactTypeCode.DATA_TABLE
source_ids_triple, # [3]
None, None, None, None, None, None, None, None, None, None, None, None, None, None, # [4-17]
[None, [instructions, language]], # [18]
],
]Source: _artifacts.py::generate_mind_map()
Note: Mind map uses a different RPC method than other artifacts.
# RPC: GENERATE_MIND_MAP (yyryJe), NOT CREATE_ARTIFACT
params = [
source_ids_nested, # 0: [[[sid]] for sid in source_ids]
None, # 1
None, # 2
None, # 3
None, # 4
["interactive_mindmap", [["[CONTEXT]", ""]], ""], # 5: Mind map command
None, # 6
[2, None, [1]], # 7: Fixed config
]Source: _artifacts.py::list(), _artifacts.py::poll_status()
params = [
[2],
notebook_id,
'NOT artifact.status = "ARTIFACT_STATUS_SUGGESTED"', # Filter string
]
# Response contains artifacts array with status:
# status = 1 → Processing
# status = 2 → Pending
# status = 3 → CompletedPython API Note: artifacts.list() also fetches mind maps from GET_NOTES_AND_MIND_MAPS and includes them as Artifact objects (type=5). This provides a unified list of all AI-generated content. Mind maps with status=2 (deleted) are filtered out.
Source: _notes.py::create()
Note: Google ignores title/content in CREATE_NOTE. Must call UPDATE_NOTE after to set actual content.
# Creates note with fixed placeholder values
params = [
notebook_id, # 0
"", # 1: Empty string (ignored)
[1], # 2: Fixed flag
None, # 3
"New Note", # 4: Placeholder title (ignored)
]
# Then call UPDATE_NOTE to set real title/contentSource: _notes.py::update()
params = [
notebook_id, # 0
note_id, # 1
[[[content, title, [], 0]]], # 2: Triple-nested [content, title, [], 0]
]Source: _notes.py::delete()
Important: This is a soft delete - it clears note content but does NOT remove the note from the list. The note remains with None content and a status flag of 2.
params = [
notebook_id, # 0
None, # 1
[note_id], # 2: Single-nested note ID
]
# BEFORE delete:
# ['note_id', ['note_id', 'content', [metadata], None, 'title']]
# AFTER delete:
# ['note_id', None, 2] # Status 2 = deleted/clearedNote: Same behavior applies to mind maps via delete_mind_map(). The Python API filters out items with status=2 in list() and list_mind_maps() to match UI behavior.
Source: _notes.py::_get_all_notes_and_mind_maps()
params = [notebook_id]Notes and mind maps share the same storage system and are distinguished by content format.
[
"note_id", # Position 0: Note ID
[
"note_id", # [1][0]: ID (duplicate)
"content", # [1][1]: Note content text
[ # [1][2]: Metadata
1, # Type flag
"user_id", # User ID
[ts, ns] # [timestamp_seconds, nanoseconds]
],
None, # [1][3]: Unknown
"title" # [1][4]: Note title
]
][
"mind_map_id", # Position 0: Mind map ID
[
"mind_map_id", # [1][0]: ID (duplicate)
'{"name": "Root", "children": [...]}', # [1][1]: JSON with children/nodes
[metadata], # [1][2]: Same as notes
None, # [1][3]: Unknown
"Mind Map Title" # [1][4]: Title
]
]["id", None, 2] # Content cleared, status=2 indicates soft-deletedThe Python API:
notes.list()- Returns only active notes (excludes mind maps and status=2)notes.list_mind_maps()- Returns only active mind maps (excludes status=2)artifacts.list()- Includes mind maps as Artifact objects (excludes status=2)
CRITICAL: Source IDs require different nesting levels depending on the method.
| Pattern | Structure | Used By |
|---|---|---|
| Single | [source_id] |
UPDATE_SOURCE position [1] |
| Double | [[source_id]] |
Artifact source_ids_double |
| Triple | [[[source_id]]] |
DELETE_SOURCE, Artifact source_ids_triple |
| Quadruple | [[[[source_id]]]] |
GET_SOURCE_GUIDE |
| Array of Double | [[s1], [s2], ...] |
Artifact generation |
| Array of Triple | [[[s1]], [[s2]], ...] |
Artifact generation |
Building nesting in Python:
source_ids = ["source_1", "source_2", "source_3"]
# Single: [source_id]
single = [source_ids[0]]
# Double: [[source_id]]
double = [[source_ids[0]]]
# Triple: [[[source_id]]]
triple = [[[source_ids[0]]]]
# Array of Double for artifacts
source_ids_double = [[sid] for sid in source_ids]
# Result: [["source_1"], ["source_2"], ["source_3"]]
# Array of Triple for artifacts
source_ids_triple = [[[sid]] for sid in source_ids]
# Result: [[["source_1"]], [["source_2"]], [["source_3"]]]Source: _notebooks.py::get_summary(), _notebooks.py::get_description()
Gets AI-generated summary and suggested topics for a notebook.
params = [
notebook_id, # 0: Notebook ID
[2], # 1: Fixed flag
]
# Called with source_path:
await rpc_call(
RPCMethod.SUMMARIZE,
params,
source_path=f"/notebook/{notebook_id}",
)
# Response structure:
# [
# [ # [0]: Outer container
# [summary_text], # [0][0]: Summary wrapped in list; text at [0][0][0]
# [[ # [0][1][0]: Suggested topics array
# [question, prompt], # Each topic has question and prompt
# ...
# ]],
# null, null, null,
# [[question, score], ...], # [0][5]: Topics with relevance scores
# ]
# ]Source: _sharing.py::get_status()
Get the current share settings for a notebook, including users with access and public status.
params = [
notebook_id, # 0: Notebook ID
[2], # 1: Fixed flag
]
# Called with source_path:
await rpc_call(
RPCMethod.GET_SHARE_STATUS,
params,
source_path=f"/notebook/{notebook_id}",
)
# Response structure:
# [
# [ # [0]: List of users with access
# [
# "email@example.com", # [0]: email
# 1, # [1]: permission (1=owner, 2=editor, 3=viewer)
# [], # [2]: flags (empty)
# [
# "Display Name", # [3][0]: display name
# "https://..." # [3][1]: avatar URL
# ]
# ],
# # ... more users
# ],
# [true], # [1]: is_public - [true] or [false]
# 1000 # [2]: unknown constant (ignore)
# ]Source: _sharing.py::set_public(), _sharing.py::add_user(), _sharing.py::remove_user()
Multi-purpose RPC for managing notebook sharing: toggle public access, add/update users, or remove users.
Toggle public/restricted access:
# access_value: 0=restricted, 1=anyone with link
params = [
[
[
notebook_id,
None, # no user changes
[access_value], # [0]=restricted, [1]=public
[access_value, ""] # [flag, welcome_message]
]
],
1, # action type
None,
[2] # fixed flag
]
# Response: [] (empty on success)Add/update user:
# permission: 2=editor, 3=viewer, 4=remove
# notify_flag: 0=no email, 1=send notification
# message_flag: 0=has message, 1=no message
params = [
[
[
notebook_id,
[[email, None, permission]], # user to add/update
None, # None = no public access change
[message_flag, welcome_message]
]
],
notify_flag, # 0 or 1
None,
[2]
]
# Response: [] (empty on success)Remove user:
params = [
[
[
notebook_id,
[[email, None, 4]], # 4 = remove permission
None,
[0, ""]
]
],
0, # no notification
None,
[2]
]Source: _sharing.py::set_view_level()
Set what viewers can access (full notebook vs chat only).
Note: This uses the same RPC ID as RENAME_NOTEBOOK (s0tc2d) but with different parameter structure.
# view_level: 0=full notebook, 1=chat only
params = [
notebook_id, # 0: Notebook ID
[
[
None, None, None, None, # indices 0-3
None, None, None, None, # indices 4-7
[[view_level]], # index 8: [[0]] or [[1]]
]
],
]
# Response: Full notebook data (same as rename response)Sharing is a notebook-level setting. When you share a notebook, ALL artifacts become accessible.
Notebooks have three sharing dimensions:
-
Visibility (SHARE_NOTEBOOK - QDyure or SHARE_ARTIFACT - RGP97b):
[0]= Restricted (only explicitly shared users)[1]= Anyone with the link
-
View Level (RENAME_NOTEBOOK - s0tc2d):
[[0]]= Full notebook (chat + sources + notes)[[1]]= Chat only (viewers can only use chat)
-
User Permissions (SHARE_NOTEBOOK - QDyure):
1= Owner (read-only, cannot be assigned)2= Editor (can edit notebook)3= Viewer (read-only access)4= Remove (internal: remove user from share list)
Python API:
# Use client.sharing for all sharing operations
status = await client.sharing.get_status(notebook_id)
await client.sharing.set_public(notebook_id, True)
await client.sharing.set_view_level(notebook_id, ShareViewLevel.CHAT_ONLY)
await client.sharing.add_user(notebook_id, "user@example.com", SharePermission.VIEWER)Share URLs:
- Notebook:
https://notebooklm.google.com/notebook/{notebook_id} - Artifact deep-link:
https://notebooklm.google.com/notebook/{notebook_id}?artifactId={artifact_id}
The ?artifactId=xxx parameter creates a deep link that opens the notebook and navigates to that specific artifact. Mind Maps cannot be shared (no public URLs).
Source: _sources.py::refresh()
Refresh a source to get updated content (for URL/Drive sources).
params = [
None, # 0
[source_id], # 1: Single-nested source ID
[2], # 2: Fixed flag
]
# Called with source_path:
await rpc_call(
RPCMethod.REFRESH_SOURCE,
params,
source_path=f"/notebook/{notebook_id}",
)Source: _sources.py::check_freshness()
Check if a source needs to be refreshed.
params = [
None, # 0
[source_id], # 1: Single-nested source ID
[2], # 2: Fixed flag
]
# Called with source_path:
await rpc_call(
RPCMethod.CHECK_SOURCE_FRESHNESS,
params,
source_path=f"/notebook/{notebook_id}",
)
# Response varies by source type:
# URL sources: [] (empty array) = fresh
# Drive sources: [[null, true, [source_id]]] = fresh
# [[null, false, [source_id]]] = stale
# Legacy: True = fresh, False = staleResearch allows searching the web or Google Drive for sources to add to notebooks.
| Code | Source |
|---|---|
| 1 | Web |
| 2 | Google Drive |
Source: _research.py::start() with mode="fast"
Start a fast research session.
# source_type: 1=Web, 2=Drive
params = [
[query, source_type], # 0: Query and source type
None, # 1
1, # 2: Fixed value
notebook_id, # 3: Notebook ID
]
# Called with source_path:
await rpc_call(
RPCMethod.START_FAST_RESEARCH,
params,
source_path=f"/notebook/{notebook_id}",
)
# Response: [task_id, report_id, ...]Source: _research.py::start() with mode="deep"
Start a deep research session (web only, more thorough).
# Deep research only supports Web (source_type=1)
params = [
None, # 0
[1], # 1: Fixed flag
[query, source_type], # 2: Query and source type
5, # 3: Fixed value
notebook_id, # 4: Notebook ID
]
# Called with source_path:
await rpc_call(
RPCMethod.START_DEEP_RESEARCH,
params,
source_path=f"/notebook/{notebook_id}",
)
# Response: [task_id, report_id, ...]Deep research is not complete after QA9ei alone. In the observed browser/client
flow, the returned report_id later becomes important during polling and import:
QA9eistarts the deep research job and returns[task_id, report_id, ...]e3bVqcpolls the notebook for all research tasks and exposes the report contentLBwxtbimports the report entry plus selected web sources using the later polled deep-research task ID, which commonly matches the earlierreport_id
Source: _research.py::poll()
Poll for research results.
params = [
None, # 0
None, # 1
notebook_id, # 2: Notebook ID
]
# Called with source_path:
await rpc_call(
RPCMethod.POLL_RESEARCH,
params,
source_path=f"/notebook/{notebook_id}",
)
# Response structure:
# [
# [task_id, [
# ...,
# query_info, # [1]: [query_text, ...]
# ...,
# sources_and_summary, # [3]: [[sources], summary_text]
# status_code, # [4]: 2=completed, 6=completed (deep), other=in_progress
# ]],
# ...
# ]
#
# sources_and_summary[0] can contain a mix of:
#
# Fast research web source:
# [url, title, desc, type, ...]
#
# Deep research report source (current shape):
# [None, [title, report_markdown], None, type, ...]
#
# Deep research report source (legacy shape):
# [None, title, None, type, None, None, [chunk1, chunk2, ...]]
#
# Notes:
# - The RPC returns all research tasks for the notebook, not just the latest one.
# - The client exposes all parsed tasks via an additive `tasks` field and keeps the
# top-level return value backward-compatible as the latest task.
# - For deep research, sources parsed from poll() carry `research_task_id`, which is
# later used by IMPORT_RESEARCH.Source: _research.py::import_sources()
Import selected research sources into the notebook.
# Build source array from selected sources
# Deep research imports prepend a special report entry before regular web sources.
source_array = []
# Deep research report entry:
source_array.append([
None, # 0
[title, markdown], # 1: Report title and full markdown body
None, # 2
3, # 3: Special report marker
None, # 4
None, # 5
None, # 6
None, # 7
None, # 8
None, # 9
3, # 10: Special report marker
])
# Standard web source entry:
source_array.append([
None, # 0
None, # 1
[url, title], # 2: URL and title
None, # 3
None, # 4
None, # 5
None, # 6
None, # 7
None, # 8
None, # 9
2, # 10: Standard web-source marker
])
params = [
None, # 0
[1], # 1: Fixed flag
task_id, # 2: Research task ID (for deep research, use the polled task ID)
notebook_id, # 3: Notebook ID
source_array, # 4: Array of sources to import
]
# Called with source_path:
await rpc_call(
RPCMethod.IMPORT_RESEARCH,
params,
source_path=f"/notebook/{notebook_id}",
)
# Response: Imported notebook sources with IDs
#
# Notes:
# - Deep research report preservation depends on importing the special report entry,
# not just the URL sources.
# - The browser/client flow uses the later polled deep-research task ID here rather
# than blindly reusing the original task ID returned by START_DEEP_RESEARCH.Global user settings that affect all notebooks in an account.
Source: _settings.py::get_output_language()
Get user settings including the current output language.
params = [
None, # 0
[1, None, None, None, None, None, None, None, None, None, [1]], # 1: Fixed config
]
# Called with root source_path:
await rpc_call(
RPCMethod.GET_USER_SETTINGS,
params,
source_path="/", # Global setting uses root path
)
# Response structure:
# [[
# null,
# [6, 500, 300, 500000], # [0][1]: Limits/quotas
# [true, null, null, true, ["ja"]], # [0][2]: Settings (language at [4][0])
# [[1]], # [0][3]: Unknown
# [true, 1, 3, 2] # [0][4]: Feature flags
# ]]
#
# Language code at: result[0][2][4][0]Source: _settings.py::set_output_language()
Set user settings (currently used for output language).
Important: This is a GLOBAL setting that affects all notebooks in the account.
# Language code goes in a triple-nested structure
params = [
[[None, [[None, None, None, None, [language]]]]], # 0: Nested language config
]
# Called with root source_path:
await rpc_call(
RPCMethod.SET_USER_SETTINGS,
params,
source_path="/", # Global setting uses root path
)
# Response structure:
# [
# null,
# [6, 500, 300, 500000], # [1]: Limits
# [true, null, null, true, ["ja"]], # [2]: Updated settings (language at [4][0])
# ...
# ]
#
# Language code at: result[2][4][0]Supported Languages:
Common language codes include:
en(English),ja(日本語),zh_Hans(中文简体),zh_Hant(中文繁體)ko(한국어),es(Español),fr(Français),de(Deutsch),pt_BR(Português)- See
cli/language.py::SUPPORTED_LANGUAGESfor the full list of 80+ languages
Source: _artifacts.py::rename()
Rename an artifact.
params = [
[artifact_id, new_title], # 0: Artifact ID and new title
[["title"]], # 1: Field mask (update title)
]
# Called with source_path:
await rpc_call(
RPCMethod.RENAME_ARTIFACT,
params,
source_path=f"/notebook/{notebook_id}",
)Source: _artifacts.py::export_report(), _artifacts.py::export_data_table(), _artifacts.py::export()
Export an artifact to Google Docs or Sheets.
# Export types:
# 1 = Google Docs
# 2 = Google Sheets
params = [
None, # 0
artifact_id, # 1: Artifact ID
content, # 2: Content to export (optional, can be None)
title, # 3: Title for exported document
export_type, # 4: 1=Docs, 2=Sheets
]
# Called with source_path:
await rpc_call(
RPCMethod.EXPORT_ARTIFACT,
params,
source_path=f"/notebook/{notebook_id}",
)
# Response: Export result with document URLSource: _notebooks.py::share()
Toggle notebook sharing. Sharing is a notebook-level setting - when enabled, ALL artifacts in the notebook become accessible via their URLs.
Note: Mind Maps are NOT shareable (they don't have public URLs).
# share_options: [1] for public, [0] for private
# artifact_id is optional - used to generate a deep-link URL to that specific artifact
params = [
share_options, # 0: [1] for public link, [0] for private
notebook_id, # 1: Notebook ID
artifact_id, # 2: Optional - artifact ID for deep-link URL
]
# Called with source_path:
await rpc_call(
RPCMethod.SHARE_ARTIFACT,
params,
source_path=f"/notebook/{notebook_id}",
)
# Share URL format:
# - Notebook: https://notebooklm.google.com/notebook/{notebook_id}
# - Artifact deep-link: https://notebooklm.google.com/notebook/{notebook_id}?artifactId={artifact_id}Important: The ?artifactId=xxx URL is a deep link - it opens the shared notebook and navigates to that artifact. The artifact itself isn't independently shared.
Source: _artifacts.py::_get_artifact_content()
Fetch HTML content for quiz or flashcard artifacts. Used for downloading these artifact types in various formats.
params = [artifact_id] # Just the artifact ID
# Called with source_path:
await rpc_call(
RPCMethod.GET_INTERACTIVE_HTML,
params,
source_path=f"/notebook/{notebook_id}",
)
# Response structure:
# [[
# ..., # indices 0-8: metadata
# [html_content], # index 9: HTML content array
# ...
# ]]
#
# HTML content contains quiz questions or flashcard data
# that can be parsed into JSON, Markdown, or kept as HTML.Source: _artifacts.py::suggest_reports()
Get AI-suggested report formats based on notebook content.
params = [
[2], # 0: Fixed flag (same pattern as LIST_ARTIFACTS)
notebook_id, # 1: Notebook ID
]
# Called with source_path:
await rpc_call(
RPCMethod.GET_SUGGESTED_REPORTS,
params,
source_path=f"/notebook/{notebook_id}",
)
# Response structure:
# [[
# [title, description, None, None, prompt, audience_level],
# ...
# ]]
#
# Example response item:
# ["Research Paper", "An academic paper analyzing...", None, None,
# "Write a research paper for an academic audience...", 2]
#
# audience_level: 1=Beginner, 2=Intermediate, 3=AdvancedNote: This is the dedicated RPC method for getting suggested report formats. Previously ACT_ON_SOURCES with "suggested_report_formats" command was attempted but it doesn't work correctly.
Most operations complete nearly instantly:
- Notebook operations: list, create, rename, delete
- Source metadata: list, rename, delete
- Note operations: create, update, delete
- Chat configuration
- Artifact listing
These require backend processing - wait for completion:
- Add source (URL): Network fetch + text extraction
- Add source (file): Upload + parsing
- Add source (YouTube): Transcript extraction
- Mind Map generation: Usually faster than other generation types
AI-generated content takes significant time:
- Audio Overview: Several minutes
- Video Overview: Several minutes (longer than audio)
- Reports/Study Guides: 1-2 minutes
- Quiz/Flashcards: 1-2 minutes
- Infographic/Slide Deck/Data Table: 1-2 minutes
Some operations can run much longer:
- Deep Research: Can take many minutes depending on query complexity
When automating, poll for completion rather than using fixed timeouts. Check artifact status or source processing state periodically.
These RPC method IDs exist in rpc/types.py but are either legacy (superseded by other methods) or not currently used in the implementation. Documented here for completeness.
| RPC ID | Method | Status | Notes |
|---|---|---|---|
hizoJc |
GET_SOURCE | Broken | Code comments indicate this doesn't work; get() uses GET_NOTEBOOK instead |
qXyaNe |
DISCOVER_SOURCES | Reserved | Not fully rolled out by Google yet |
Why keep these? These IDs are preserved in the codebase in case:
- Google re-enables or changes their functionality
- Future reverse-engineering reveals their purpose
- They become useful for specific edge cases
Note: The unified CREATE_ARTIFACT (R7cb6c) method handles all artifact generation (audio, video, reports, quizzes, etc.).