ScreenshotNeo

BlogHow-to

How to Monitor YouTube Channels for New Videos and Changes

Use YouTube notifications for a few channels, or poll the Data API to track uploads and compare saved video metadata for changes.

By the ScreenshotNeo team4 October 202612 min read

Direct answer: For a few channels, subscribe, check your Subscriptions feed, and set the bell to All. For a repeatable watchlist, poll each channel’s uploads playlist through the YouTube Data API and compare the returned video IDs with IDs you saved earlier. To detect title or description changes, save metadata snapshots and compare later reads. YouTube documents the fields you can retrieve, but this is your own polling-and-diff system—not a built-in change-alert feed.

Choose a monitoring method

Method Best for What it provides Limit
Subscriptions feed and notifications A handful of channels and personal viewing YouTube’s feed for recent uploads and optional alerts Alerts are personalized and may be limited or omitted.
YouTube Data API polling A larger public-channel watchlist, automation, or a saved record Upload IDs and the metadata returned at each check You must schedule requests, store a baseline, and compare results. Polling does not reveal every change between checks.

Start with the built-in feed if you do not need a record or automation. Choose API polling when you need a repeatable list, your own delivery schedule, or to compare metadata over time.

Use YouTube’s subscriptions and notifications

  1. Subscribe to each channel while signed in to YouTube.
  2. Open the Subscriptions feed to check recent uploads.
  3. For a channel’s alerts, select its notification bell and choose All if you want more alerts than the default Personalized setting.
  4. Check your YouTube notification settings and the device or inbox where you expect alerts to arrive.

Notifications are not a complete upload log. YouTube says not every new upload results in a notification, notification volume is limited, and channels whose audience is set as Made for Kids do not send notifications. YouTube Help explains: “Limiting notifications to 3 new video notifications per channel per 24-hour period helps keep viewers active on YouTube long-term.” This is YouTube’s stated limit, not a promise that you will receive three notifications. See Manage YouTube notifications and Fix subscriber notification problems.

Monitor uploads with the YouTube Data API

The API workflow is: look up each channel’s uploads playlist ID, list items in that playlist, and compare the returned video IDs with the IDs you have already recorded. Google documents the channel’s contentDetails.relatedPlaylists.uploads field and the playlistItems.list method for retrieving playlist entries. Your saved-ID store and polling schedule are implementation choices, not an API notification service. See Google’s YouTube Data API reference, channels.list, and playlistItems.list.

1. Enable the API and prepare a channel list

  1. Create or select a Google Cloud project and enable the YouTube Data API v3.
  2. Create an API key and keep it out of public source code and client-side applications.
  3. Collect the channel IDs to monitor. This example accepts channel IDs directly, so you do not need to search for channels on every polling run.
  4. Store the key in an environment variable named YOUTUBE_API_KEY.

Channel IDs are not the same as handles or display names. If you only have a handle, resolve it to a channel ID through a documented API method before using the script. Do not assume a display name is unique.

2. Poll uploads and save a baseline in Python

This runnable example uses only Python’s standard library. It checks a set of channel IDs, stores seen video IDs and selected metadata in a local JSON file, prints newly observed uploads and changed titles or descriptions, then updates the baseline. The first run establishes a baseline and reports existing playlist entries as new observations; review the output or seed the state file first if you want to suppress that initial report.

import json
import os
import sys
import time
from pathlib import Path
from urllib.error import HTTPError, URLError
from urllib.parse import urlencode
from urllib.request import urlopen

API_KEY = os.environ.get("YOUTUBE_API_KEY")
CHANNEL_IDS = ["UCxxxxxxxxxxxxxxxxxxxxxx"]  # Replace with channel IDs.
STATE_PATH = Path("youtube-monitor-state.json")
MAX_RESULTS = 50

if not API_KEY:
    sys.exit("Set the YOUTUBE_API_KEY environment variable first.")


