Scenario
YouTube’s Most Popular chart reveals what the platform is amplifying right now — but your brand isn’t on it. This job pulls the top 50 trending videos viavideos.list?chart=mostPopular, extracts their categories, tags, and descriptions, then sends the full dataset to Mave with your brand context. Mave identifies content themes that are trending but absent from your channel — gaps your brand could own with timely content. The result is a content gap report with specific video concepts ranked by opportunity size.
Architecture
Code
import os, requests
YT = os.environ["YOUTUBE_API_KEY"]
MV = os.environ["MAVERA_API_KEY"]
YT_BASE = "https://www.googleapis.com/youtube/v3"
MV_BASE = "https://app.mavera.io/api/v1"
MV_H = {"Authorization": f"Bearer {MV}", "Content-Type": "application/json"}
BRAND_CONTEXT = """
Brand: [Your Brand] — DTC fitness/wellness company
Channel focus: Workout tutorials, nutrition science, product reviews
Audience: 18-35, health-conscious, 60% female, US-based
Current content gaps we suspect: trending challenges, reaction content, collaborations
"""
# 1. Fetch top 50 trending videos (1 quota unit per call)
trending = requests.get(f"{YT_BASE}/videos", params={
"key": YT, "chart": "mostPopular",
"part": "snippet,contentDetails,statistics,topicDetails",
"maxResults": 50, "regionCode": "US",
}).json()
if "error" in trending:
raise SystemExit(f"YouTube API error: {trending['error']['message']}")
videos = []
category_counts = {}
all_tags = []
for item in trending.get("items", []):
snippet = item["snippet"]
stats = item.get("statistics", {})
category = snippet.get("categoryId", "0")
tags = snippet.get("tags", [])
category_counts[category] = category_counts.get(category, 0) + 1
all_tags.extend(tags[:10])
videos.append({
"title": snippet["title"],
"channel": snippet["channelTitle"],
"category": category,
"tags": tags[:10],
"views": int(stats.get("viewCount", 0)),
"likes": int(stats.get("likeCount", 0)),
"description": snippet.get("description", "")[:200],
})
# 2. Resolve category IDs to names
cat_ids = ",".join(set(category_counts.keys()))
categories = requests.get(f"{YT_BASE}/videoCategories", params={
"key": YT, "id": cat_ids, "part": "snippet",
}).json()
cat_map = {
item["id"]: item["snippet"]["title"]
for item in categories.get("items", [])
}
# 3. Build trending summary
trending_block = "\n".join(
f"- \"{v['title']}\" by {v['channel']} | Category: {cat_map.get(v['category'], v['category'])} | "
f"Views: {v['views']:,} | Tags: {', '.join(v['tags'][:5])}"
for v in sorted(videos, key=lambda x: -x["views"])[:30]
)
category_summary = "\n".join(
f"- {cat_map.get(cid, cid)}: {count} videos"
for cid, count in sorted(category_counts.items(), key=lambda x: -x[1])
)
# 4. Gap analysis via Mave
gap = requests.post(f"{MV_BASE}/mave/chat", headers=MV_H, json={
"message": f"""Identify content gaps between what's trending on YouTube and what our brand covers.
BRAND CONTEXT:
{BRAND_CONTEXT}
TRENDING NOW (top 30 by views):
{trending_block}
CATEGORY DISTRIBUTION:
{category_summary}
TOP TRENDING TAGS: {', '.join(set(all_tags)[:30])}
Produce:
1. **Trending themes** our brand is NOT covering (ranked by opportunity size)
2. **Specific video concepts** for each gap (title, format, estimated audience size)
3. **Cross-category opportunities** — trends in non-fitness categories our fitness brand could adapt
4. **Timing urgency** — which gaps are time-sensitive (trend peaking) vs. evergreen
5. **Risk assessment** — which trends could backfire for our brand""",
}).json()
print("TRENDING CONTENT GAP ANALYSIS")
print("=" * 60)
print(f"Analyzed: {len(videos)} trending videos across {len(category_counts)} categories")
print(f"Top categories: {', '.join(cat_map.get(c, c) for c, _ in sorted(category_counts.items(), key=lambda x: -x[1])[:5])}")
print("\n" + gap.get("content", "")[:2500])
const YT = process.env.YOUTUBE_API_KEY;
const MV = process.env.MAVERA_API_KEY;
const YT_BASE = "https://www.googleapis.com/youtube/v3";
const MV_BASE = "https://app.mavera.io/api/v1";
const MV_H = { Authorization: `Bearer ${MV}`, "Content-Type": "application/json" };
const BRAND_CONTEXT = `Brand: [Your Brand] — DTC fitness/wellness
Channel: Workout tutorials, nutrition science, product reviews
Audience: 18-35, health-conscious, 60% female, US
Suspected gaps: trending challenges, reaction content, collaborations`;
// 1. Trending videos (1 quota unit)
const trending = await fetch(
`${YT_BASE}/videos?key=${YT}&chart=mostPopular&part=snippet,contentDetails,statistics,topicDetails&maxResults=50®ionCode=US`
).then(r => r.json());
if (trending.error) throw new Error(trending.error.message);
const videos = [];
const categoryCounts = {};
const allTags = [];
for (const item of trending.items || []) {
const s = item.snippet;
const stats = item.statistics || {};
const cat = s.categoryId || "0";
const tags = (s.tags || []).slice(0, 10);
categoryCounts[cat] = (categoryCounts[cat] || 0) + 1;
allTags.push(...tags);
videos.push({
title: s.title, channel: s.channelTitle, category: cat, tags,
views: parseInt(stats.viewCount || "0", 10),
likes: parseInt(stats.likeCount || "0", 10),
description: (s.description || "").slice(0, 200),
});
}
// 2. Category names
const catIds = [...new Set(Object.keys(categoryCounts))].join(",");
const categories = await fetch(
`${YT_BASE}/videoCategories?key=${YT}&id=${catIds}&part=snippet`
).then(r => r.json());
const catMap = Object.fromEntries(
(categories.items || []).map(i => [i.id, i.snippet.title])
);
// 3. Build summary
const trendingBlock = videos
.sort((a, b) => b.views - a.views).slice(0, 30)
.map(v => `- "${v.title}" by ${v.channel} | ${catMap[v.category] || v.category} | Views: ${v.views.toLocaleString()} | Tags: ${v.tags.slice(0, 5).join(", ")}`)
.join("\n");
const catSummary = Object.entries(categoryCounts)
.sort(([, a], [, b]) => b - a)
.map(([cid, count]) => `- ${catMap[cid] || cid}: ${count} videos`)
.join("\n");
// 4. Gap analysis
const gap = await fetch(`${MV_BASE}/mave/chat`, {
method: "POST", headers: MV_H,
body: JSON.stringify({
message: `Identify content gaps between trending YouTube and our brand.\n\nBRAND:\n${BRAND_CONTEXT}\n\nTRENDING (top 30):\n${trendingBlock}\n\nCATEGORIES:\n${catSummary}\n\nTOP TAGS: ${[...new Set(allTags)].slice(0, 30).join(", ")}\n\nProduce: 1) Trending themes we miss 2) Specific video concepts 3) Cross-category opportunities 4) Timing urgency 5) Risk assessment`,
}),
}).then(r => r.json());
console.log("TRENDING CONTENT GAP ANALYSIS");
console.log("=".repeat(60));
console.log(`Analyzed: ${videos.length} videos, ${Object.keys(categoryCounts).length} categories`);
console.log((gap.content || "").slice(0, 2500));
Example Output
TRENDING CONTENT GAP ANALYSIS
============================================================
Analyzed: 50 trending videos across 12 categories
Top categories: Entertainment, Music, Sports, Gaming, People & Blogs
## Content Gaps (Ranked by Opportunity)
### 1. "Get Ready With Me" Fitness Edition (HIGH URGENCY)
- 4 trending GRWM videos, none fitness-focused
- Concept: "GRWM for a 5AM Workout" — morning routine + product integration
- Format: 8-12 min, vlog-style, trending audio
- Audience: 2.4M views on average for GRWM content this week
### 2. Reaction/Commentary on Fitness Trends (MEDIUM URGENCY)
- 6 trending reaction videos, fitness commentary absent
- Concept: "Trainer Reacts to Viral Workout Hacks" — debunk + educate
- Format: 10-15 min, split-screen reaction format
- Audience: Commentary format averages 1.8M views
### 3. Challenge Format — Adapted (EVERGREEN)
- 3 trending challenge videos, zero fitness challenges this week
- Concept: "30-Day Morning Routine Challenge" — serialized, daily uploads
- Risk: Challenge fatigue — must feel fresh, not derivative
### 4. Cross-Category: Gaming × Fitness (EXPERIMENTAL)
- Gaming dominates trending (8 videos). No gaming-fitness crossover exists
- Concept: "I Trained Like a Pro Gamer for 7 Days" — posture, nutrition, endurance
- Risk: Low — unique angle, high shareability
## Risk Assessment
- Avoid: Political commentary trending videos — brand misalignment
- Caution: Music trends — licensing complexity for brand channels
Error Handling
mostPopular requires regionCode
mostPopular requires regionCode
Without
regionCode, results default to the US. Set this explicitly for non-US brands. Not all regions return 50 results.Category ID mapping
Category ID mapping
YouTube uses numeric category IDs (e.g., 17 = Sports). The
videoCategories endpoint translates these. Some categories are region-specific.Quota efficiency
Quota efficiency
This entire job uses ~2 quota units (1 for
videos.list, 1 for videoCategories). It’s the most quota-efficient job in this guide.