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

You have 15 active video creatives across campaigns and need to know which ones have the strongest messaging, emotional pull, and CTA effectiveness. This job pulls all active creatives, runs Video Analysis on each, then feeds the scores into Mave to produce a ranked comparison matrix with actionable recommendations on which to scale and which to kill.

Architecture

Code

import os, requests, time, tempfile

META = os.environ["META_ACCESS_TOKEN"]
ACCT = os.environ["META_AD_ACCOUNT_ID"]
MV = os.environ["MAVERA_API_KEY"]
GRAPH = "https://graph.facebook.com/v24.0"
MB = "https://app.mavera.io/api/v1"
MH = {"Authorization": f"Bearer {MV}", "Content-Type": "application/json"}

# 1. Pull active ads with creative details
ads = requests.get(
    f"{GRAPH}/{ACCT}/ads",
    params={
        "access_token": META,
        "effective_status": '["ACTIVE"]',
        "fields": "id,name,creative{id,name,video_id,title,body}",
        "limit": 50,
    },
).json().get("data", [])

video_ads = [a for a in ads if a.get("creative", {}).get("video_id")]
print(f"Active video ads: {len(video_ads)}")

# 2. Analyze each (reusing upload+analysis pattern)
scored = []
for ad in video_ads[:15]:
    creative = ad["creative"]
    vid = creative["video_id"]

    video_info = requests.get(
        f"{GRAPH}/{vid}",
        params={"access_token": META, "fields": "source,length"},
    ).json()
    if not video_info.get("source"):
        continue

    vid_resp = requests.get(video_info["source"], stream=True)
    tmp = tempfile.NamedTemporaryFile(suffix=".mp4", delete=False)
    for chunk in vid_resp.iter_content(8192):
        tmp.write(chunk)
    tmp.close()

    with open(tmp.name, "rb") as f:
        asset = requests.post(f"{MB}/assets",
            headers={"Authorization": f"Bearer {MV}"},
            files={"file": (f"{vid}.mp4", f, "video/mp4")},
        ).json()
    os.unlink(tmp.name)

    analysis = requests.post(f"{MB}/video-analyses", headers=MH,
        json={"asset_id": asset["id"], "name": creative.get("name", vid)},
    ).json()

    for _ in range(30):
        time.sleep(10)
        status = requests.get(f"{MB}/video-analyses/{analysis['id']}",
            headers={"Authorization": f"Bearer {MV}"}).json()
        if status.get("status") in ("completed", "failed"):
            break

    if status.get("status") == "completed":
        scored.append({
            "ad_name": ad.get("name", "Untitled"),
            "creative_name": creative.get("name", ""),
            "title": creative.get("title", ""),
            "body": creative.get("body", "")[:100],
            "duration": video_info.get("length"),
            "scores": status.get("scores", {}),
        })
    time.sleep(1)

# 3. Build comparison table for Mave
table_rows = []
for i, s in enumerate(scored, 1):
    sc = s["scores"]
    table_rows.append(
        f"{i}. \"{s['ad_name']}\" — emotional: {sc.get('emotional','?')}, "
        f"cognitive: {sc.get('cognitive','?')}, behavioral: {sc.get('behavioral','?')}, "
        f"duration: {s['duration']}s, copy: \"{s['body']}\""
    )
table = "\n".join(table_rows)

# 4. Mave comparison
comparison = requests.post(f"{MB}/mave/chat", headers=MH, json={
    "message": f"""Compare these {len(scored)} video ad creatives based on their Mavera analysis scores.

CREATIVE SCORES:
{table}

Produce:
1. Ranked matrix (best to worst) with rationale
2. Message clarity ranking
3. Emotional intensity ranking
4. CTA strength ranking
5. Which creatives to SCALE (top 3) and why
6. Which creatives to PAUSE or REWORK and what to fix
7. Patterns in top performers vs bottom performers
8. Specific recommendations for each creative"""
}).json()

print("=== Creative Comparison Matrix ===")
print(comparison.get("content", ""))

Example Output

=== Creative Comparison Matrix ===

## Overall Ranking
| Rank | Creative | Emotional | Cognitive | Behavioral | Verdict |
|------|----------|-----------|-----------|------------|---------|
| 1 | "Testimonial — Real Users" | 9.0 | 7.1 | 8.4 | SCALE |
| 2 | "Summer Sale Hero — 30s" | 8.2 | 6.5 | 7.8 | SCALE |
| 3 | "Product Demo — Features" | 5.8 | 8.9 | 7.2 | KEEP |
| 4 | "Brand Story — Our Mission" | 7.5 | 4.2 | 3.1 | REWORK |
| 5 | "UGC Compilation" | 6.1 | 3.8 | 4.5 | PAUSE |

## Key Patterns
- Top performers combine emotional hooks (>8.0) with strong CTAs (behavioral >7.5)
- "Brand Story" has emotional pull but no conversion path — add CTA overlay at 0:22
- UGC compilation lacks narrative structure; re-edit with clear problem→solution arc

## Recommendations
1. Scale "Testimonial" with 3 new audience segments
2. Test "Summer Sale" with shorter 15s cut — first 3s hook scores 9.1
3. Rework "Brand Story" — add product demo at midpoint and end-card CTA

Error Handling

The effective_status param requires a JSON array as a string: '["ACTIVE"]'. Common mistake: passing ACTIVE without brackets.
Use creative{id,name,video_id} syntax for nested field expansion. Without braces, you get only the creative ID.
For accounts with 100+ creatives, paginate with after cursor from the response’s paging.cursors.after field.

Meta Ads Integration

All Meta Ads jobs

Mave Agent

AI research agent reference