Scenario
Your product’s YouTube videos have hundreds of comments containing raw, unsolicited audience language — objections, praise, feature requests, and emotional reactions you can’t get from surveys. This job pulls up to 200 comments viacommentThreads.list, then sends them to a Mavera research persona through Chat. The persona segments commenters into audience archetypes, identifies dominant sentiment themes, and surfaces the language patterns your marketing should mirror. The result is persona validation grounded in authentic audience voice.
Architecture
Code
import os, requests, time
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"}
VIDEO_ID = "dQw4w9WgXcQ" # Replace with your video ID
# 1. Fetch up to 200 comments (1 quota unit per page, ~20 per page)
comments = []
page_token = None
while len(comments) < 200:
params = {
"key": YT, "videoId": VIDEO_ID,
"part": "snippet", "maxResults": 100,
"order": "relevance", "textFormat": "plainText",
}
if page_token:
params["pageToken"] = page_token
r = requests.get(f"{YT_BASE}/commentThreads", params=params)
if r.status_code == 403:
print("Comments disabled or API quota exceeded")
break
r.raise_for_status()
data = r.json()
for item in data.get("items", []):
snippet = item["snippet"]["topLevelComment"]["snippet"]
comments.append({
"text": snippet["textDisplay"][:500],
"likes": snippet.get("likeCount", 0),
"published": snippet.get("publishedAt", ""),
})
page_token = data.get("nextPageToken")
if not page_token:
break
print(f"Collected {len(comments)} comments")
# 2. Create a research analyst persona
persona = requests.post(f"{MV_BASE}/personas", headers=MV_H, json={
"name": "YouTube Audience Analyst",
"description": (
"Senior qualitative researcher specializing in digital audience analysis. "
"Expert at identifying audience segments from unstructured text, detecting "
"sentiment patterns, and extracting the language that resonates with each segment. "
"Thinks in terms of jobs-to-be-done and psychographic clusters, not just demographics."
),
}).json()
# 3. Build comment block sorted by engagement
comments.sort(key=lambda c: -c["likes"])
comment_block = "\n".join(
f"[{c['likes']} likes] {c['text'][:300]}"
for c in comments[:200]
)
# 4. Analyze via Mave Chat with persona context
analysis = requests.post(f"{MV_BASE}/mave/chat", headers=MV_H, json={
"persona_id": persona["id"],
"message": f"""Analyze these {len(comments)} YouTube comments as audience segments.
COMMENTS:
{comment_block}
Produce:
1. **Audience Segments** (3-5 clusters): Name each segment, estimate its share of comments, describe their motivation, and quote 2-3 representative comments verbatim.
2. **Sentiment Distribution**: % positive / neutral / negative with examples.
3. **Language Patterns**: Exact phrases, slang, and emotional triggers your marketing should adopt.
4. **Objections & Concerns**: Recurring pain points or skepticism themes.
5. **Content Opportunities**: Topics commenters ask about that you haven't covered.
6. **Persona Validation**: Do these segments match typical B2C buyer personas? Where do they diverge?""",
}).json()
print(f"\nAUDIENCE ANALYSIS — {len(comments)} comments from {VIDEO_ID}")
print("=" * 60)
print(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 VIDEO_ID = "dQw4w9WgXcQ";
// 1. Fetch up to 200 comments
const comments = [];
let pageToken = null;
while (comments.length < 200) {
const url = new URL(`${YT_BASE}/commentThreads`);
url.searchParams.set("key", YT);
url.searchParams.set("videoId", VIDEO_ID);
url.searchParams.set("part", "snippet");
url.searchParams.set("maxResults", "100");
url.searchParams.set("order", "relevance");
url.searchParams.set("textFormat", "plainText");
if (pageToken) url.searchParams.set("pageToken", pageToken);
const res = await fetch(url);
if (res.status === 403) {
console.error("Comments disabled or quota exceeded");
break;
}
const data = await res.json();
for (const item of data.items || []) {
const s = item.snippet.topLevelComment.snippet;
comments.push({
text: s.textDisplay.slice(0, 500),
likes: s.likeCount || 0,
published: s.publishedAt || "",
});
}
pageToken = data.nextPageToken;
if (!pageToken) break;
}
console.log(`Collected ${comments.length} comments`);
// 2. Research analyst persona
const persona = await fetch(`${MV_BASE}/personas`, {
method: "POST", headers: MV_H,
body: JSON.stringify({
name: "YouTube Audience Analyst",
description: "Senior qualitative researcher. Expert at segmenting audiences from unstructured text, detecting sentiment, extracting resonant language. Thinks in jobs-to-be-done and psychographic clusters.",
}),
}).then(r => r.json());
// 3. Build comment block
comments.sort((a, b) => b.likes - a.likes);
const commentBlock = comments.slice(0, 200).map(c =>
`[${c.likes} likes] ${c.text.slice(0, 300)}`
).join("\n");
// 4. Analyze
const analysis = await fetch(`${MV_BASE}/mave/chat`, {
method: "POST", headers: MV_H,
body: JSON.stringify({
persona_id: persona.id,
message: `Analyze these ${comments.length} YouTube comments as audience segments.\n\nCOMMENTS:\n${commentBlock}\n\nProduce:\n1. Audience Segments (3-5 clusters) with representative quotes\n2. Sentiment Distribution (% pos/neutral/neg)\n3. Language Patterns to adopt\n4. Objections & Concerns\n5. Content Opportunities\n6. Persona Validation vs typical buyer personas`,
}),
}).then(r => r.json());
console.log(`\nAUDIENCE ANALYSIS — ${comments.length} comments from ${VIDEO_ID}`);
console.log("=".repeat(60));
console.log((analysis.content || "").slice(0, 2500));
Example Output
Collected 200 comments
AUDIENCE ANALYSIS — 200 comments from dQw4w9WgXcQ
============================================================
## Audience Segments
### 1. Power Users (32% of comments)
Motivation: Already own the product, seeking advanced tips and validation.
Quotes: "Been using this for 6 months — game changer for my workflow"
"Can you do a deep dive on the API integration?"
### 2. Skeptical Evaluators (28%)
Motivation: Comparing alternatives, need proof before commitment.
Quotes: "How does this compare to [Competitor]? Switching costs are real"
"Nice demo but show me real results from actual customers"
### 3. Aspirational Followers (22%)
Motivation: Follow the brand for inspiration, not yet in buying mode.
Quotes: "This is so cool, saving for when I can afford it"
"Love the energy in this video, keep making content like this"
### 4. Feature Requesters (12%)
Motivation: Active users pushing for specific capabilities.
Quotes: "PLEASE add dark mode" / "When is the mobile app coming?"
### 5. Trolls & Off-Topic (6%)
Filtered — spam, unrelated memes, or single-emoji reactions.
## Sentiment Distribution
Positive: 58% | Neutral: 26% | Negative: 16%
## Language Patterns
- "game changer" (14 uses) — adopt as social proof language
- "finally" (9 uses) — signals long-awaited solution framing
- Question format "but does it...?" — address preemptively in ads
## Objections
- Price sensitivity (11 comments mention cost)
- Competitor comparison requests (8 comments)
- "Is this just another [category]?" — differentiation needed
Error Handling
Comments disabled
Comments disabled
Quota cost for pagination
Quota cost for pagination
Each
commentThreads.list page costs 1 quota unit. Fetching 200 comments across 2 pages = 2 units — very efficient. The maxResults ceiling is 100.Comment language filtering
Comment language filtering
Non-English comments may skew analysis. Add
&searchTerms= or filter by detected language before sending to Mave if your audience is monolingual.
commentsDisabled. Check the video’ssnippet.liveBroadcastContentand fallback to a different video.