Sources

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: Missing input_spec, unsupported mime_type, blank or oversized text_content (over 1,000,000 bytes), invalid URL format, or unsupported URL domain (such as Google Drive or Google Docs URLs).
    • 400 INVALID_ARGUMENT on list: A malformed pageToken, or a pageToken issued for a different notebook. Restart listing from the first page.
    • 401 UNAUTHENTICATED or 403 PERMISSION_DENIED: Expired OAuth token, missing https://www.googleapis.com/auth/notebook scope, missing x-goog-user-project quota 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 RPM for CreateSource, 60 RPM for 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, GetSource returns "state": "FAILED" with details in the error field:

    {
      "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 when state is FAILED:

    error.code gRPC status error.message
    3 INVALID_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."
    7 PERMISSION_DENIED "The source is behind a paywall."
    8 RESOURCE_EXHAUSTED "The source limit has been exceeded."
    9 FAILED_PRECONDITION "The source is unreachable."
    13 INTERNAL "An internal error occurred while processing the source.", "Failed to process the YouTube video.", or "The source failed to be processed."