Monitor a YouTube Channel for New Videos via API
David Boulen · 6/27/2026 · 8 min read
Monitor a YouTube Channel for New Videos via API
YouTube uploads more than 500 hours of video every minute. For a specific channel you care about—a competitor, a news source, a creator you're building tooling around—the challenge isn't volume, it's knowing the moment a new video lands so you can act on it: trigger a summary, ingest a transcript, fire a Slack alert, or kick off a content pipeline.
YouTube doesn't offer a push webhook for new uploads on public channels. That means you need a polling approach: periodically check the channel, compare against what you've already seen, and process anything new. This post walks through doing that cleanly using the YouTube Transcriber API, from resolving a channel handle to storing state and chaining actions.
Why This Matters: Real Use Cases
Before diving into code, here are the practical reasons developers build this:
- Auto-summaries: Detect a new upload, pull the transcript, send it to GPT for a summary, post it to Slack or a newsletter.
- Content pipelines: Feed transcripts into a RAG index, vector database, or fine-tuning dataset every time a channel publishes.
- Competitive alerts: Watch a competitor's channel and get notified within minutes of a new drop.
- SEO repurposing: Auto-convert video transcripts into blog drafts whenever a creator you follow publishes.
- Research and journalism: Track channels in a beat (politics, finance, tech) and archive transcripts as they publish.
All of these follow the same pattern: detect → fetch transcript → act.
Step 1: Resolve the Channel Handle to an ID
YouTube channels have stable internal IDs that start with UC (e.g., UCVHdiWBhBFRqMOLJCVTAREA). But humans share channels by handle (@mkbhd), by URL, or by name. You need the canonical ID before you can poll reliably.
The /api/v2/channel-resolve endpoint accepts any of these formats and returns the canonical ID. It's a free endpoint—no credits consumed.
curl:
curl "https://getyoutubetranscriber.com/api/v2/channel-resolve?channel=@mkbhd" \
-H "Authorization: Bearer YOUR_API_KEY"
Example response:
{
"channelId": "UCBcRF18a7Qf58cCRy5xuWwQ",
"handle": "@mkbhd",
"title": "Marques Brownlee"
}
Store this channelId. Every subsequent call uses it. If your users supply channel URLs or handles, run them through this endpoint at setup time and store the resolved ID.
Python helper:
import requests
API_KEY = "YOUR_API_KEY"
BASE_URL = "https://getyoutubetranscriber.com/api/v2"
def resolve_channel(handle_or_url: str) -> str:
resp = requests.get(
f"{BASE_URL}/channel-resolve",
params={"channel": handle_or_url},
headers={"Authorization": f"Bearer {API_KEY}"},
)
resp.raise_for_status()
return resp.json()["channelId"]
Step 2: Fetch the Latest Uploads
With a channel ID in hand, use /api/v2/channel-latest to get the 15 most recent uploads. This endpoint is backed by YouTube's public RSS feed and is free—it doesn't consume credits.
curl "https://getyoutubetranscriber.com/api/v2/channel-latest?channelId=UCBcRF18a7Qf58cCRy5xuWwQ" \
-H "Authorization: Bearer YOUR_API_KEY"
Example response:
{
"channelId": "UCBcRF18a7Qf58cCRy5xuWwQ",
"videos": [
{
"videoId": "dQw4w9WgXcQ",
"title": "New Review: Best Phone of 2025",
"publishedAt": "2025-06-20T14:32:00Z",
"url": "https://www.youtube.com/watch?v=dQw4w9WgXcQ"
},
{
"videoId": "abc123xyz",
"title": "Hands-On: The New MacBook",
"publishedAt": "2025-06-18T10:00:00Z",
"url": "https://www.youtube.com/watch?v=abc123xyz"
}
// ... up to 15 videos
]
}
If you need more than 15 videos—say, for backfilling or initial ingestion—use /api/v2/channel-videos, which returns a fuller first-page list of up to ~100 videos. That endpoint costs credits, so use it selectively.
Step 3: Store State to Avoid Duplicate Processing
The key to a reliable polling loop is idempotent state management. You need to know which videos you've already processed so you don't re-trigger your pipeline on the same video twice.
Don't use timestamps. Publish times can be inconsistent or backfilled. Use video IDs—they're immutable.
Here's a minimal approach using a JSON file as a state store (swap in Redis, SQLite, or a Postgres table for production):
import json
import os
STATE_FILE = "channel_state.json"
def load_seen_ids() -> set:
if not os.path.exists(STATE_FILE):
return set()
with open(STATE_FILE) as f:
return set(json.load(f).get("seen_ids", []))
def save_seen_ids(seen_ids: set):
with open(STATE_FILE, "w") as f:
json.dump({"seen_ids": list(seen_ids)}, f)
Step 4: Build the Polling Loop
Put it all together: resolve the channel once, then poll on a schedule, compare against your seen IDs, and act on anything new.
import requests
import time
API_KEY = "YOUR_API_KEY"
BASE_URL = "https://getyoutubetranscriber.com/api/v2"
CHANNEL_HANDLE = "@mkbhd"
POLL_INTERVAL_SECONDS = 900 # 15 minutes
def get_latest_videos(channel_id: str) -> list:
resp = requests.get(
f"{BASE_URL}/channel-latest",
params={"channelId": channel_id},
headers={"Authorization": f"Bearer {API_KEY}"},
)
resp.raise_for_status()
return resp.json().get("videos", [])
def handle_new_video(video: dict):
video_id = video["videoId"]
print(f"New video detected: {video['title']} ({video_id})")
# Chain your action here:
# - Fetch transcript: GET /api/v2/transcript?videoId=...
# - Send to GPT for summary
# - Post to Slack
# - Insert into your database
def poll_channel(channel_id: str):
seen_ids = load_seen_ids()
videos = get_latest_videos(channel_id)
new_videos = [v for v in videos if v["videoId"] not in seen_ids]
for video in new_videos:
handle_new_video(video)
seen_ids.add(video["videoId"])
save_seen_ids(seen_ids)
if __name__ == "__main__":
channel_id = resolve_channel(CHANNEL_HANDLE)
print(f"Monitoring channel: {channel_id}")
while True:
poll_channel(channel_id)
time.sleep(POLL_INTERVAL_SECONDS)
For production, replace the while True loop with a cron job or a scheduled task in your orchestrator (GitHub Actions, Airflow, AWS EventBridge, etc.). A cron every 15 minutes is clean and predictable.
*/15 * * * * /usr/bin/python3 /opt/monitor/poll.py >> /var/log/channel_monitor.log 2>&1

