> ## 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.

# Ad Creative Comparison Matrix

> Pull all active video creatives from Meta, run Video Analysis on each, and use Mave to produce a ranked comparison matrix with scale/kill recommendations

### 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

```mermaid theme={"dark"}
flowchart LR
    A["Meta GET ads (ACTIVE)"] --> B[GET creatives] --> C[Video Analysis on each] --> D[Collect scores] --> E["POST /api/v1/mave/chat"] --> F[Ranked matrix]
```

### Code

<CodeGroup>
  ```python Python theme={"dark"}
  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", ""))
  ```

  ```javascript JavaScript theme={"dark"}
  const META = process.env.META_ACCESS_TOKEN;
  const ACCT = process.env.META_AD_ACCOUNT_ID;
  const MV = process.env.MAVERA_API_KEY;
  const GRAPH = "https://graph.facebook.com/v24.0";
  const MB = "https://app.mavera.io/api/v1";
  const MH = { Authorization: `Bearer ${MV}`, "Content-Type": "application/json" };

  // 1. Pull active video ads
  const ads = await fetch(
    `${GRAPH}/${ACCT}/ads?access_token=${META}&effective_status=["ACTIVE"]&fields=id,name,creative{id,name,video_id,title,body}&limit=50`
  ).then(r => r.json()).then(d => d.data || []);

  const videoAds = ads.filter(a => a.creative?.video_id);
  console.log(`Active video ads: ${videoAds.length}`);

  // 2. Analyze each creative
  const scored = [];
  for (const ad of videoAds.slice(0, 15)) {
    const { creative } = ad;
    const vid = creative.video_id;

    const videoInfo = await fetch(
      `${GRAPH}/${vid}?access_token=${META}&fields=source,length`
    ).then(r => r.json());
    if (!videoInfo.source) continue;

    const vidBuffer = Buffer.from(await fetch(videoInfo.source).then(r => r.arrayBuffer()));
    const form = new FormData();
    form.append("file", new Blob([vidBuffer], { type: "video/mp4" }), `${vid}.mp4`);
    const asset = await fetch(`${MB}/assets`, {
      method: "POST", headers: { Authorization: `Bearer ${MV}` }, body: form,
    }).then(r => r.json());

    const analysis = await fetch(`${MB}/video-analyses`, {
      method: "POST", headers: MH,
      body: JSON.stringify({ asset_id: asset.id, name: creative.name || vid }),
    }).then(r => r.json());

    let status;
    for (let i = 0; i < 30; i++) {
      await new Promise(r => setTimeout(r, 10000));
      status = await fetch(`${MB}/video-analyses/${analysis.id}`,
        { headers: { Authorization: `Bearer ${MV}` } }).then(r => r.json());
      if (status.status === "completed" || status.status === "failed") break;
    }

    if (status?.status === "completed") {
      scored.push({
        ad_name: ad.name || "Untitled", creative_name: creative.name || "",
        title: creative.title || "", body: (creative.body || "").slice(0, 100),
        duration: videoInfo.length, scores: status.scores || {},
      });
    }
    await new Promise(r => setTimeout(r, 1000));
  }

  // 3. Build comparison table
  const table = scored.map((s, i) => {
    const sc = s.scores;
    return `${i+1}. "${s.ad_name}" — emotional: ${sc.emotional ?? "?"}, cognitive: ${sc.cognitive ?? "?"}, behavioral: ${sc.behavioral ?? "?"}, duration: ${s.duration}s, copy: "${s.body}"`;
  }).join("\n");

  // 4. Mave comparison
  const comparison = await fetch(`${MB}/mave/chat`, {
    method: "POST", headers: MH,
    body: JSON.stringify({
      message: `Compare these ${scored.length} video ad creatives.\n\nSCORES:\n${table}\n\nProduce: 1) Ranked matrix 2) Message clarity ranking 3) Emotional intensity 4) CTA strength 5) SCALE top 3 6) PAUSE/REWORK 7) Patterns 8) Per-creative recommendations`,
    }),
  }).then(r => r.json());

  console.log("=== Creative Comparison Matrix ===");
  console.log(comparison.content || "");
  ```
</CodeGroup>

### Example Output

```text theme={"dark"}
=== 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

<AccordionGroup>
  <Accordion title="Effective status filter syntax">The `effective_status` param requires a JSON array as a string: `'["ACTIVE"]'`. Common mistake: passing `ACTIVE` without brackets.</Accordion>
  <Accordion title="Creative nested fields">Use `creative{id,name,video_id}` syntax for nested field expansion. Without braces, you get only the creative ID.</Accordion>
  <Accordion title="Large creative libraries">For accounts with 100+ creatives, paginate with `after` cursor from the response's `paging.cursors.after` field.</Accordion>
</AccordionGroup>

<CardGroup cols={2}>
  <Card title="Meta Ads Integration" icon="meta" href="/integrations/meta-ads">
    All Meta Ads jobs
  </Card>

  <Card title="Mave Agent" icon="brain" href="/features/mave-agent">
    AI research agent reference
  </Card>
</CardGroup>
