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

Your team uploads a draft video to Vimeo as a private link for stakeholder review — but the review is subjective and inconsistent. This job intercepts the draft after upload, pulls it via the Vimeo API, runs Video Analysis to score it against your quality thresholds, and if any metric falls below the threshold, automatically triggers a Focus Group asking: “What could make this more compelling?” The result is a publish-or-revise decision backed by quantitative scores and synthetic panel feedback — before the video goes public.

Architecture

Code

import os, requests, time

VM = os.environ["VIMEO_ACCESS_TOKEN"]
MV = os.environ["MAVERA_API_KEY"]
VM_BASE = "https://api.vimeo.com"
MV_BASE = "https://app.mavera.io/api/v1"
VM_H = {"Authorization": f"Bearer {VM}", "Accept": "application/vnd.vimeo.*+json;version=3.4"}
MV_H = {"Authorization": f"Bearer {MV}", "Content-Type": "application/json"}

DRAFT_VIDEO_ID = "123456789"
THRESHOLDS = {
    "message_clarity": 70,
    "emotional_impact": 65,
    "hook_score": 60,
    "behavioral_effectiveness": 60,
}

# 1. Fetch the draft video from Vimeo
draft = requests.get(f"{VM_BASE}/videos/{DRAFT_VIDEO_ID}", headers=VM_H, params={
    "fields": "uri,name,link,duration,privacy,status,pictures.sizes",
}).json()

if "error" in draft:
    raise SystemExit(f"Vimeo API error: {draft.get('developer_message', draft.get('error', ''))}")

print(f"Draft: \"{draft['name']}\" | Duration: {draft.get('duration', 0)}s | Privacy: {draft.get('privacy', {}).get('view', 'unknown')}")

# 2. Upload to Mavera and run Video Analysis
upload = requests.post(f"{MV_BASE}/assets", headers=MV_H, json={
    "url": draft["link"], "name": f"[DRAFT] {draft['name'][:70]}", "type": "video",
}).json()

analysis = requests.post(f"{MV_BASE}/video-analysis", headers=MV_H, json={
    "asset_id": upload["id"],
    "analysis_types": list(THRESHOLDS.keys()) + ["pacing", "cognitive_load", "emotional_arc"],
    "metadata": {"vimeo_id": DRAFT_VIDEO_ID, "stage": "pre-publish"},
}).json()

# 3. Poll for results
for _ in range(30):
    time.sleep(3)
    status = requests.get(
        f"{MV_BASE}/video-analysis/{analysis['id']}", headers=MV_H
    ).json()
    if status.get("status") == "completed":
        break

results = status.get("results", {})
scores = {
    metric: results.get(metric, {}).get("score", 0)
    for metric in THRESHOLDS
}

# 4. Check thresholds
failures = {
    metric: {"score": scores[metric], "threshold": threshold}
    for metric, threshold in THRESHOLDS.items()
    if scores[metric] < threshold
}

print(f"\nSCORECARD:")
for metric, threshold in THRESHOLDS.items():
    score = scores[metric]
    verdict = "PASS" if score >= threshold else "FAIL"
    print(f"  {metric:<30} {score:>3}/100  (threshold: {threshold})  [{verdict}]")

if not failures:
    print("\n✓ ALL THRESHOLDS MET — Ready to publish")
else:
    print(f"\n{len(failures)} THRESHOLD(S) FAILED — Triggering Focus Group review")

    # 5. Create Focus Group for improvement feedback
    persona_ids = []
    for name, desc in [
        ("Target Customer", "35-year-old marketing director evaluating SaaS tools. Watches product videos to assess vendor quality. Judges professionalism, clarity, and whether the video respects their time."),
        ("Creative Director", "Senior creative with 15 years in video production. Evaluates pacing, visual storytelling, emotional arc, and hook effectiveness. Gives specific, actionable feedback."),
        ("Skeptical Buyer", "CFO who dislikes marketing fluff. Wants data, proof points, and clear ROI messaging. Will disengage immediately if the video feels like a hard sell."),
    ]:
        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)

    failure_summary = "\n".join(
        f"- {m}: scored {f['score']}/100 (needs {f['threshold']}+)"
        for m, f in failures.items()
    )
    arc_summary = results.get("emotional_arc", {}).get("summary", "No arc data available")

    fg = requests.post(f"{MV_BASE}/focus-groups", headers=MV_H, json={
        "name": f"Pre-Publish Review — {draft['name'][:40]}",
        "persona_ids": persona_ids,
        "questions": [
            f"This video failed quality thresholds:\n{failure_summary}\n\nEmotional arc: {arc_summary}\n\nWhat specifically could make this video more compelling?",
            "If you could change only ONE thing about the first 5 seconds, what would it be?",
            "What is this video trying to make you feel? Is it working? What emotion is missing?",
            "Would you share this video with a colleague? Why or why not? What would change your answer?",
        ],
        "responses_per_persona": 2,
    }).json()

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

    print("\nFOCUS GROUP FEEDBACK:")
    for resp in fg_data.get("responses", []):
        print(f"\n[{resp.get('persona_name', '?')}] {resp.get('question', '')[:60]}...")
        print(f"  → {resp.get('answer', '')[:400]}")

Example Output

Draft: "Q2 Product Launch — Feature Overview" | Duration: 94s | Privacy: nobody

SCORECARD:
  message_clarity                 72/100  (threshold: 70)  [PASS]
  emotional_impact                48/100  (threshold: 65)  [FAIL]
  hook_score                      55/100  (threshold: 60)  [FAIL]
  behavioral_effectiveness        63/100  (threshold: 60)  [PASS]

2 THRESHOLD(S) FAILED — Triggering Focus Group review

FOCUS GROUP FEEDBACK:

[Target Customer] Failed thresholds: emotional_impact 48, hook_score 55...
  → The video opens with a logo animation — that's 3 seconds of dead air where
    I'm already deciding to close the tab. Start with the problem I have, not
    your brand. Show me a frustrated user, then show me the fix. The emotional
    impact is low because it's a feature list disguised as a video.

[Creative Director] Change ONE thing about the first 5 seconds...
  → Kill the logo intro entirely. Open on a tight shot of someone's face
    reacting to a problem — frustration, confusion, overwhelm. You have 2
    seconds to create emotional investment. The current opener is visual
    wallpaper. Sound design matters too: add a tension cue in the first second.

[Skeptical Buyer] Would you share this with a colleague?...
  → No. It doesn't give me ammunition for an internal business case. I need
    this video to say "this saves 4 hours/week" or "this reduces error rates by
    30%." Right now it says "look at our features." Add one concrete metric in
    the first 15 seconds and I'll forward it to my team.

Error Handling

Private Vimeo videos require the token owner to be the video owner or a team member. The privacy.view field should be nobody (private link) or password. Videos set to disable cannot be accessed via API.
Newly uploaded Vimeo videos may have status: "uploading" or "transcoding". Wait for status: "available" before sending to Mavera. Poll GET /videos/{id}?fields=status every 10 seconds.
Start with conservative thresholds (60-70) and tighten as your library builds. Analyze your top 10 performing videos first to establish baseline scores for each metric.

What’s Next

Vimeo Integration

Back to Vimeo integration overview

Engagement Scoring Correlation

Correlate Mavera scores with real engagement

Video Analysis API

Full reference for POST /api/v1/video-analysis

Focus Groups API

Full reference for POST /api/v1/focus-groups