Status: Active
Last Updated: 2026-01-18
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_VIDEO | Unified artifact generation | _artifacts.py |
gArtLc |
LIST_ARTIFACTS | List/poll artifacts | _artifacts.py |
V5N4be |
DELETE_STUDIO | Delete studio artifact | _artifacts.py |
hPTbtc |
GET_CONVERSATION_HISTORY | Get chat history | _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 |
ACT_ON_SOURCES | 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 | Not implemented |
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::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_history()
params = [
[], # 0: Empty sources array
None, # 1
notebook_id, # 2
limit, # 3: Max conversations (e.g., 20)
]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]: StudioContentType.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]: StudioContentType.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]: StudioContentType.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]: StudioContentType.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]: StudioContentType.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]: StudioContentType.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]: StudioContentType.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]: StudioContentType.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: ACT_ON_SOURCES (yyryJe), NOT CREATE_VIDEO
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()
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:
# [
# [summary_text], # [0][0]: Summary string
# [[ # [1][0]: Suggested topics array
# [question, prompt], # Each topic has question and prompt
# ...
# ]],
# ]Source: Not yet implemented
Get the current share settings for a notebook.
params = [
notebook_id, # 0: Notebook ID
[2], # 1: Fixed flag
]
# Response: Share status informationSource: _notebooks.py::share()
Set notebook visibility (restricted vs anyone with link).
# visibility: [0] = restricted, [1] = anyone with link
# access: [0, ""] = chat only?, [1, ""] = full notebook?
params = [
[
[
notebook_id, # Notebook ID
None,
visibility, # [0]=restricted, [1]=public link
access, # [0, ""] or [1, ""] - access level
]
],
1, # Fixed value
None,
[2], # Fixed flag
]Source: Not yet implemented (same RPC ID as RENAME_NOTEBOOK)
Set viewer access level (chat only vs full notebook).
Note: This uses the same RPC ID as RENAME_NOTEBOOK (s0tc2d) but with different parameter structure.
# The access toggle is at index [8] in a deeply nested array
params = [
notebook_id, # 0: Notebook ID
[
[
None, None, None, None, # indices 0-3
None, None, None, None, # indices 4-7
[[access_level]], # index 8: [[1]] for chat only?
]
],
]Sharing is a notebook-level setting. When you share a notebook, ALL artifacts become accessible.
Notebooks have two independent sharing toggles:
-
Visibility (SHARE_NOTEBOOK - QDyure or SHARE_ARTIFACT - RGP97b):
[0]= Restricted (only specific people)[1]= Anyone with the link
-
Access Level (SET_SHARE_ACCESS - s0tc2d):
[[1]]= Chat only (viewers can only use chat)- Other = Full notebook access
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: True = fresh, False = stale (needs refresh)Research 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, ...]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 (per task):
# [
# [task_id, [
# ...,
# query_info, # [1]: [query_text, ...]
# ...,
# sources_and_summary, # [3]: [[sources], summary_text]
# status_code, # [4]: 2=completed, other=in_progress
# ]],
# ...
# ]Source: _research.py::import_sources()
Import selected research sources into the notebook.
# Build source array from selected sources
source_array = []
for src in sources:
source_data = [
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: Fixed value
]
source_array.append(source_data)
params = [
None, # 0
[1], # 1: Fixed flag
task_id, # 2: Research 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 sources with IDsGlobal 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() (currently uses ACT_ON_SOURCES alternative)
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: The current implementation uses ACT_ON_SOURCES with "suggested_report_formats" command as an alternative approach. This dedicated RPC method provides the same functionality.
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 | Unused | Purpose unknown, not called in implementation |
AHyHrd |
CREATE_AUDIO | Legacy | Superseded by unified CREATE_VIDEO (R7cb6c) |
VUsiyb |
GET_AUDIO | Legacy | Superseded by LIST_ARTIFACTS (gArtLc) |
sJDbic |
DELETE_AUDIO | Legacy | Superseded by DELETE_STUDIO (V5N4be) |
xpWGLf |
CREATE_ARTIFACT | Unused | Purpose unknown, CREATE_VIDEO handles all artifact types |
BnLyuf |
GET_ARTIFACT | Unused | Purpose unknown, LIST_ARTIFACTS provides artifact data |
LfTXoe |
LIST_ARTIFACTS_ALT | Unused | Alternative list method, main LIST_ARTIFACTS (gArtLc) is used |
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_VIDEO (R7cb6c) method handles all artifact generation (audio, video, reports, quizzes, etc.) despite its name. The legacy audio-specific methods were likely from an earlier API version.