[0.7.0] - 2026-06-04
Highlights
- v0.8.0 error-contract runway. This release lands the additive half of a
cross-SDK convergence on "absence and refusal raise; only success and
async-lifecycle state are returned." You can adopt the forward-compatible form
today and run on both 0.7.0 and 0.8.0 with no flag day:- Test your code against 0.8.0 today — set
NOTEBOOKLM_FUTURE_ERRORS=1to
opt your process into the v0.8.0 error contract (get()raises
*NotFoundErroron a miss, all dict-style access on the typed returns
([...],.get(),in,.keys(), …) raises, and
the deprecatedwait_for_completion(interval=...)alias raises) without
changing default behavior. Run your test suite with it on to find breakage
before you upgrade. This is the "test-before-you-migrate" mechanism paired
with the Upgrading to v0.8.0 guide. get_or_none()— a new, silent optional lookup on
sources/artifacts/notes/mind_mapsthat returns the object or
Noneand never warns. It is the sanctioned replacement for the now-soft
get()-returns-Nonepattern.get()now warns on a miss (still returnsNonethis release) and will
raise the typed*NotFoundErrorfor its domain in v0.8.0 (#1247).- Typed
*NotFoundErrorper domain —NoteNotFoundError/
MindMapNotFoundErrorjoin the existing source / artifact / notebook errors,
all catchable via theNotFoundErrorumbrella.
- Test your code against 0.8.0 today — set
- Breaking:
rename()returns the renamed object;delete()returnsNone.
rename()now re-fetches and returns the live object (raising*NotFoundError
on a missing target), anddelete()returnsNoneand is idempotent on an
already-absent target. See Breaking changes below before upgrading. - Typed dataclass returns for
research.poll/start/
wait_for_completion,artifacts.generate_mind_map, andsources.get_guide
(ResearchStatus,ResearchTask,ResearchSource,ResearchStart,
MindMapResult,SourceGuide) — attribute access instead of untyped dicts,
with a backward-compatible read-only mapping bridge. - Unified
client.mind_mapssurface over both backends (note-backed +
interactive), plusclient.artifacts.retry_failed()to retry a failed
Studio artifact in place (and a matchingnotebooklm artifact retrycommand).
Breaking changes
⚠ BREAKING —
rename()returns the renamed object;delete()returnsNone.These return-type changes ship now, as a clean break with no deprecation
runway, because the old returns were never usable contracts a caller could
depend on in good faith. (Contrastget()'sNone-on-miss, which is a
real, documented contract and keeps its full deprecation runway to v0.8.0 —
see issue #1247. The coherent story: reads/renames are missing-strict;
deletes are absence-idempotent.)
rename()→ returns the renamed object, raises*NotFoundErroron a
missing target (issues #1255, #1256):
artifacts.renamepreviously returnedNoneeven on success (an
unusable return); it now re-fetches and returns the renamedArtifact,
raisingArtifactNotFoundErrorwhen the target is absent.sources.renamepreviously fabricated an unverified
Source(id, title)when the RPC echoed nothing (a silent-false-success
bug); it now prefers theUPDATE_SOURCEecho, falls back to an internal
fetch, returns the realSource, and raisesSourceNotFoundErrorwhen the
target is absent. The fabrication is gone.notebooks.renamealready returned the re-fetchedNotebook(the
reference behavior) — unchanged.mind_maps.rename(both note-backed and interactive backings) now returns
the renamedMindMapand raises on a missing target.- Error taxonomy: only genuine absence (empty-payload / absent-from-list,
detected via a content/list lookup — not a transport 404) maps to a
*NotFoundError. Transport /429/5xx/ auth errors propagate as
themselves and are never laundered into a synthetic*NotFoundError.- Bulk opt-out: every
rename()acceptsreturn_object: bool = True.
Passreturn_object=Falseto skip the hydrate re-fetch and returnNone
(artifacts' re-fetch is a fullLIST_ARTIFACTS, so bulk renamers that
ignore the return should opt out to avoid N extra list calls).
delete()→ returnsNone, idempotent on a missing target (issue #1211):
notebooks/sources/artifacts/notes.deleteand
notes.delete_mind_map(andmind_maps.delete) previously returned a
hardcodedTrue; they now returnNone. The oldTruewas a tautology
(neverFalse), butTrue → Noneis a real, observable flip from truthy
to falsy:Drop the# BEFORE (entered the block; delete always returned True) if await client.sources.delete(nb_id, src_id): ... # this branch no longer runs — delete() now returns None (falsy)if; calldelete()for its effect. Useget()first if you
need to assert existence.- Idempotent: deleting an already-absent target succeeds (returns
None); it does not raise*NotFoundError. This matches HTTPDELETE
idempotency and keeps retry/teardown loops clean. (The one exception is
mind_maps.deletewithout an explicitkind, which must list to pick
the right RPC family and so raisesValueErrorfor an unknown id; pass
kind=to delete idempotently.)- Real failures still raise:
allow_null=Truetolerates only a null
result, not an RPC/HTTP error — a403/5xx/ auth / transport
failure on delete still propagates. "Idempotent on missing" is not "swallow
all errors."
⚠ BREAKING — lapsed v0.6.0-targeted deprecations removed.
These deprecation shims advertised removal in v0.6.0, which has shipped, so
they have now been removed. This is a pre-1.0 breaking change. See
docs/deprecations.md"Removed in v0.7.0".
- Positional
wait/wait_timeoutonSourcesAPI.add_url/add_text/
add_file/add_drive— these parameters are now keyword-only.
Passing them positionally raisesTypeError.# BEFORE (deprecated, emitted DeprecationWarning) await client.sources.add_url(nb_id, url, True, 45.0) # AFTER await client.sources.add_url(nb_id, url, wait=True, wait_timeout=45.0)ArtifactsAPI.wait_for_completion(poll_interval=...)— the deprecated
poll_intervalalias was removed; useinitial_interval=...(same
cadence). Passingpoll_intervalraisesTypeError.# BEFORE await client.artifacts.wait_for_completion(nb_id, task_id, poll_interval=5.0) # AFTER await client.artifacts.wait_for_completion(nb_id, task_id, initial_interval=5.0)NOTEBOOKLM_STRICT_DECODE=0soft-mode opt-out — removed. Strict
decoding is now the only mode: schema-drift helpers (notablysafe_index)
always raiseUnknownRPCMethodErroron shape drift instead of
warn-and-returningNone/[]. The env var is now ignored (no-op).
Callers that previously relied on the soft fallback should handle
UnknownRPCMethodError(a subclass ofRPCError/DecodingError).NotesAPI.create_from_chat(...)— removed (deprecated since v0.5.0,
two MINOR cycles of warnings served; the documented removal target was
v0.7.0). It was a pure forwarder. UseChatAPI.save_answer_as_note(...),
the canonical citation-rich saved-from-chat method and data owner
(ADR-0013):await client.chat.save_answer_as_note(nb_id, ask_result).
The now-unusedsave_chat_answerinjection plumbing onNotesAPIwas
removed with it.Not removed:
SourcesAPI.add_file(mime_type=...)and
notebooklm source add --mime-type(file sources) were reassessed and
kept —mime_typewas re-wired to set the resumable-upload content-type
header (overriding filename-extension inference), so it is a supported
parameter, not a dead shim. Its staleDeprecationWarninghad already been
removed; the documentation now reflects this.Not removed: awaiting
NotebookLMClient.from_storage(...)still works —
its deprecation targets v1.0, not v0.6.0.
Added
get_or_none()— the sanctioned silent optional lookup, added to
client.sources/client.artifacts/client.notes/client.mind_maps.
It returns the entity (Source/Artifact/Note/MindMap) orNone
for a genuine absence and never warns, making it the drop-in migration
target for the now-deprecatedget()-returns-Nonepattern (see
Deprecated below; issue #1247). Unlikeget(), it does not swallow
transport, auth, or decode faults — only a real "not found" yieldsNone.Additive (ADR-0019; issue #1247).# Silent optional lookup (no DeprecationWarning): src = await client.sources.get_or_none(nb_id, source_id) if src is None: ...
NOTEBOOKLM_FUTURE_ERRORSopt-in preview flag — run the v0.8.0 error
contract early to test forward-compatibility before the breaking flips ship
(ADR-0019 / umbrella #1346). Default-off and byte-identical to current
v0.7.0 behavior; when truthy (1/true/yes/on) the three warn-runways
adopt their v0.8.0 raise-target:sources.get/artifacts.get/
notes.get/mind_maps.getraise the matching*NotFoundErroron a miss
(#1247), the wholeMappingCompatMixinmapping surface —[...]
subscript plus the silentget/keys/items/values/len/in/
itershims — raises the exact error a bare dataclass would (#1251), and
the deprecatedResearchAPI.wait_for_completion(interval=...)alias raises
TypeError(#1254). Takes precedence overNOTEBOOKLM_QUIET_DEPRECATIONS
(a runway raises regardless of quiet). The fourget()methods are now routed
through a single_lookup.resolve_getbridge, eliminating the hand-duplicated
warn-on-miss pattern. Helper:notebooklm._deprecation.future_errors_enabled.
The flag now also previews the purely-behavioral v0.8.0 changes that have
no warn-runway (#1405): the uninformativeboolreturns ofsources.refresh
andchat.delete_conversationbecomeNone(#1290); a synchronous generation
refusal raises the decoder'sRateLimitError/RPCError/
DecodingError/ArtifactFeatureUnavailableErrorinstead of being swallowed
intoGenerationStatus(status="failed")/ returnedNone— across
_call_generate,revise_slide,_parse_generation_result, and
research.start(#1342); and the mutate-existing opsnotes.updateand
sources/artifactsrename(return_object=False)fail loud with a
*NotFoundErroron a missing target (#1362). These previews are runtime-only —
no public return annotation changes until the v0.8.0 flip — so default-off
stays byte-identical. Does not close #1247/#1251/#1254/#1290/#1342/#1362 —
the runways and current behavior remain until the v0.8.0 flip. See
docs/deprecations.md. Additive (issues #1346, #1405).client.artifacts.retry_failed(notebook_id, artifact_id)— retry a failed
Studio artifact in place (the web UI "Retry" action), via the new
RETRY_ARTIFACT(Rytqqe) RPC. The artifact is not deleted first and the
sameartifact_idis preserved, so existingpoll_status()/
wait_for_completion()flows keep working. Follows the ADR-0019 "async
kickoff" contract: an accepted retry returns
GenerationStatus(status="in_progress"), while a synchronous refusal
(USER_DISPLAYABLE_ERROR— rate limit / quota / not-retryable) raises the
underlyingRateLimitError/RPCErrorrather than returning a
status="failed"handle. Newnotebooklm artifact retry <artifact_id> [--wait] [--json]CLI command. Additive (issues #1319, #1346).notebooklm.artifacts.with_rate_limit_retrynow also retries when the
wrapped callable raisesRateLimitError(backing off and re-raising once
the retry budget is exhausted), so it can wrap the newretry_failed. The
existing returned-rate-limited-GenerationStatuspath (used bygenerate_*)
is unchanged — this is a backward-compatible addition (issue #1319).- New public exception types for the note and mind-map domains, mirroring the
existingSourceError/SourceNotFoundErrorshape:NoteError+
NoteNotFoundErrorandMindMapError+MindMapNotFoundError. Each
*NotFoundErroris a triple-base(NotFoundError, RPCError, <Domain>Error),
so it is catchable via the cross-domainNotFoundErrorumbrella, at
transport-levelexcept RPCErrorcall sites, and at domain-level
except NoteError/except MindMapErrorcall sites. These are the
prerequisite for the mind-map not-found work (ADR-0019; issues #1291, #1346).
MindMapNotFoundErroris now raised by themind_mapsmutation paths (see
Changed below);NoteNotFoundErroris not raised by any method yet. ResearchStatus.NOT_FOUND— a typed lifecycle sentinel for the
poll-observed absence of a specific requested research task, distinct from
NO_RESEARCH("nothing in flight").research.poll(notebook_id, task_id=...)
now returnsResearchTask.not_found(task_id)(statusNOT_FOUND, carrying
the requested id) when a non-empty pinnedtask_idmatches no in-flight task;
the unfilteredtask_id=Noneempty poll still returnsNO_RESEARCH
unchanged. Additive and non-breaking — the poll never raises for an absent
task (ADR-0019 Rule 4; issues #1344, #1346).- Typed return values for the research / mind-map / source-guide methods.
research.poll/research.start/research.wait_for_completion,
artifacts.generate_mind_map, andsources.get_guidenow return typed
dataclasses instead of untypeddict[str, Any], with a new
ResearchStatusstr-enum for the status field. The new public types are
exported fromnotebooklmandnotebooklm.types:
ResearchStatus,ResearchTask,ResearchSource,ResearchStart,
MindMapResult, andSourceGuide.This is backward-compatible:from notebooklm import ResearchStatus result = await client.research.poll(nb_id) if result.status == ResearchStatus.COMPLETED: # also == "completed" for source in result.sources: print(source.title, source.url) guide = await client.sources.get_guide(nb_id, src_id) print(guide.summary, guide.keywords) mind_map = await client.artifacts.generate_mind_map(nb_id) print(mind_map.note_id, mind_map.mind_map)
ResearchStatusis astrenum (so
status == "completed"still holds), and the returned dataclasses keep
working as read-only mappings —result["status"]/result.get("status")
/result.keys()/"status" in resultall still work (subscript emits a
DeprecationWarning; see Deprecated below). The dict-subscript bridge is
removed in v0.8.0. WaitTimeoutError— one catchable base for every wait/poll timeout. A
new public exception (notebooklm.WaitTimeoutError) is the common base of
SourceTimeoutError,ArtifactTimeoutError(and its
ArtifactPendingTimeoutError/ArtifactInProgressTimeoutErrorsubclasses),
and the newResearchTimeoutError, so a singleexcept WaitTimeoutError
clause catches a wait timeout from any domain. It mixes in the built-in
TimeoutError, so this is fully backward-compatible: existing
except TimeoutErrorclauses keep catching every wait timeout unchanged.from notebooklm import WaitTimeoutError try: await client.sources.wait_until_ready(nb_id, src_id) await client.artifacts.wait_for_completion(nb_id, task_id) await client.research.wait_for_completion(nb_id, research_task_id) except WaitTimeoutError: # was three separate / inconsistent timeout types ...
ResearchError/ResearchTimeoutError. The research domain gained a
catchable base (ResearchError, mirroringSourceError/ArtifactError)
and a domain timeout (ResearchTimeoutError).ResearchAPI.wait_for_completion
previously raised the bare built-inTimeoutError; it now raises
ResearchTimeoutError, aWaitTimeoutError(and therefore still a
TimeoutError), exposingnotebook_id/task_id/timeout/
timeout_seconds/last_status. (ResearchTaskMismatchErrorstays a
ValidationError— it is caller-input validation, not a wait timeout.)
Changed / Deprecated
ArtifactTimeoutErrornow declares its bases umbrella-first
(WaitTimeoutError, ArtifactError), matchingSourceTimeoutErrorand
ResearchTimeoutError. This is a cosmetic reorder with no behavior change:
isinstance/exceptagainst either base is unaffected.client.mind_mapsmutation sites now raiseMindMapNotFoundErrorinstead of
a bareValueErroron a missing target, so callers canexcept NotFoundError
(orexcept MindMapError) uniformly across namespaces.rename(and the
underlying note-backedrename_mind_map) raise it;MindMapNotFoundError
multi-inheritsValueError's siblingNotFoundError, notValueError
itself, so existingexcept ValueErrorrename callers must switch to
except NotFoundError/except MindMapNotFoundError.delete(kind=None)is
now idempotent — deleting an already-absent mind map returnsNonerather
than raising (matchingsources/artifacts/notesdelete, and the
kind-supplied path).get_treereturnsNonefor a missing mind map (it is
a derived read that does not police parent existence) — previouslykind=None
raised on an unknown id. Shape-drift in the interactive payload still raises
UnknownRPCMethodError(ADR-0019; issues #1291, #1346).client.mind_maps.generate(kind=MindMapKind.INTERACTIVE)now raises
ArtifactFeatureUnavailableError(instead of a bareArtifactError) when the
CREATE_ARTIFACTcall returns no artifact id — no generation task was
created. Non-breaking forexcept ArtifactError:
ArtifactFeatureUnavailableErroris a subclass ofArtifactError, so that
catch still works. (It also multi-inheritsRPCError, so a handler that does
except RPCErrorbeforeexcept ArtifactErrorwill now take theRPCError
branch — the same MRO the siblinggenerate_*/retry_failednull-create
paths already produce.) This aligns the interactive async kickoff with that
sibling null-create contract (ADR-0019 "async kickoff"; issue #1359).- Documented two pre-existing
client.mind_mapsread semantics (docs-only, no
behavior change):list()populatesMindMap.treeonly for note-backed
entries — interactive entries carrytree=None("not fetched", not "empty";
callget_tree(..., kind=MindMapKind.INTERACTIVE)to fetch one); and the explicit
get_tree(..., kind=MindMapKind.INTERACTIVE)path delegates absence detection to the RPC,
so a missing id's value is server-dependent (returnsNonetoday) rather than
enforced client-side (issues #1355, #1359). ResearchAPI.wait_for_completion(interval=...)→initial_interval=....
The research waiter's poll-cadence keyword is nowinitial_interval,
matchingSourcesAPI.wait_until_readyand
ArtifactsAPI.wait_for_completion. The oldinterval=keyword still works
as a deprecated alias (warns in 0.7.0, removed in v0.8.0): passing a
non-default value emits aDeprecationWarning(suppressible with
NOTEBOOKLM_QUIET_DEPRECATIONS=1), and passing bothintervaland
initial_intervalraisesTypeError. Default-shape calls stay silent and
the signature is otherwise unchanged, so the public-API compatibility audit
stays clean. Seedocs/deprecations.mdfor the
migration.Decision —
wait_timeoutkept. Thewait_timeoutkeyword on the
SourcesAPI.add_*family was deliberately not renamed totimeout:
on those add methodstimeoutwould be ambiguous with a per-request HTTP
timeout, whereas the dedicated waiter methods already spell their budget
timeout. The researchinterval→initial_intervalrename was the only
standardization with a clear, unambiguous win.
Deprecated
Every deprecation below is on a compatibility runway to v0.8.0. The
consolidated Upgrading to v0.8.0 guide is the
single reference for moving your code across the boundary; set
NOTEBOOKLM_FUTURE_ERRORS=1to exercise the v0.8.0 behavior in your tests
today.
client.mind_maps.get()returningNonefor a missing mind map is now
deprecated, closing the runway gap that leftmind_mapsas the only
#1247-cohort namespace without one. It now emits aDeprecationWarningon a
miss while still returningNone(behavior unchanged this release),
matchingsources.get()/artifacts.get()/notes.get(). In v0.8.0 it
will instead raiseMindMapNotFoundError. Useget_or_none()for the
sanctioned optional lookup (it stays silent), or migrate theNone-check to a
try/except MindMapNotFoundError. The warning fires only on a miss; suppress
it withNOTEBOOKLM_QUIET_DEPRECATIONS=1. Tracking issue: #1247 (gap: #1358).
Seedocs/deprecations.md.sources.get()/artifacts.get()/notes.get()returningNonefor a
missing entity is deprecated. These three methods now emit a
DeprecationWarningon a miss while still returningNone(behavior is
unchanged this release). In v0.8.0 they will instead raise the
matching*NotFoundError(SourceNotFoundError/ArtifactNotFoundError/
NoteNotFoundError), unifying the not-found contract withnotebooks.get(),
which already raisesNotebookNotFoundError. Tracking issue: #1247.The warning fires only on a miss; successful lookups stay silent. Suppress it# Migrate the None-check to a try/except before v0.8.0: # BEFORE (deprecated) src = await client.sources.get(nb_id, source_id) if src is None: ... # AFTER try: src = await client.sources.get(nb_id, source_id) except SourceNotFoundError: ...
withNOTEBOOKLM_QUIET_DEPRECATIONS=1. See
docs/deprecations.md.- Dict-subscript access on the new typed research / mind-map / guide
returns is deprecated. Now thatresearch.poll/research.start/
research.wait_for_completion,artifacts.generate_mind_map, and
sources.get_guidereturn typed dataclasses (see Added), the legacy
result["status"]dict-subscript access emits aDeprecationWarningand
will be removed in v0.8.0. Migrate to attribute access (result.status).
The silentresult.get(...)/result.keys()/"x" in resultmapping
shims also disappear in v0.8.0. Suppress the warning with
NOTEBOOKLM_QUIET_DEPRECATIONS=1. See
docs/deprecations.md.# BEFORE (still works in 0.7.0, warns on subscript) if result["status"] == "completed": sources = result["sources"] # AFTER if result.status == "completed": sources = result.sources
Fixed
- CLI now emits the
NOT_FOUNDerror envelope for the*NotFoundError
family from the centralized handler, instead of the generic
NOTEBOOKLM_ERROR. AnyNotebookNotFoundError/SourceNotFoundError/
ArtifactNotFoundError/NoteNotFoundError/MindMapNotFoundErrorthat
reachescli/error_handler.py(e.g.notebooks.get()on a missing notebook,
or arenamewhose target was deleted mid-operation) now exits1with the
typed{"error": true, "code": "NOT_FOUND", ...}JSON envelope carrying the
missing resource id — matching the per-commandsource/artifact/
note getconvention (the documented CLI not-found contract since v0.5.0).
The per-commandgetpaths already usedget_or_noneand are unaffected.
This also makes theNOTEBOOKLM_FUTURE_ERRORS=1preview faithful at the CLI
boundary, pre-positioning it for the v0.8.0get()→ raise / mutate-existing
fail-loud flips (issues #1364, #1247, #1362). Source.from_api_responsenow reports the real processingstatus. The
ADD_SOURCE/ rename parsing path previously never read the status block and
always fell back toSourceStatus.READY, whileclient.sources.list()/
get()and the source poller read the decoded status. Both parsers now
funnel through a singleSource.from_rowconstruction site, so aSource
produced from an add/rename response carries the samestatus(andurl/
created_at) as the listing path. TheSource.statusfield annotation was
also corrected frominttoSourceStatus(still anint-compatible enum).