Track Competitor YouTube Channels via API
David Boulen · 7/29/2026 · 8 min read
Track Competitor YouTube Channels via API
Knowing what your competitors publish on YouTube, and when they publish it, is straightforward to automate with the right API. This guide walks through a complete pipeline: resolving channel handles, listing uploads on a schedule, detecting new videos, enriching them with transcripts, and storing results for dashboards.
Why Automate Competitor Channel Monitoring
Manual monitoring does not scale. If you follow ten competitor channels, checking each one daily takes time and produces inconsistent results. More importantly, the raw upload list is not the intelligence you actually want. You want to know: did they publish a video on a keyword you're targeting? Did they shift to a new content cadence? What topics are getting traction based on their titles?
Automating the feed layer with a youtube channel videos api lets you answer those questions programmatically. The transcript layer turns video metadata into keyword-searchable text. Combined, they give you a repeatable signal you can route into dashboards, Slack alerts, or a database.
Step 1: Resolve Channel Handles to Channel IDs
YouTube channels are identified internally by IDs like UCxxxxxxxxxxxxxx, but public handles use the @username format. Before you can list uploads, you need the canonical channel ID.
The /api/v2/channel-resolve endpoint accepts any handle, URL, or existing ID and returns the channel ID. It is free and does not deduct credits.
curl "https://getyoutubetranscriber.com/api/v2/channel-resolve?handle=@mkbhd" \
-H "Authorization: Bearer YOUR_API_KEY"
Example response:
{
"channel_id": "UCBcRF18a7Qf58cCRy5xuWwQ"
}
Do this once per competitor and store the IDs. You only need to re-resolve if a channel changes its handle, which is rare.
import httpx
API_KEY = "YOUR_API_KEY"
BASE = "https://getyoutubetranscriber.com/api/v2"
COMPETITORS = [
"@mkbhd",
"@linustechtips",
"@fireship",
]
def resolve_handles(handles: list[str]) -> dict[str, str]:
ids = {}
for handle in handles:
r = httpx.get(
f"{BASE}/channel-resolve",
params={"handle": handle},
headers={"Authorization": f"Bearer {API_KEY}"},
)
r.raise_for_status()
ids[handle] = r.json()["channel_id"]
return ids
channel_ids = resolve_handles(COMPETITORS)
# {"@mkbhd": "UCBcRF18a7Qf58cCRy5xuWwQ", ...}
Step 2: List Recent Uploads on a Schedule
With channel IDs in hand, you can pull the upload list for each channel. The /api/v2/channel/videos endpoint returns a paginated list sorted newest first.
curl "https://getyoutubetranscriber.com/api/v2/channel/videos?channel=UCBcRF18a7Qf58cCRy5xuWwQ" \
-H "Authorization: Bearer YOUR_API_KEY"
For competitive monitoring you generally only need the first page: it contains the most recent uploads, and you care about detecting new ones since your last poll, not re-scanning the entire back catalog.
def list_recent_videos(channel_id: str, max_pages: int = 1) -> list[dict]:
videos = []
params = {"channel": channel_id}
for _ in range(max_pages):
r = httpx.get(
f"{BASE}/channel/videos",
params=params,
headers={"Authorization": f"Bearer {API_KEY}"},
)
r.raise_for_status()
data = r.json()
videos.extend(data.get("videos", []))
continuation = data.get("continuation")
if not continuation:
break
params = {"continuation": continuation}
return videos
Each video object in the response includes the video ID, title, published date, and other metadata. Store at minimum the video ID and title for your diff step.

Step 3: Diff Upload Lists to Detect New Videos
The core of any polling-based monitor is the diff: compare what you saw last time against what you see now. Any video ID in the current list but absent from your stored snapshot is a new upload.
import json
import pathlib
SNAPSHOT_DIR = pathlib.Path("snapshots")
SNAPSHOT_DIR.mkdir(exist_ok=True)
def load_snapshot(channel_id: str) -> set[str]:
path = SNAPSHOT_DIR / f"{channel_id}.json"
if not path.exists():
return set()
return set(json.loads(path.read_text()))
def save_snapshot(channel_id: str, video_ids: set[str]) -> None:
path = SNAPSHOT_DIR / f"{channel_id}.json"
path.write_text(json.dumps(list(video_ids)))
def find_new_videos(channel_id: str, current_videos: list[dict]) -> list[dict]:
current_ids = {v["video_id"] for v in current_videos}
previous_ids = load_snapshot(channel_id)
new_ids = current_ids - previous_ids
save_snapshot(channel_id, current_ids)
return [v for v in current_videos if v["video_id"] in new_ids]
On the first run, the snapshot is empty, so every video in the first page looks "new." You may want to seed the snapshot on initialization without processing transcripts, then start enriching on the second run onward.
For this type of diffing pattern at scale, the YouTube Transcript API: Designing a Resilient ETL Pipeline post covers durable queue patterns worth combining with the approach above.
Step 4: Enrich New Videos with Transcripts for Keyword Tracking
Once you have a list of genuinely new videos, fetch transcripts to make them searchable. The /api/v2/transcript endpoint takes a video ID and returns the full transcript as structured JSON.
def fetch_transcript(video_id: str, language: str = "en") -> list[dict]:
r = httpx.get(
f"{BASE}/transcript",
params={"video_url": video_id, "language": language},
headers={"Authorization": f"Bearer {API_KEY}"},
)
r.raise_for_status()
return r.json().get("transcript", [])
The response is a list of segment objects with text, start, and duration fields. Joining the text fields gives you a plain-text document you can run keyword searches against.
def extract_text(transcript: list[dict]) -> str:
return " ".join(seg["text"] for seg in transcript)
def check_keywords(text: str, keywords: list[str]) -> list[str]:
text_lower = text.lower()
return [kw for kw in keywords if kw.lower() in text_lower]
If a competitor mentions a keyword you're targeting, you can flag it immediately. This is how you move from "they published a video" to "they published a video about our core topic."
For batches of new videos, consider the /api/v2/transcripts-bulk endpoint, which accepts up to 50 video IDs per request and reduces round trips. If multiple channels publish on the same day, bulk is more efficient than serial calls.
Step 5: Store Results for Dashboards and Reporting
Monitoring without a persistent store is useful for alerts but not for trends. Store at least the following per video:
- Channel ID and handle
- Video ID, title, and published timestamp
- Detected keywords from the transcript
- Full transcript text (or a vector embedding, if you are building semantic search)
- The timestamp of when your pipeline processed it
A simple schema in SQLite or PostgreSQL covers most use cases:
CREATE TABLE competitor_videos (
id SERIAL PRIMARY KEY,
channel_id TEXT NOT NULL,
video_id TEXT UNIQUE NOT NULL,
title TEXT,
published TIMESTAMPTZ,
keywords TEXT[],
transcript TEXT,
processed TIMESTAMPTZ DEFAULT NOW()
);
With this in place you can query upload frequency per channel, keyword frequency over time, and content theme shifts. Connect it to a lightweight BI tool like Metabase or Grafana and you have a live competitive dashboard with no third-party subscription required.