def api_get(resource, params):
    query = urlencode({**params, "key": API_KEY})
    url = f"https://www.googleapis.com/youtube/v3/{resource}?{query}"
    try:
        with urlopen(url, timeout=30) as response:
            return json.load(response)
    except HTTPError as exc:
        details = exc.read().decode("utf-8", errors="replace")
        raise RuntimeError(f"YouTube API returned HTTP {exc.code}: {details}") from exc
    except URLError as exc:
        raise RuntimeError(f"Network error calling YouTube API: {exc}") from exc


def load_state():
    if not STATE_PATH.exists():
        return {"channels": {}}
    return json.loads(STATE_PATH.read_text(encoding="utf-8"))


def save_state(state):
    temp_path = STATE_PATH.with_suffix(".tmp")
    temp_path.write_text(json.dumps(state, indent=2, sort_keys=True), encoding="utf-8")
    temp_path.replace(STATE_PATH)


def get_uploads_playlist(channel_id):
    result = api_get("channels", {
        "part": "contentDetails,snippet",
        "id": channel_id,
    })
    items = result.get("items", [])
    if not items:
        raise RuntimeError(f"No channel found for ID {channel_id!r}")
    channel = items[0]
    playlist_id = channel["contentDetails"]["relatedPlaylists"]["uploads"]
    return playlist_id, channel.get("snippet", {}).get("title", channel_id)


def get_recent_videos(playlist_id):
    result = api_get("playlistItems", {
        "part": "snippet,contentDetails",
        "playlistId": playlist_id,
        "maxResults": MAX_RESULTS,
    })
    videos = {}
    for item in result.get("items", []):
        video_id = item.get("contentDetails", {}).get("videoId")
        snippet = item.get("snippet", {})
        if not video_id:
            continue
        videos[video_id] = {
            "title": snippet.get("title", ""),
            "description": snippet.get("description", ""),
            "publishedAt": snippet.get("publishedAt", ""),
        }
    return videos


def main():
    state = load_state()
    stored_channels = state.setdefault("channels", {})
    for channel_id in CHANNEL_IDS:
        playlist_id, channel_title = get_uploads_playlist(channel_id)
        current = get_recent_videos(playlist_id)
        previous = stored_channels.get(channel_id, {}).get("videos", {})
        print(f"\\n{channel_title} ({channel_id})")

        new_ids = current.keys() - previous.keys()
        changed_ids = current.keys() & previous.keys()
        if not new_ids and not changed_ids:
            print("  No new uploads or observed metadata changes.")
        for video_id in sorted(new_ids):
            print(f"  NEW https://www.youtube.com/watch?v={video_id}")
            print(f"      {current[video_id]['title']}")
        for video_id in sorted(changed_ids):
            before, after = previous[video_id], current[video_id]
            changed_fields = [
                field for field in ("title", "description")
                if before.get(field) != after.get(field)
            ]
            if changed_fields:
                print(f"  CHANGED {video_id}: {', '.join(changed_fields)}")
                print(f"      https://www.youtube.com/watch?v={video_id}")

        stored_channels[channel_id] = {
            "channelTitle": channel_title,
            "uploadsPlaylistId": playlist_id,
            "checkedAt": int(time.time()),
            "videos": current,
        }
        # Space requests across channels instead of firing a burst.
        time.sleep(0.1)

    save_state(state)


if __name__ == "__main__":
    main()

Save it as monitor_youtube.py, replace the example channel ID, then run YOUTUBE_API_KEY=your_key python3 monitor_youtube.py. On Windows PowerShell, set the variable with $env:YOUTUBE_API_KEY="your_key" before running python monitor_youtube.py. Run it again later to compare against the saved file.

3. Inspect a channel’s uploads with cURL

These commands show the two API calls for one channel. Replace the placeholders. The first response includes the uploads playlist ID; use it as PLAYLIST_ID in the second command.

curl -G 'https://www.googleapis.com/youtube/v3/channels' \\
  --data-urlencode 'part=contentDetails,snippet' \\
  --data-urlencode 'id=CHANNEL_ID' \\
  --data-urlencode 'key=YOUR_API_KEY'

curl -G 'https://www.googleapis.com/youtube/v3/playlistItems' \\
  --data-urlencode 'part=snippet,contentDetails' \\
  --data-urlencode 'playlistId=PLAYLIST_ID' \\
  --data-urlencode 'maxResults=50' \\
  --data-urlencode 'key=YOUR_API_KEY'