Step 5: Chain the Transcript for Downstream Actions
Detecting a new video is only step one. The real value is what you do with it. Once you have a videoId, fetch its transcript in a single call:
curl "https://getyoutubetranscriber.com/api/v2/transcript?videoId=dQw4w9WgXcQ&lang=en" \
-H "Authorization: Bearer YOUR_API_KEY"
The response is clean JSON—no VTT parsing, no timing cleanup required. You can pipe it directly into an LLM prompt, a vector store, or a summarization endpoint. For a worked example of building that pipeline, see YouTube Transcript API: Build a Video Summarizer with GPT.
If you're supporting multiple languages, the API covers 125+ languages. Add &lang=es for Spanish, &lang=ja for Japanese, and so on—useful if you're monitoring international channels or building multilingual content tools.
Polling vs. Webhook-Style Architectures
YouTube's public feeds don't support push webhooks for arbitrary channels (the official PubSubHubbub / WebSub support exists but is unreliable and requires a publicly accessible endpoint). In practice, polling is more robust for most use cases.
Here's a quick comparison:
| Approach | Latency | Complexity | Reliability |
|---|---|---|---|
| Poll /channel-latest (this post) | 5–30 min delay | Low | High |
| YouTube PubSubHubbub | Near-real-time | High (requires public webhook endpoint, lease renewal) | Moderate (feed gaps common) |
| YouTube Data API v3 search | Near-real-time | Moderate | High, but 100 quota units per call |
For most content pipelines and alert systems, a 15-minute polling interval is well within acceptable latency. If you need sub-minute detection, consider a hybrid: poll more frequently during business hours and less at night.
If you've previously used the YouTube Data API v3 for this, you'll notice the quota math gets painful fast—search.list costs 100 units per call, and you have 10,000 units per day by default. That's 100 polls per day across your entire project. See YouTube Data API v3 vs a Dedicated Transcript API for a full breakdown of the trade-offs.
Monitoring Multiple Channels
Scaling to dozens or hundreds of channels is straightforward. Store your channel IDs in a list or database and loop over them in each poll run:
CHANNELS = [
"UCBcRF18a7Qf58cCRy5xuWwQ", # MKBHD
"UCXuqSBlHAE6Xw-yeJA0Tunw", # Linus Tech Tips
"UC295-Dw0tDd-NBS0zTyQXug", # 8-Bit Guy
]
for channel_id in CHANNELS:
poll_channel(channel_id)
time.sleep(1) # small delay to avoid burst rate limiting
The YouTube Transcriber API allows up to 300 requests per minute, so you can comfortably monitor 50+ channels in a single cron run that completes in well under a minute.
Keep state per channel (keyed by channelId) so you don't accidentally mark video IDs from one channel as seen for another:
def load_seen_ids(channel_id: str) -> set:
key = f"seen_{channel_id}"
# load from your store using this key

Gotchas and Edge Cases
RSS feed delay: The /channel-latest endpoint is backed by YouTube's RSS feed, which can lag 5–20 minutes behind an actual upload. That's typically fine for content pipelines, but don't use this for anything requiring sub-minute latency.
Live streams and premieres: YouTube may list a live stream or scheduled premiere in the RSS feed before it has a transcript. If your pipeline fetches a transcript immediately on detection, add error handling for the case where the transcript isn't available yet, and retry after a delay.
State file size: Video IDs are 11 characters each. Even if you store 10,000 IDs, that's under 200KB. You don't need to prune aggressively. That said, if you're monitoring hundreds of channels over months, migrate to SQLite or Redis.
Initial backfill: On first run, channel-latest returns 15 videos. If you want to backfill more history, call /channel-videos once at setup time to get the full first-page list, seed your state store with those IDs, and then switch to /channel-latest for ongoing polling.
Detecting Issues with youtube-transcript-api
If you've been relying on the Python youtube-transcript-api library and hitting blocks, rate limits, or empty responses—that's a separate but related problem. The root causes and fixes are covered in detail in Why youtube-transcript-api Gets Blocked (and What to Do). The short answer: YouTube aggressively blocks residential and cloud IPs that scrape transcripts directly, which is why a managed API with rotating proxies and retry logic handles it for you.
Getting Started
Channel monitoring with YouTube Transcriber requires:
- A free account at getyoutubetranscriber.com — 100 free credits, no credit card.
- Your Bearer token from the dashboard.
- The code above, pointed at your channel.
The /channel-resolve and /channel-latest endpoints are free, so your polling loop itself costs nothing. Credits are only consumed when you fetch transcripts or use the paid channel-videos endpoint.
Full endpoint reference, parameter docs, and response schemas are at getyoutubetranscriber.com/docs.