Step 6: Rate-Limit-Friendly Polling Patterns
Polling every channel every few minutes burns credits and is unnecessary. Most creators publish at most once per day. A 6 to 12 hour interval catches uploads the same day while keeping your credit usage proportional to actual activity.
A few concrete patterns:
Staggered scheduling. If you monitor 20 channels, do not fire all 20 requests simultaneously. Spread them over a window, for example one request every 30 seconds. This avoids burst-rate issues and makes your logs easier to read.
Adaptive frequency. Track each channel's historical posting cadence. A channel that posts three times per week does not need the same polling interval as one that posts daily. Lower-frequency channels can be polled every 24 hours.
Webhook-first when available. Some monitoring platforms support push notifications for new YouTube videos. If your stack already has that layer, use it to trigger on-demand transcript fetches instead of polling. Your /api/v2/channel/videos call then becomes a fallback verification step rather than the primary detection mechanism.
Credit accounting. Each transcript fetch costs one credit. Channel listing and resolution are cheap or free. Budget accordingly: a pipeline covering 10 channels, detecting one new video per channel per day, and fetching one transcript per video costs roughly 10 credits per day. At that scale, the free tier covers initial development comfortably.
For a deeper look at staying within limits as you scale, Understanding YouTube Transcript Rate Limits covers the mechanics in detail.
Putting It All Together
Here is the full polling loop that ties the steps above together:
import time
import schedule
KEYWORDS = ["your-product-name", "competitor-feature", "target-topic"]
def monitor_channel(handle: str, channel_id: str) -> None:
print(f"Polling {handle}...")
videos = list_recent_videos(channel_id)
new_videos = find_new_videos(channel_id, videos)
for video in new_videos:
print(f" New video: {video['title']}")
transcript = fetch_transcript(video["video_id"])
text = extract_text(transcript)
hits = check_keywords(text, KEYWORDS)
store_video(
channel_id=channel_id,
video=video,
keywords=hits,
transcript=text,
)
if hits:
print(f" Keyword hits: {hits}")
send_alert(handle, video["title"], hits)
def run_monitor() -> None:
ids = resolve_handles(COMPETITORS)
for handle, channel_id in ids.items():
monitor_channel(handle, channel_id)
# Run every 8 hours
schedule.every(8).hours.do(run_monitor)
run_monitor() # Run once immediately on startup
while True:
schedule.run_pending()
time.sleep(60)
This is roughly 60 lines of Python and produces a repeatable signal you can route anywhere. The same logic applies in Node.js or Go; the API surface is REST so any HTTP client works.
For content teams who want to go further, transcripts open up a second layer of analysis: topic modeling, sentiment shifts, and semantic similarity between your content and a competitor's. The List All Videos from a YouTube Channel via API post covers deeper pagination strategies when you need historical data beyond the first page.
A Note on Accuracy
No automated transcript contains zero errors. Auto-generated captions have higher error rates on technical jargon and proper nouns. If you are running keyword searches, account for minor misspellings by normalizing text or using fuzzy matching. If the transcript for a given video is unavailable (the channel may have disabled captions), the API returns a clear error you can log and retry later.
YouTube uploads roughly 500 hours of video every minute according to Statista, so at scale the signal-to-noise challenge is real. Staying focused on a defined list of competitor channels, rather than trying to monitor broad keyword searches across all of YouTube, keeps your pipeline tractable and your insights actionable.
Next Steps
Start with a list of three to five competitor handles, run the resolve step, and verify you get valid channel IDs back. Then run a single listing call and inspect the response structure before building out the full polling loop. Once new-video detection is working reliably, add the transcript layer.
The free tier at YouTube Transcriber includes 100 credits with no credit card required, which is enough to build and test this pipeline end to end before committing to a paid plan.