cURL retrieves data but does not keep a baseline or detect changes by itself. Save the relevant IDs and fields somewhere persistent, then compare later responses. Avoid putting a real API key into shell history or logs on shared machines.

4. Retrieve uploads with Node.js

This Node.js example uses built-in fetch. It prints the recent playlist items for a channel; add persistent storage and an ID/metadata comparison if you want it to monitor across runs.

const apiKey = process.env.YOUTUBE_API_KEY;
const channelId = 'UCxxxxxxxxxxxxxxxxxxxxxx'; // Replace with a channel ID.

if (!apiKey) throw new Error('Set YOUTUBE_API_KEY first.');

async function apiGet(resource, params) {
  const query = new URLSearchParams({ ...params, key: apiKey });
  const response = await fetch(`https://www.googleapis.com/youtube/v3/${resource}?${query}`);
  const body = await response.json();
  if (!response.ok) {
    throw new Error(`YouTube API ${response.status}: ${JSON.stringify(body)}`);
  }
  return body;
}

const channelData = await apiGet('channels', {
  part: 'contentDetails,snippet',
  id: channelId,
});
const channel = channelData.items?.[0];
if (!channel) throw new Error(`No channel found for ${channelId}`);
const playlistId = channel.contentDetails.relatedPlaylists.uploads;

const uploads = await apiGet('playlistItems', {
  part: 'snippet,contentDetails',
  playlistId,
  maxResults: '50',
});
for (const item of uploads.items ?? []) {
  const videoId = item.contentDetails?.videoId;
  if (!videoId) continue;
  console.log(JSON.stringify({
    videoId,
    title: item.snippet?.title,
    description: item.snippet?.description,
    publishedAt: item.snippet?.publishedAt,
    url: `https://www.youtube.com/watch?v=${videoId}`,
  }));
}

For Node.js versions where fetch is unavailable, use a supported Node.js release or provide a fetch implementation. The API key must remain on the server side.

Track title and description changes

Playlist items are useful for discovering uploads. For selected video metadata, call videos.list with the video IDs and request the snippet part. Google documents snippet fields such as channelId, title, description, tags, and categoryId. Store only the fields relevant to your purpose, along with the video ID and time observed, then compare each new response with your saved snapshot. See videos.list.

curl -G 'https://www.googleapis.com/youtube/v3/videos' \\
  --data-urlencode 'part=snippet' \\
  --data-urlencode 'id=VIDEO_ID_1,VIDEO_ID_2' \\
  --data-urlencode 'key=YOUR_API_KEY'

Change detection here is an inference from the API’s returned fields: the documentation describes retrieval, not a general change-alert stream or a complete revision history. A poll can tell you that a returned value differs from the previous snapshot you saved. It cannot tell you when the edit occurred, recover intermediate versions, or detect a change that happens and is reverted between checks. A missing video in a response also should not automatically be treated as a deletion; access or visibility may have changed.

Choose a polling schedule and stay within quota

Set a cadence based on how quickly you need to notice uploads and how many channels you monitor. Each check generally needs one channels.list lookup per channel and one playlistItems.list call per channel, plus videos.list calls when you fetch metadata. Cache the uploads playlist ID after resolving it, since it is channel configuration rather than something that needs to be rediscovered every run.

Google’s API overview states a default allocation of 10,000 units per day combined for most endpoints, with separate allocations for search.list and videos.insert. The reference pages list a one-unit quota impact for channels.list and playlistItems.list. These figures were checked on October 4, 2026; check your project’s current quota and the current method references before selecting a schedule, because API details and project allocations can change. See the YouTube Data API overview, channels.list quota details, and playlistItems.list quota details.

For a rough estimate, multiply monitored channels by checks per day and by the calls per channel in your design. For example, one playlist-items request per channel every hour across 100 channels is 2,400 such requests per day. Include channel lookups, metadata requests, retries, and any other API use in your own estimate. This arithmetic is an estimate, not a quota guarantee.

