Skip to main content

Documentation Index

Fetch the complete documentation index at: https://docs.mavera.io/llms.txt

Use this file to discover all available pages before exploring further.

Scenario

YouTube thumbnails drive 80%+ of click decisions, but you can’t A/B test them natively. This job pulls your video thumbnails (and optionally competitor thumbnails) via videos.list, extracts the thumbnail URLs, then runs a Focus Group where personas evaluate each thumbnail with the core question: “Which would you click? Rate 1-10.” The result is a click-prediction scorecard with specific visual feedback from each persona — before you commit to a thumbnail.

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_IDS = ["VIDEO_ID_1", "VIDEO_ID_2", "VIDEO_ID_3", "VIDEO_ID_4"]

# 1. Fetch video details with thumbnails (1 quota unit)
details = requests.get(f"{YT_BASE}/videos", params={
    "key": YT, "id": ",".join(VIDEO_IDS),
    "part": "snippet,statistics",
}).json()

if "error" in details:
    raise SystemExit(f"YouTube API error: {details['error']['message']}")

thumbnails = []
for video in details.get("items", []):
    snippet = video["snippet"]
    stats = video.get("statistics", {})
    thumb_url = (
        snippet.get("thumbnails", {}).get("maxres", {}).get("url")
        or snippet.get("thumbnails", {}).get("high", {}).get("url")
        or snippet.get("thumbnails", {}).get("medium", {}).get("url", "")
    )

    thumbnails.append({
        "video_id": video["id"],
        "title": snippet["title"],
        "thumbnail_url": thumb_url,
        "views": int(stats.get("viewCount", 0)),
        "ctr_context": f"{snippet['channelTitle']}{snippet['title'][:60]}",
    })

print(f"Collected {len(thumbnails)} thumbnails")

# 2. Create Focus Group personas
persona_ids = []
for name, desc in [
    ("Casual Browser", "Scrolls YouTube homepage on phone. Clicks based on thumbnail emotion and curiosity gap. Ignores text-heavy thumbnails. Prefers faces and bright colors."),
    ("Search-Intent Viewer", "Types specific queries into YouTube search. Clicks thumbnails that visually confirm the video matches their query. Values clarity over creativity."),
    ("Subscriber", "Regular subscriber who sees these in their feed. Already trusts the channel. Clicks based on topic interest — thumbnail is secondary but influences priority."),
    ("Competitor's Audience", "Watches competitor channels. Unfamiliar with this brand. Thumbnail must prove higher quality or unique angle to earn a click away from known creators."),
]:
    p = requests.post(f"{MV_BASE}/personas", headers=MV_H, json={
        "name": name, "description": desc,
    }).json()
    persona_ids.append(p["id"])
    time.sleep(0.3)

# 3. Build thumbnail descriptions for focus group
thumb_block = "\n\n".join(
    f"THUMBNAIL {chr(65+i)}: \"{t['title']}\"\n"
    f"  Image URL: {t['thumbnail_url']}\n"
    f"  Channel context: {t['ctr_context']}\n"
    f"  Current views: {t['views']:,}"
    for i, t in enumerate(thumbnails)
)

# 4. Run Focus Group
fg = requests.post(f"{MV_BASE}/focus-groups", headers=MV_H, json={
    "name": "YouTube Thumbnail A/B Test",
    "persona_ids": persona_ids,
    "questions": [
        f"You see these thumbnails on YouTube. Which would you click FIRST? Rate each 1-10 for click likelihood.\n\n{thumb_block}",
        "What specific visual element in your top-rated thumbnail made you want to click?",
        "Which thumbnail would you NEVER click? What turns you off about it?",
        "If you could redesign the lowest-rated thumbnail, what would you change? Be specific about colors, text, faces, and composition.",
        "Imagine all these thumbnails appeared in a row on the YouTube homepage. Which stands out most and why?",
    ],
    "responses_per_persona": 2,
}).json()

# 5. Poll for results
for _ in range(20):
    time.sleep(5)
    data = requests.get(f"{MV_BASE}/focus-groups/{fg['id']}", headers=MV_H).json()
    if data.get("status") == "completed":
        break

# 6. Output
print("\nTHUMBNAIL A/B FOCUS GROUP RESULTS")
print("=" * 60)
for t in thumbnails:
    print(f"  [{t['video_id'][:11]}] {t['title'][:50]}")
    print(f"    Thumbnail: {t['thumbnail_url']}")
    print(f"    Views: {t['views']:,}\n")

print("PERSONA RESPONSES:")
for resp in data.get("responses", []):
    persona_name = next(
        (name for pid, name in zip(persona_ids, ["Casual Browser", "Search-Intent", "Subscriber", "Competitor's Audience"])
         if pid == resp.get("persona_id")),
        "Unknown"
    )
    print(f"\n[{persona_name}] {resp.get('question', '')[:60]}...")
    print(f"  → {resp.get('answer', '')[:350]}")

Example Output

THUMBNAIL A/B FOCUS GROUP RESULTS
============================================================
  [dQw4w9WgXc] How to Build a Morning Routine That Sticks
    Thumbnail: https://i.ytimg.com/vi/.../maxresdefault.jpg
    Views: 245,000

  [xYz1234567] 5 Nutrition Myths Debunked by Science
    Thumbnail: https://i.ytimg.com/vi/.../maxresdefault.jpg
    Views: 89,000

  [aBc9876543] I Tried Every Protein Powder — Here's the Winner
    Thumbnail: https://i.ytimg.com/vi/.../maxresdefault.jpg
    Views: 412,000

  [mNoPqRsTuV] Beginner Workout Plan (No Equipment)
    Thumbnail: https://i.ytimg.com/vi/.../maxresdefault.jpg
    Views: 178,000

PERSONA RESPONSES:

[Casual Browser] Rate each thumbnail 1-10 for click likelihood...
  → A: 6/10 — sunrise photo is calming but doesn't create urgency.
    B: 8/10 — red "X" marks on food items create instant curiosity.
    C: 9/10 — face with surprised expression + product grid = clickable.
    D: 5/10 — generic gym stock photo. Seen this a thousand times.

[Search-Intent Viewer] What visual element made you click?...
  → Thumbnail C: The grid layout showing all products immediately tells
    me this video answers my exact query. I don't need to guess what the
    video covers — the thumbnail IS the table of contents.

[Competitor's Audience] Which would you NEVER click?...
  → Thumbnail D. It looks identical to every other fitness channel. If I
    already watch [Competitor], why would I click a generic thumbnail from
    a channel I don't recognize? Give me a reason to switch.

[Subscriber] Redesign the lowest-rated thumbnail...
  → Thumbnail D: Replace stock gym photo with the creator's face doing the
    workout. Add text overlay "0 Equipment" in bold yellow. Use a split
    frame: left side = living room, right side = results. The current
    design says "another workout video" — the redesign says "YOUR workout."

Error Handling

Not all videos have maxres thumbnails (1280×720). The code falls back to high (480×360) then medium (320×180). Custom thumbnails require the channel owner to verify their account.
Mavera Focus Groups analyze thumbnail descriptions and URLs contextually. For pixel-level visual analysis, upload thumbnails as image assets via POST /assets first and reference the asset IDs.
This entire job uses 1 quota unit (videos.list with up to 50 IDs per call). The most quota-efficient job alongside trending analysis.

YouTube Integration

Focus Groups API