A Source is a document, web page, YouTube video, or text snippet ingested
into a parent Notebook.
Resource representation
{
"name": "notebooks/550e8400-e29b-41d4-a716-446655440000/sources/770e8400-e29b-41d4-a716-446655440111",
"displayName": "Gemini API Documentation",
"mimeType": "text/html",
"state": "ACTIVE",
"createTime": "2026-08-06T04:01:12Z",
"originalUrl": "https://ai.google.dev/gemini-api/docs"
}
| Field | Type | Behavior | Description |
|---|---|---|---|
name |
string |
Identifier | Format notebooks/{notebook}/sources/{source}. |
displayName |
string |
Optional | Display title of the source (up to 500 characters). If omitted for web page URLs, the page title is used; for YouTube URLs, the video title is always used. |
mimeType |
string |
Optional | MIME type of the source. On input, optional for text_content (text/plain or text/markdown; auto-detected if omitted). On output, populated by the server (such as text/html for web URLs or video/vnd.youtube.yt for YouTube videos). |
state |
enum (State) |
Output only | Processing lifecycle state returned by GetSource and ListSources: PROCESSING, ACTIVE, FAILED, or DELETING. |
createTime |
string (Timestamp) |
Output only | Creation timestamp in RFC 3339 format. |
error |
object (Status) |
Output only | Populated with a google.rpc.Status (code, message) when state is FAILED. |
originalUrl |
string |
Output only | Original URL for web_content sources. |
webContent |
object (WebContent) |
Input (oneof input_spec) |
Public web URL or YouTube URL to ingest ({"url": "https://..."}). |
textContent |
object (TextContent) |
Input (oneof input_spec) |
Inline plain text or Markdown string ({"content": "..."}). |
Supported source types
Each CreateSource request must set one input_spec field (web_content or
text_content):
| Source type | input_spec field |
mime_type |
Requirements and behavior |
|---|---|---|---|
| Web page | web_content.url |
Omit | Must be a publicly accessible https:// URL (no login wall or paywall). |
| YouTube video | web_content.url |
Omit | Standard watch URL (https://www.youtube.com/watch?v=... or https://youtu.be/...) for a public or unlisted video with captions. |
| Plain text | text_content.content |
text/plain (or omit) |
Inline UTF-8 text up to 500,000 words and 1,000,000 bytes (1 MB). |
| Markdown | text_content.content |
text/markdown (or omit) |
Inline UTF-8 Markdown up to 500,000 words and 1,000,000 bytes (1 MB). |
Add a source (CreateSource)
Send a POST request to /v1/{parent=notebooks/*}/sources.
Web page or YouTube video
REST
# Public web page
curl -s -X POST "https://notebook.googleapis.com/v1/${NOTEBOOK_NAME}/sources" \
-H "Authorization: Bearer $ACCESS_TOKEN" \
-H "x-goog-user-project: $PROJECT_ID" \
-H "Content-Type: application/json" \
-d '{
"display_name": "Gemini API Docs",
"web_content": {
"url": "https://ai.google.dev/gemini-api/docs"
}
}'
# YouTube video
curl -s -X POST "https://notebook.googleapis.com/v1/${NOTEBOOK_NAME}/sources" \
-H "Authorization: Bearer $ACCESS_TOKEN" \
-H "x-goog-user-project: $PROJECT_ID" \
-H "Content-Type: application/json" \
-d '{
"web_content": {
"url": "https://www.youtube.com/watch?v=dQw4w9WgXcQ"
}
}'
Python
import requests
headers = {
"Authorization": f"Bearer {ACCESS_TOKEN}",
"x-goog-user-project": PROJECT_ID,
}
resp = requests.post(
f"https://notebook.googleapis.com/v1/{notebook_name}/sources",
headers=headers,
json={
"display_name": "Gemini API Docs",
"web_content": {"url": "https://ai.google.dev/gemini-api/docs"},
},
)
source = resp.json()
print(source["name"])
Plain text or Markdown
REST
curl -s -X POST "https://notebook.googleapis.com/v1/${NOTEBOOK_NAME}/sources" \
-H "Authorization: Bearer $ACCESS_TOKEN" \
-H "x-goog-user-project: $PROJECT_ID" \
-H "Content-Type: application/json" \
-d '{
"display_name": "Architecture Notes",
"mime_type": "text/markdown",
"text_content": {
"content": "# System Architecture\n\nServices communicate over gRPC."
}
}'
Python
resp = requests.post(
f"https://notebook.googleapis.com/v1/{notebook_name}/sources",
headers=headers,
json={
"display_name": "Architecture Notes",
"mime_type": "text/markdown",
"text_content": {
"content": "# System Architecture\n\nServices communicate over gRPC."
},
},
)
print(resp.json())
Get a source and poll processing status
Source ingestion is asynchronous. New sources start in PROCESSING and
transition to ACTIVE (ready) or FAILED. Poll
GET /v1/{name=notebooks/*/sources/*} until a terminal state is reached:
REST
curl -s -X GET "https://notebook.googleapis.com/v1/${SOURCE_NAME}" \
-H "Authorization: Bearer $ACCESS_TOKEN" \
-H "x-goog-user-project: $PROJECT_ID"
Python
import time
def wait_for_source(source_name: str, timeout_sec: int = 120) -> dict:
deadline = time.time() + timeout_sec
delay = 2.0
while time.time() < deadline:
source = requests.get(
f"https://notebook.googleapis.com/v1/{source_name}",
headers=headers,
).json()
if source.get("state") in ("ACTIVE", "FAILED"):
return source
time.sleep(delay)
delay = min(delay * 1.5, 10.0)
raise TimeoutError(f"Source {source_name} still PROCESSING after {timeout_sec}s")
List sources
List all sources in a notebook (GET /v1/{parent=notebooks/*}/sources). Use
pageSize (default 50, max 100) and pageToken to paginate. Sources are
returned in ascending order of creation time, so sources added during
pagination appear on later pages:
REST
curl -s -X GET "https://notebook.googleapis.com/v1/${NOTEBOOK_NAME}/sources?pageSize=100" \
-H "Authorization: Bearer $ACCESS_TOKEN" \
-H "x-goog-user-project: $PROJECT_ID"
Python
resp = requests.get(
f"https://notebook.googleapis.com/v1/{notebook_name}/sources",
headers=headers,
params={"pageSize": 100},
)
for src in resp.json().get("sources", []):
print(src["name"], src.get("state"))
Error handling
Errors can occur synchronously on the HTTP request or asynchronously during
source processing (state: "FAILED"):
- Synchronous HTTP errors:
400 INVALID_ARGUMENT: Missinginput_spec, unsupportedmime_type, blank or oversizedtext_content(over 1,000,000 bytes), invalid URL format, or unsupported URL domain (such as Google Drive or Google Docs URLs).400 INVALID_ARGUMENTon list: A malformedpageToken, or apageTokenissued for a different notebook. Restart listing from the first page.401 UNAUTHENTICATEDor403 PERMISSION_DENIED: Expired OAuth token, missinghttps://www.googleapis.com/auth/notebookscope, missingx-goog-user-projectquota project header when using Application Default Credentials, authenticating with an unsupported Google Workspace account (WORKSPACE_ACCOUNT_NOT_SUPPORTED), or insufficient access to the target notebook.404 NOT_FOUND: The notebook or source resource name does not exist, or the authenticated Google Account has never signed in to Gemini Notebook (Account not found for user.).429 RESOURCE_EXHAUSTED: Project rate limit (100 RPM), per-user rate limit (20 RPMforCreateSource,60 RPMfor other methods), or account tier quota exceeded.
Asynchronous ingestion errors (
state: "FAILED"): When a URL cannot be fetched, a YouTube video has no available transcript, or a source exceeds the 500,000-word limit,GetSourcereturns"state": "FAILED"with details in theerrorfield:{ "name": "notebooks/550e8400-e29b-41d4-a716-446655440000/sources/770e8400-e29b-41d4-a716-446655440111", "displayName": "Unreachable Page", "mimeType": "text/html", "state": "FAILED", "createTime": "2026-08-06T04:01:12Z", "error": { "code": 9, "message": "The source is unreachable." }, "originalUrl": "https://example.com/unreachable" }Common
error(google.rpc.Status) values whenstateisFAILED:error.codegRPC status error.message3INVALID_ARGUMENT"Source exceeds the maximum allowed length.","The source is empty and contains no content.","The source domain is blocked.", or"The source content was blocked by a safety policy."7PERMISSION_DENIED"The source is behind a paywall."8RESOURCE_EXHAUSTED"The source limit has been exceeded."9FAILED_PRECONDITION"The source is unreachable."13INTERNAL"An internal error occurred while processing the source.","Failed to process the YouTube video.", or"The source failed to be processed."