Make the monitor reliable

  • Persist state: save video IDs and snapshots to durable storage so restarts do not reset the baseline.
  • Make updates safely: write a complete new state atomically or use a database transaction, so an interrupted run does not leave a half-written baseline.
  • Deduplicate: use video IDs as stable keys and make downstream alerts idempotent, since repeated polling returns already-seen items.
  • Handle pagination when needed: a playlist response can include a next-page token. If your monitoring window may exceed the first page, follow pagination and stop at IDs already seen. Fetching every historical page on every run wastes requests.
  • Handle retries deliberately: retry transient network errors and server errors with exponential backoff and jitter. Do not loop rapidly on invalid credentials, permission errors, or quota exhaustion.
  • Record check results: keep a timestamp and success or error status per channel. A failed poll is unknown state, not evidence that there were no uploads.
  • Separate discovery from alerts: persist observations first, then send notifications. This reduces the chance that a failed email or webhook causes the monitor to forget an upload.

Or skip the browser setup

If you also need a visual record of a public channel or video page, ScreenshotNeo can capture the page with one API request. It is a screenshot API, not a YouTube upload monitor, so use the Data API workflow above to discover uploads and metadata changes.

ScreenshotNeo removes cookie banners, popups, and chat widgets before the shot. Bot checks, blank pages, and failed loads are never billed. Its MCP server lets AI agents take screenshots. The free plan includes 1,000 screenshots a month with no card; paid plans start at $5 for 3,000. See ScreenshotNeo and the API documentation.

curl -G "https://api.screenshotneo.com/v1/shot" -d access_key=YOUR_API_KEY --data-urlencode url=https://www.youtube.com/@CHANNEL_HANDLE -o channel.webp

Sign up for 1,000 free screenshots a month, with no card required.

Troubleshooting

Symptom Likely cause What to do
No notification arrived The bell is set to Personalized, device delivery is disabled, or YouTube did not send an alert for that upload. Made for Kids channels do not send notifications. Check the Subscriptions feed, set the channel bell to All, and review account and device settings. Do not rely on alerts as a complete log.
API returns an empty channel list The supplied ID is wrong, or a handle/display name was supplied where a channel ID is expected. Resolve the channel to its actual ID and retry. Check that the API request uses the intended channel.
API returns 400 or invalid parameter A required parameter is missing or malformed, such as an invalid part, channel ID, playlist ID, or video ID. Inspect the response body, verify IDs, and compare parameters with the method reference.
API returns 403 The key may be invalid or restricted, the API may not be enabled for the project, or the project may have exhausted quota. Check project setup, key restrictions, API enablement, and quota usage. Correct configuration errors; wait for quota reset or request an appropriate quota change if needed.
New videos are missed The monitor checks only a limited page, state was lost, or the schedule had a long gap. Persist state reliably, implement pagination for the required lookback, and choose a cadence that fits your detection needs. A finite polling window cannot guarantee detection after a long outage unless you fetch far enough back.
Every run reports the same videos as new The state file is not being written, the process uses a different working directory, or the stored IDs are not being loaded. Use an explicit state path, check file permissions and logs, and verify the state is committed after a successful fetch.
Title or description changes are not detected The monitor only fetches playlist items, compares the wrong fields, or does not request video snippets. Fetch the relevant videos.list snippet fields and compare them to persisted snapshots. Changes between polls may be unobservable.
Request times out or returns a server error Network interruption or temporary service trouble. Retry with bounded exponential backoff and jitter. Preserve the prior baseline until a complete successful poll finishes.

FAQ

Does selecting All guarantee a notification for every upload?

No. YouTube says notifications may not be sent for every upload and may be limited. Use the Subscriptions feed or API polling when missing an upload matters.

Can the API tell me exactly when a title changed?

Not from the current metadata response alone. Your monitor can record when it first observed a difference between snapshots, not necessarily when the edit happened.

Can I monitor private videos?

This guide’s API workflow is for publicly retrievable channel and video data. Access to private data depends on authorization and the viewer’s permissions; do not assume an API key grants access to it.

How often should I poll?

Choose the slowest cadence that meets your freshness needs, then estimate request volume and confirm the project’s current quota. More frequent polling uses more quota and still does not provide real-time delivery.