yt_oauth() says so when
it finds one; run yt_oauth(app_id, app_secret) again and
the new token replaces the old file. After that, yt_oauth()
with no arguments reads the cache as before.tools::R_user_dir("tuber", "cache").options(google_token) holds an httr2 token. Code
reading token$credentials$access_token should read
token$access_token.yt_oauth() refuses to write into a file named
.httr-oauth. That name is httr’s shared cache,
read by every httr-based package in the same working directory, and an
httr2 token there would break them.yt_get_key() and yt_set_key() read and set
YOUTUBE_KEY as given. The decrypt and
type arguments and the TUBER_KEY variable are
gone; requests never used the decrypted key. In continuous integration,
supply YOUTUBE_KEY from the secret store..... An argument a function
does not know, such as a misspelled simplfy = FALSE, is now
an error instead of being silently dropped. yt_oauth()
still forwards ... to
httr2::oauth_flow_auth_code().get_channel_details() drops category_id
and max_results. Google retired the guide-category filter
in 2020.post_comment() requires video_id. YouTube
no longer accepts comments on a channel itself.set_comment_moderation_status() takes
comment_ids, since it accepts several.get_all_channel_video_stats() returns
published_at instead of publication_date,
returns its counts as numbers, and keeps its columns when a channel has
no videos.tuber now exports only what needs the YouTube API.
%>% pipe is no longer re-exported. Use R’s
|>, or load magrittr or dplyr yourself.search_short_videos() is removed. Use
yt_search(video_duration = "short"), which is all it did;
it never identified YouTube Shorts.with_retry() is no longer exported.analyze_trends() no longer reports
trending_score, a formula with no source. The summary is
ordered by total_views.Content-Range-tagged chunks; if a chunk dies in flight or
draws a 5xx, tuber asks YouTube how many bytes it kept and continues
from there instead of restarting at byte zero. Tune with the new
chunk_size (default 8 MB) and max_tries
(default 5 consecutive failed chunks) arguments to
upload_video() and upload_caption(). An
expired session URL raises tuber_upload_session_expired;
exhausting the retries raises tuber_upload_interrupted,
whose condition carries the byte count reached.nextPageToken
pointing past items that were never returned, so resuming from it
skipped them, and get_all_channel_video_stats() could drop
videos whenever YouTube returned a short page.get_all_comments() reports comments as disabled only
when YouTube says so. A project without the YouTube Data API enabled was
reported as “comments disabled” and returned an empty data frame; it is
now an error.reason, and
a 403 whose body is not Google’s usual error object no longer fails with
an unrelated R error. A quota error (tuber_quota_exhausted)
is now also a tuber_http_error and carries
reason and status_code like the rest.get_all_channel_video_stats() no longer rewraps a
failed channel lookup as tuber_channel_info_error; the
original error, such as tuber_quota_exhausted, reaches the
caller.list_comments(comment_ids = ) returns every comment
asked for. It capped the result at max_results, which the
documentation says is ignored there.update_video_metadata() no longer resends a scheduled
publish time when it makes a video public or unlisted. YouTube accepts
publishAt only on a private video.update_video_metadata(tags = "one") sends a JSON array
rather than a bare string.read_sbv() reads captions that span several lines and
keeps them in file order.analyze_channel(include_comments = TRUE) no longer
fails when an upload is private or deleted.yt_search() accepts any RFC 3339 date-time, including
UTC offsets, and no longer rejects valid times that fall in the local
daylight-saving gap. It rejects event_type and
location unless type = "video", as YouTube
requires. Its documentation now says that
relevance_language defaults to "en" and that
NULL searches without a language preference.list_channel_members() and
list_super_chat_events() recognize a refused request by its
HTTP status rather than by the word “forbidden” in the message, and keep
YouTube’s error as the parent of the one they raise.upload_video() matches privacyStatus and
license exactly instead of accepting abbreviations.list_channel_videos() reports why a channel could not
be resolved (tuber_channel_not_found,
tuber_autogenerated_channel,
tuber_no_uploads_playlist) instead of a generic resolution
error.liveChatMessages.list five units.set_comment_moderation_status() and other endpoints
returning HTTP 204 no longer attempt to parse an absent response
body.Released 2026-08-17.
list_*() prefix.
The renamed functions are list_comments(),
list_comment_threads(), list_playlist_items(),
list_playlists(), list_subscriptions(),
list_live_chat_messages(), and
list_super_chat_events().get_video_stats(),
get_channel_details(), list_popular_videos(),
list_video_categories(), list_languages(), and
download_caption().video_id, comment_id, and
playlist_item_id. Plural ID arguments accept vectors.auth choice.simplify = FALSE to retain the collected API response.
get_video_details() remains dynamic because its columns
depend on part.See vignette("api-conventions", package = "tuber") for
the full migration table and endpoint support matrix.
max_results as a total limit and
trims the first page as well as later pages.list_videocats() and list_guidecats()
appended the translated filter to the query list as an unnamed element,
so the query contained an unnamed component. Both httr and
httr2 reject such a query (“All components of query must be
named”), so neither function could issue a request at all. The filter is
now merged by name, and regionCode/id reach
the API.
Note for list_guidecats() specifically: Google
deprecated the guideCategories endpoint in September 2020
and it no longer returns data. The request is now assembled correctly,
but the method it targets is gone, so this repairs the client rather
than restoring the feature.
get_live_streams() accepted channel_id
and required either that or stream_id, then sent
channelId alongside broadcastStatus.
liveBroadcasts.list has no channelId parameter
and accepts exactly one of broadcastStatus, id
or mine, so a status-only query was impossible and the
combinations it did send were invalid. It now takes exactly one filter,
gains mine, and errors clearly on the rest.
get_comments(simplify = TRUE) assigned the first
comment’s id to every row, because raw_res$items[[1]]$id is
a scalar that R recycled over the frame. Two comments came back carrying
the same id and the second was lost. It also sent pageToken
and maxResults alongside a comment_id filter,
which comments.list documents as unsupported with
id.
list_videocats() indexed its filter with
filter$regionCode. filter is a named character
vector, so this raised “$ operator is invalid for atomic vectors” on
every call. list_guidecats() failed the same way when
assembling its result: with no items it raised “replacement has 1 row,
data has 0”, and with items it returned a list rather than the
documented data.frame. Both now index by name and return a
data.frame in every case.
get_comments() documented and validated a
page_token argument but never placed it in the query, so
paging through replies with a parent_id filter always
returned the first page. It is now sent as
pageToken.
list_regions() and
list_abuse_report_reasons() documented and validated an
hl argument that never reached the query. Both
i18nRegions.list and
videoAbuseReportReasons.list support hl, and
the sibling list_langs() already sent it. It is now
sent.
get_live_streams() sent its status
filter as eventType, which is a search.list
parameter. liveBroadcasts.list filters on
broadcastStatus.
This release significantly expands tuber’s capabilities
to support full programmatic channel management and deep creator economy
analytics:
Added functions to actively manage and engage with your community: *
post_comment(): Programmatically post new top-level
comments to videos or channels. * reply_to_comment(): Reply
to existing comments. * set_comment_moderation_status():
Approve, reject, or hold comments for review, with an option to ban
authors.
Added highly requested data extraction tools for analysts tracking
the creator economy: * get_super_chat_events(): Retrieve
all Super Chat and Super Sticker events, including currency amounts and
donor details. * get_live_chat_messages(): Extract
real-time chat messages from active live broadcasts. *
list_channel_members(): List all active channel members and
their tier levels.
Completed the video publishing pipeline with new media upload
endpoints: * set_video_thumbnail(): Upload and set custom
thumbnails for your videos. * insert_channel_banner():
Upload custom channel banner art.
This release resolves 19 out of 22 total GitHub issues, dramatically improving package reliability and user experience:
unicode_utils.R (#79)get_stats() function (#77)get_video_details() (#87)Impact: Package reliability increased from ~60% to ~95% with most critical user-reported issues resolved.
This version was prepared but incorporated into 1.4.0 before final release.
This release focuses on core infrastructure improvements, performance optimizations, and reliability enhancements:
auth = "token") and API key
(auth = "key") authentication across all functionsget_videos_batch(): Process up to 50 videos per API
call (50x more efficient)get_channels_batch(): Bulk channel statistics
retrievalget_playlists_batch(): Efficient playlist information
fetchinganalyze_channel(): Comprehensive channel analysiscompare_channels(): Multi-channel comparisonbulk_video_analysis(): Performance benchmarking across
video setsget_live_streams(): Live broadcast and premiere
informationget_video_thumbnails(): Thumbnail URLs and
metadataget_channel_sections(): Channel sections and featured
contentsearch_shorts(): YouTube Shorts-specific searchget_premiere_info(): Premiere scheduling
information.lintr configuration
enabling automated code quality checksdigest package for
caching functionalityThis patch release addresses several critical bugs and improves documentation:
get_comment_threads() pagination bug where comments were
duplicated instead of fetching unique results when
max_results > 100 (issue #107)get_playlist_items() returning only metadata instead of
actual video items (issue #95)list_channel_videos() returning only pageInfo instead of
video details (issue #88)yt_search() instead (issue #118)get_video_details()
including Shiny application usage (issue #87)get_playlist_items()yt_search.R approachThis release fixes critical issues that were causing the package to be unreliable for production use:
get_all_comments() - now scales linearly (issues #62, #111,
#115)list_channel_resources() with retry logic (issue #73)yt_get_quota_usage(),
yt_set_quota_limit(), yt_reset_quota()
functionsyt_search() to
stop fetching when max_results reached (issues #66, #77)get_all_channel_video_stats() to use batch requests (50x
fewer API calls)get_all_comments(): ~100x faster for videos with many
commentsyt_search(): ~50% quota savings by precise result
fetchingget_all_channel_video_stats(): 50x fewer API calls
through batchinglist_channel_resources(): ~95% reliability improvement
with retry logicyt_get_key() and yt_set_key(), and
a new auth argument to the internal function
tuber_GET(). Users can now pass auth = "key"
to any function that uses tuber_GET() to enable API key
authentication. The behavior of tuber_GET() remains
unchanged when using the new default, auth = "token", which
avoids breaking changes to previously written code (@gvelasq, #117).fixes list_channel_videos. it used to iterate over all the playlists. You don’t need to do that. All the uploaded videos of a channel are available in a playlist with the same id as channel id except the first two letters are switched.
fixes get_playlist_items. thanks to @TebanSierra. See #39.