Scenario
Your Vimeo account holds hundreds of marketing videos spanning campaigns, product demos, testimonials, and event recordings — but you have no systematic way to know which are your strongest creative assets. This job pulls your entire video library viaGET /me/videos, uploads each to Mavera Assets, and runs Video Analysis scoring every video on emotional impact, message clarity, and behavioral effectiveness. The result is a ranked catalog that tells you which videos to promote, which to retire, and which patterns your best-performing creative shares.
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"}
# 1. Fetch entire video library (paginated, 100 per page)
all_videos = []
page = 1
while True:
resp = requests.get(f"{VM_BASE}/me/videos", headers=VM_H, params={
"per_page": 100, "page": page,
"fields": "uri,name,link,duration,stats,created_time,pictures.sizes",
"sort": "date", "direction": "desc",
})
if resp.status_code == 429:
retry = int(resp.headers.get("Retry-After", 60))
print(f"Rate limited — waiting {retry}s")
time.sleep(retry)
continue
resp.raise_for_status()
data = resp.json()
for v in data.get("data", []):
vid_id = v["uri"].split("/")[-1]
all_videos.append({
"id": vid_id, "name": v["name"], "link": v["link"],
"duration": v.get("duration", 0),
"plays": v.get("stats", {}).get("plays", 0),
"created": v.get("created_time", "")[:10],
})
if not data.get("paging", {}).get("next"):
break
page += 1
time.sleep(0.6)
print(f"Library size: {len(all_videos)} videos")
# 2. Analyze each video via Mavera (sample top 20 by plays)
scored = []
for video in sorted(all_videos, key=lambda v: -v["plays"])[:20]:
upload = requests.post(f"{MV_BASE}/assets", headers=MV_H, json={
"url": video["link"], "name": video["name"][:80], "type": "video",
}).json()
analysis = requests.post(f"{MV_BASE}/video-analysis", headers=MV_H, json={
"asset_id": upload["id"],
"analysis_types": [
"message_clarity", "emotional_impact", "behavioral_effectiveness",
"hook_score", "pacing", "cognitive_load",
],
"metadata": {"vimeo_id": video["id"], "plays": video["plays"]},
}).json()
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
r = status.get("results", {})
scored.append({
**video,
"clarity": r.get("message_clarity", {}).get("score", 0),
"emotion": r.get("emotional_impact", {}).get("score", 0),
"behavior": r.get("behavioral_effectiveness", {}).get("score", 0),
"hook": r.get("hook_score", {}).get("score", 0),
})
time.sleep(1)
# 3. Catalog ranking via Mave
scores_block = "\n".join(
f"- \"{s['name'][:50]}\" — plays: {s['plays']:,}, clarity: {s['clarity']}/100, "
f"emotion: {s['emotion']}/100, behavior: {s['behavior']}/100, hook: {s['hook']}/100"
for s in scored
)
ranking = requests.post(f"{MV_BASE}/mave/chat", headers=MV_H, json={
"message": f"""Rank this marketing video library by creative quality.
SCORED CATALOG ({len(scored)} videos):
{scores_block}
Produce:
1. **Tier 1 — Promote** (top creative assets): which videos and why
2. **Tier 2 — Optimize** (strong but fixable): what to improve
3. **Tier 3 — Retire** (underperforming creative): replace with what
4. **Patterns**: What do Tier 1 videos share that Tier 3 lacks?
5. **Recommendations**: 3 specific creative briefs for new videos based on the winning patterns""",
}).json()
print("MARKETING VIDEO LIBRARY ANALYSIS")
print("=" * 60)
for s in sorted(scored, key=lambda x: -x["emotion"]):
print(f" {s['name'][:40]:<42} Plays:{s['plays']:>8,} "
f"Clarity:{s['clarity']:>3} Emotion:{s['emotion']:>3} "
f"Hook:{s['hook']:>3}")
print("\n" + ranking.get("content", "")[:2000])
const VM = process.env.VIMEO_ACCESS_TOKEN;
const MV = process.env.MAVERA_API_KEY;
const VM_BASE = "https://api.vimeo.com";
const MV_BASE = "https://app.mavera.io/api/v1";
const VM_H = { Authorization: `Bearer ${VM}`, Accept: "application/vnd.vimeo.*+json;version=3.4" };
const MV_H = { Authorization: `Bearer ${MV}`, "Content-Type": "application/json" };
// 1. Fetch entire library (paginated)
const allVideos = [];
let page = 1;
while (true) {
const resp = await fetch(
`${VM_BASE}/me/videos?per_page=100&page=${page}&fields=uri,name,link,duration,stats,created_time&sort=date&direction=desc`,
{ headers: VM_H }
);
if (resp.status === 429) {
const retry = parseInt(resp.headers.get("Retry-After") || "60", 10);
console.log(`Rate limited — waiting ${retry}s`);
await new Promise(r => setTimeout(r, retry * 1000));
continue;
}
const data = await resp.json();
for (const v of data.data || []) {
allVideos.push({
id: v.uri.split("/").pop(), name: v.name, link: v.link,
duration: v.duration || 0,
plays: v.stats?.plays || 0,
created: (v.created_time || "").slice(0, 10),
});
}
if (!data.paging?.next) break;
page++;
await new Promise(r => setTimeout(r, 600));
}
console.log(`Library size: ${allVideos.length} videos`);
// 2. Analyze top 20 by plays
const scored = [];
for (const video of allVideos.sort((a, b) => b.plays - a.plays).slice(0, 20)) {
const upload = await fetch(`${MV_BASE}/assets`, {
method: "POST", headers: MV_H,
body: JSON.stringify({ url: video.link, name: video.name.slice(0, 80), type: "video" }),
}).then(r => r.json());
const analysis = await fetch(`${MV_BASE}/video-analysis`, {
method: "POST", headers: MV_H,
body: JSON.stringify({
asset_id: upload.id,
analysis_types: [
"message_clarity", "emotional_impact", "behavioral_effectiveness",
"hook_score", "pacing", "cognitive_load",
],
metadata: { vimeo_id: video.id, plays: video.plays },
}),
}).then(r => r.json());
let status;
for (let i = 0; i < 30; i++) {
await new Promise(r => setTimeout(r, 3000));
status = await fetch(
`${MV_BASE}/video-analysis/${analysis.id}`, { headers: MV_H }
).then(r => r.json());
if (status.status === "completed") break;
}
const r = status.results || {};
scored.push({
...video,
clarity: r.message_clarity?.score || 0,
emotion: r.emotional_impact?.score || 0,
behavior: r.behavioral_effectiveness?.score || 0,
hook: r.hook_score?.score || 0,
});
await new Promise(r => setTimeout(r, 1000));
}
// 3. Catalog ranking via Mave
const scoresBlock = scored.map(s =>
`- "${s.name.slice(0, 50)}" — plays: ${s.plays.toLocaleString()}, clarity: ${s.clarity}/100, ` +
`emotion: ${s.emotion}/100, behavior: ${s.behavior}/100, hook: ${s.hook}/100`
).join("\n");
const ranking = await fetch(`${MV_BASE}/mave/chat`, {
method: "POST", headers: MV_H,
body: JSON.stringify({
message: `Rank this marketing video library by creative quality.\n\nCATALOG (${scored.length} videos):\n${scoresBlock}\n\nProduce:\n1. Tier 1 — Promote (top assets and why)\n2. Tier 2 — Optimize (strong but fixable)\n3. Tier 3 — Retire (replace with what)\n4. Patterns (what Tier 1 shares that Tier 3 lacks)\n5. 3 creative briefs for new videos based on winning patterns`,
}),
}).then(r => r.json());
console.log("MARKETING VIDEO LIBRARY ANALYSIS");
console.log("=".repeat(60));
scored.sort((a, b) => b.emotion - a.emotion).forEach(s =>
console.log(` ${s.name.slice(0, 40).padEnd(42)} Plays:${String(s.plays.toLocaleString()).padStart(8)} ` +
`Clarity:${String(s.clarity).padStart(3)} Emotion:${String(s.emotion).padStart(3)} ` +
`Hook:${String(s.hook).padStart(3)}`)
);
console.log("\n" + (ranking.content || "").slice(0, 2000));
Example Output
MARKETING VIDEO LIBRARY ANALYSIS
============================================================
Q1 Brand Campaign — Feel the Differen Plays: 84,200 Clarity: 91 Emotion: 94 Hook: 87
Customer Story — Acme Corp Transformat Plays: 42,100 Clarity: 88 Emotion: 82 Hook: 76
Product Demo — Enterprise Dashboard Plays: 31,500 Clarity: 79 Emotion: 45 Hook: 62
Webinar Replay — State of the Market Plays: 18,300 Clarity: 72 Emotion: 38 Hook: 41
Trade Show Booth Walkthrough Plays: 9,400 Clarity: 54 Emotion: 29 Hook: 33
## Catalog Ranking
### Tier 1 — Promote
- "Q1 Brand Campaign" (composite: 91/100) — highest emotional impact in library.
Opens with face close-up and music swell in first 2 seconds. Redistribute as
paid social hero and homepage embed.
- "Customer Story — Acme Corp" (composite: 82/100) — strong narrative arc with
clear problem→solution→result structure. Feature on case study landing pages.
### Tier 2 — Optimize
- "Product Demo" — clarity 79 but emotion 45. The demo is thorough but clinical.
Re-cut with a customer voiceover narrating their workflow instead of feature
bullet points. Hook score 62 suggests the first 5 seconds need a problem statement.
### Tier 3 — Retire
- "Trade Show Booth Walkthrough" — all scores below 55. This format doesn't
translate to digital. Replace with a 60-second highlight reel from the booth
with customer sound bites.
### Patterns
Tier 1 videos share: human faces in first frame, emotional audio within 2 seconds,
single-message focus (1 CTA per video). Tier 3 videos share: wide establishing shots,
multiple messages crammed in, no clear CTA.
Error Handling
Rate limit (429) handling
Rate limit (429) handling
Vimeo returns a
Retry-After header in seconds. The code respects this value. Free accounts hit ~100 req/min; Pro/Business accounts get ~600 req/10 min. Add exponential backoff for large libraries.Video file access scope
Video file access scope
The
video_files scope is required to access download URLs for some endpoints. Without it, use the link field (public Vimeo URL) for Mavera asset uploads instead.Large library pagination
Large library pagination
Libraries with 500+ videos require multiple pages. Each page costs one API call. The code paginates automatically but add a 600ms delay between pages to stay within rate limits.
What’s Next
Vimeo Integration
Back to Vimeo integration overview
Pre-Publish Creative Testing
Gate publishing with quality thresholds
Video Analysis API
Full reference for POST /api/v1/video-analysis
Mave Agent
Full reference for POST /api/v1/mave/chat