Scenario
Your competitors organize their YouTube content into playlists that reveal their content strategy — topic clusters, sequencing, and narrative arcs. This job pulls a competitor’s playlists and their items, then sends the full structure to Mave for strategic analysis. Mave identifies what content themes the competitor prioritizes, how they sequence viewer journeys, and where their playlist strategy has gaps your brand could exploit. The result is a reverse-engineered content strategy with actionable recommendations.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"}
COMPETITOR_CHANNEL_ID = "UC_COMPETITOR_CHANNEL_ID"
# 1. Get all playlists (1 quota unit per page)
playlists_data = requests.get(f"{YT_BASE}/playlists", params={
"key": YT, "channelId": COMPETITOR_CHANNEL_ID,
"part": "snippet,contentDetails", "maxResults": 25,
}).json()
if "error" in playlists_data:
raise SystemExit(f"YouTube API error: {playlists_data['error']['message']}")
playlists = []
for pl in playlists_data.get("items", []):
playlists.append({
"id": pl["id"],
"title": pl["snippet"]["title"],
"description": pl["snippet"].get("description", "")[:200],
"video_count": pl["contentDetails"]["itemCount"],
"published": pl["snippet"]["publishedAt"][:10],
})
print(f"Found {len(playlists)} playlists")
# 2. Get items for each playlist (1 quota unit per page per playlist)
playlist_details = []
for pl in playlists[:10]:
items_data = requests.get(f"{YT_BASE}/playlistItems", params={
"key": YT, "playlistId": pl["id"],
"part": "snippet", "maxResults": 20,
}).json()
videos = []
for item in items_data.get("items", []):
s = item["snippet"]
videos.append({
"position": s["position"],
"title": s["title"],
"description": s.get("description", "")[:150],
})
playlist_details.append({
"title": pl["title"],
"description": pl["description"],
"video_count": pl["video_count"],
"published": pl["published"],
"videos": videos,
})
# 3. Build playlist map for Mave
playlist_block = ""
for pd in playlist_details:
playlist_block += f"\n### Playlist: \"{pd['title']}\" ({pd['video_count']} videos, created {pd['published']})\n"
playlist_block += f"Description: {pd['description']}\n"
playlist_block += "Videos (in order):\n"
for v in pd["videos"]:
playlist_block += f" {v['position']+1}. {v['title']}\n"
# 4. Strategic analysis via Mave
analysis = requests.post(f"{MV_BASE}/mave/chat", headers=MV_H, json={
"message": f"""Analyze this competitor's YouTube playlist strategy.
COMPETITOR PLAYLISTS:
{playlist_block}
Produce:
1. **Content Themes**: What topics does this competitor prioritize? Rank by investment (playlist count × video count).
2. **Sequencing Strategy**: How do they order videos within playlists? Do they follow a learning path, chronological release, or engagement-optimized sequence?
3. **Narrative Arcs**: Do playlists tell a story? Identify any viewer journey patterns (awareness → consideration → conversion).
4. **Content Gaps**: What obvious topics are MISSING from their playlist strategy?
5. **Recommendations**: 3-5 playlist concepts our brand should create to fill gaps the competitor hasn't covered.
6. **Counter-Programming**: For each competitor playlist, suggest a playlist we could create that directly competes but differentiates.""",
}).json()
print("\nPLAYLIST-BASED CONTENT STRATEGY")
print("=" * 60)
print(f"Competitor channel: {COMPETITOR_CHANNEL_ID}")
print(f"Playlists analyzed: {len(playlist_details)}")
print(f"Total videos across playlists: {sum(pd['video_count'] for pd in playlist_details)}")
print("\n" + analysis.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 COMPETITOR_CHANNEL_ID = "UC_COMPETITOR_CHANNEL_ID";
// 1. Playlists (1 quota unit)
const playlistsData = await fetch(
`${YT_BASE}/playlists?key=${YT}&channelId=${COMPETITOR_CHANNEL_ID}&part=snippet,contentDetails&maxResults=25`
).then(r => r.json());
if (playlistsData.error) throw new Error(playlistsData.error.message);
const playlists = (playlistsData.items || []).map(pl => ({
id: pl.id, title: pl.snippet.title,
description: (pl.snippet.description || "").slice(0, 200),
videoCount: pl.contentDetails.itemCount,
published: pl.snippet.publishedAt.slice(0, 10),
}));
console.log(`Found ${playlists.length} playlists`);
// 2. Playlist items (1 quota unit per playlist)
const playlistDetails = [];
for (const pl of playlists.slice(0, 10)) {
const itemsData = await fetch(
`${YT_BASE}/playlistItems?key=${YT}&playlistId=${pl.id}&part=snippet&maxResults=20`
).then(r => r.json());
const videos = (itemsData.items || []).map(item => ({
position: item.snippet.position,
title: item.snippet.title,
description: (item.snippet.description || "").slice(0, 150),
}));
playlistDetails.push({ ...pl, videos });
}
// 3. Build block
const playlistBlock = playlistDetails.map(pd => {
const vids = pd.videos.map(v => ` ${v.position + 1}. ${v.title}`).join("\n");
return `### Playlist: "${pd.title}" (${pd.videoCount} videos, ${pd.published})\nDescription: ${pd.description}\nVideos:\n${vids}`;
}).join("\n\n");
// 4. Analysis
const analysis = await fetch(`${MV_BASE}/mave/chat`, {
method: "POST", headers: MV_H,
body: JSON.stringify({
message: `Analyze competitor YouTube playlist strategy.\n\n${playlistBlock}\n\nProduce: 1) Content themes ranked by investment 2) Sequencing strategy 3) Narrative arcs 4) Content gaps 5) Our playlist recommendations 6) Counter-programming per playlist`,
}),
}).then(r => r.json());
console.log("\nPLAYLIST-BASED CONTENT STRATEGY");
console.log("=".repeat(60));
console.log(`Playlists: ${playlistDetails.length} | Videos: ${playlistDetails.reduce((s, p) => s + p.videoCount, 0)}`);
console.log((analysis.content || "").slice(0, 2500));
Example Output
PLAYLIST-BASED CONTENT STRATEGY
============================================================
Competitor channel: UC_COMPETITOR_CHANNEL_ID
Playlists analyzed: 8
Total videos across playlists: 142
## Content Themes (by investment)
1. **Product Tutorials** (3 playlists, 48 videos) — heaviest investment
2. **Customer Stories** (2 playlists, 31 videos) — social proof focus
3. **Industry Trends** (1 playlist, 22 videos) — thought leadership
4. **Webinar Replays** (1 playlist, 28 videos) — repurposed live content
5. **Company Culture** (1 playlist, 13 videos) — employer branding
## Sequencing Strategy
- Tutorial playlists follow a learning path: basics → intermediate → advanced
- Customer stories are ordered by company size (SMB → Enterprise)
- Trend playlists are reverse-chronological (newest first)
## Content Gaps
1. **No comparison/vs. content** — they avoid direct competitor mentions
2. **No community content** — no user-generated or collaborative videos
3. **No Shorts playlists** — all content is 5-30min long-form
4. **No FAQ/troubleshooting** — support content is absent
## Recommended Playlists for Our Channel
1. "Us vs. [Competitor]" — Direct comparison playlist (they won't make this)
2. "60-Second Tips" — Shorts playlist filling their short-form gap
3. "Community Showcase" — Feature customer content they ignore
4. "Ask Us Anything" — FAQ format they haven't adopted
5. "Behind the Product" — Transparency content they lack
Error Handling
Empty or private playlists
Empty or private playlists
Private playlists are not returned by the API. Unlisted playlists are also hidden. The analysis only covers public content.
Quota cost for deep playlist scraping
Quota cost for deep playlist scraping
Each
playlists.list page = 1 unit, each playlistItems.list page = 1 unit. 10 playlists × 1 page = 11 total units. For channels with 50+ playlists, paginate with nextPageToken and batch across runs.Playlist item limit
Playlist item limit
The
playlistItems endpoint returns max 50 items per page. Playlists with 100+ videos need pagination. The code fetches the first 20 items per playlist — sufficient for sequencing analysis.