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

# Cross-Creative Performance Leaderboard

> Cross-reference Mavera behavioral scores against TikTok performance metrics to build a predictive creative leaderboard.

### Scenario

You're spending \$50K/month across 30+ TikTok creatives but don't know which ones *should* be winning. This job pulls all creatives with their TikTok performance metrics (CTR, CVR, CPA), runs Video Analysis on each, then builds a leaderboard that cross-references Mavera's behavioral scores (hook, emotion, pacing) against actual performance. The result is a predictive scoring model: find high-behavioral-score / low-spend creatives to scale, and low-score / high-spend creatives to kill.

### Architecture

```mermaid theme={"dark"}
flowchart LR
    A["TikTok POST /reports/integrated/get/"] --> B["Mavera Video Analysis per creative"]
    B --> C["Cross-reference behavioral scores vs CTR"]
    C --> D["Ranked leaderboard"]
```

### Code

<CodeGroup>
  ```python Python theme={"dark"}
  import os, requests, time, tempfile

  TT = os.environ["TIKTOK_ACCESS_TOKEN"]
  ADV = os.environ["TIKTOK_ADVERTISER_ID"]
  MV = os.environ["MAVERA_API_KEY"]
  TT_BASE = "https://business-api.tiktok.com/open_api/v1.3"
  MV_BASE = "https://app.mavera.io/api/v1"
  TT_H = {"Access-Token": TT}
  MV_H = {"Authorization": f"Bearer {MV}", "Content-Type": "application/json"}

  # 1. Pull integrated report — creative-level performance
  report = requests.post(f"{TT_BASE}/reports/integrated/get/",
      headers=TT_H,
      json={
          "advertiser_id": ADV,
          "report_type": "BASIC",
          "data_level": "AUCTION_AD",
          "dimensions": ["ad_id"],
          "metrics": ["ad_name", "impressions", "clicks", "ctr", "conversion", "cost_per_conversion",
                       "spend", "video_play_actions", "video_watched_2s", "video_watched_6s",
                       "average_video_play_per_user"],
          "start_date": "2025-01-01",
          "end_date": "2025-12-31",
          "page_size": 50,
          "page": 1,
      }).json()

  if report.get("code") != 0:
      raise SystemExit(f"TikTok report error: {report.get('message')}")

  rows = report.get("data", {}).get("list", [])

  # 2. For each creative, fetch video and run analysis
  leaderboard = []
  for row in rows[:15]:
      metrics = row.get("metrics", {})
      dims = row.get("dimensions", {})
      ad_id = dims.get("ad_id", "")
      impressions = int(metrics.get("impressions", 0))
      if impressions < 500:
          continue

      # Fetch ad details for video URL
      ad_detail = requests.get(f"{TT_BASE}/ad/get/",
          headers=TT_H,
          params={"advertiser_id": ADV, "filtering": f'{{"ad_ids": ["{ad_id}"]}}',
                  "fields": '["video_id", "ad_name"]'}).json()
      ad_info = (ad_detail.get("data", {}).get("list") or [{}])[0]
      video_id = ad_info.get("video_id")
      if not video_id:
          continue

      vid_info = requests.get(f"{TT_BASE}/file/video/ad/info/",
          headers=TT_H,
          params={"advertiser_id": ADV, "video_ids": f'["{video_id}"]'}).json()
      video_url = (vid_info.get("data", {}).get("list") or [{}])[0].get("video_url", "")
      if not video_url:
          continue

      vid_bytes = requests.get(video_url).content
      with tempfile.NamedTemporaryFile(suffix=".mp4", delete=False) as tmp:
          tmp.write(vid_bytes); tmp_path = tmp.name

      upload = requests.post(f"{MV_BASE}/assets",
          headers={"Authorization": f"Bearer {MV}"},
          files={"file": (f"ad_{ad_id}.mp4", open(tmp_path, "rb"), "video/mp4")}).json()

      analysis = requests.post(f"{MV_BASE}/video-analysis", headers=MV_H, json={
          "asset_id": upload["id"],
          "analysis_types": ["hook_score", "emotional_arc", "pacing", "cognitive_load"],
      }).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

      results = status.get("results", {})
      hook = results.get("hook_score", {}).get("score", 0)
      emotion = results.get("emotional_arc", {}).get("intensity_avg", 0)
      ctr = float(metrics.get("ctr", 0))
      behavioral_score = (hook * 0.4 + emotion * 6 * 0.3 + (10 - results.get("cognitive_load", {}).get("average", 5)) * 10 * 0.3)

      leaderboard.append({
          "ad_id": ad_id,
          "ad_name": metrics.get("ad_name", ad_info.get("ad_name", "")),
          "impressions": impressions,
          "ctr": ctr, "spend": float(metrics.get("spend", 0)),
          "hook_score": hook, "emotion_avg": emotion,
          "behavioral_score": round(behavioral_score, 1),
          "verdict": "SCALE" if behavioral_score > 60 and ctr > 1.5 else
                     "TEST" if behavioral_score > 60 else
                     "KILL" if behavioral_score < 40 and ctr < 1.0 else "HOLD",
      })
      os.unlink(tmp_path)
      time.sleep(1)

  # 3. Print leaderboard
  leaderboard.sort(key=lambda x: -x["behavioral_score"])
  print(f"{'Rank':<5} {'Ad Name':<30} {'Hook':<6} {'Behavioral':<12} {'CTR':<8} {'Spend':<10} {'Verdict'}")
  print("-" * 85)
  for i, lb in enumerate(leaderboard, 1):
      print(f"{i:<5} {lb['ad_name'][:28]:<30} {lb['hook_score']:<6} {lb['behavioral_score']:<12} {lb['ctr']:.2f}%{'':<4} ${lb['spend']:>8,.0f}  {lb['verdict']}")
  ```

  ```javascript JavaScript theme={"dark"}
  const TT = process.env.TIKTOK_ACCESS_TOKEN;
  const ADV = process.env.TIKTOK_ADVERTISER_ID;
  const MV = process.env.MAVERA_API_KEY;
  const TT_BASE = "https://business-api.tiktok.com/open_api/v1.3";
  const MV_BASE = "https://app.mavera.io/api/v1";
  const TT_H = { "Access-Token": TT };
  const MV_H = { Authorization: `Bearer ${MV}`, "Content-Type": "application/json" };

  // 1. Pull integrated report
  const report = await fetch(`${TT_BASE}/reports/integrated/get/`, {
    method: "POST", headers: { ...TT_H, "Content-Type": "application/json" },
    body: JSON.stringify({
      advertiser_id: ADV, report_type: "BASIC", data_level: "AUCTION_AD",
      dimensions: ["ad_id"],
      metrics: ["ad_name", "impressions", "clicks", "ctr", "conversion",
                "cost_per_conversion", "spend", "video_play_actions",
                "video_watched_2s", "video_watched_6s", "average_video_play_per_user"],
      start_date: "2025-01-01", end_date: "2025-12-31", page_size: 50, page: 1,
    }),
  }).then(r => r.json());

  if (report.code !== 0) throw new Error(`TikTok: ${report.message}`);
  const rows = report.data?.list || [];

  // 2. Analyze each creative
  const leaderboard = [];
  for (const row of rows.slice(0, 15)) {
    const metrics = row.metrics || {};
    const adId = row.dimensions?.ad_id || "";
    const impressions = parseInt(metrics.impressions || "0", 10);
    if (impressions < 500) continue;

    const adDetail = await fetch(
      `${TT_BASE}/ad/get/?advertiser_id=${ADV}&filtering=${encodeURIComponent(`{"ad_ids":["${adId}"]}`)}` +
      `&fields=${encodeURIComponent('["video_id","ad_name"]')}`,
      { headers: TT_H }
    ).then(r => r.json());
    const videoId = adDetail.data?.list?.[0]?.video_id;
    if (!videoId) continue;

    const vidInfo = await fetch(
      `${TT_BASE}/file/video/ad/info/?advertiser_id=${ADV}&video_ids=${encodeURIComponent(`["${videoId}"]`)}`,
      { headers: TT_H }
    ).then(r => r.json());
    const videoUrl = vidInfo.data?.list?.[0]?.video_url;
    if (!videoUrl) continue;

    const vidBytes = await fetch(videoUrl).then(r => r.arrayBuffer());
    const formData = new FormData();
    formData.append("file", new Blob([vidBytes], { type: "video/mp4" }), `ad_${adId}.mp4`);
    const upload = await fetch(`${MV_BASE}/assets`, {
      method: "POST", headers: { Authorization: `Bearer ${MV}` }, body: formData,
    }).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: ["hook_score", "emotional_arc", "pacing", "cognitive_load"],
      }),
    }).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 results = status.results || {};
    const hook = results.hook_score?.score || 0;
    const emotion = results.emotional_arc?.intensity_avg || 0;
    const cogLoad = results.cognitive_load?.average || 5;
    const ctr = parseFloat(metrics.ctr || "0");
    const behavioral = hook * 0.4 + emotion * 6 * 0.3 + (10 - cogLoad) * 10 * 0.3;

    leaderboard.push({
      ad_id: adId, ad_name: metrics.ad_name || "",
      impressions, ctr, spend: parseFloat(metrics.spend || "0"),
      hook_score: hook, emotion_avg: emotion,
      behavioral_score: parseFloat(behavioral.toFixed(1)),
      verdict: behavioral > 60 && ctr > 1.5 ? "SCALE" :
               behavioral > 60 ? "TEST" :
               behavioral < 40 && ctr < 1.0 ? "KILL" : "HOLD",
    });
    await new Promise(r => setTimeout(r, 1000));
  }

  // 3. Leaderboard
  leaderboard.sort((a, b) => b.behavioral_score - a.behavioral_score);
  console.log("Rank  Ad Name                       Hook  Behavioral  CTR      Spend     Verdict");
  console.log("-".repeat(85));
  leaderboard.forEach((lb, i) =>
    console.log(`${i+1}     ${lb.ad_name.slice(0,28).padEnd(30)} ${String(lb.hook_score).padEnd(6)} ${String(lb.behavioral_score).padEnd(12)} ${lb.ctr.toFixed(2)}%    $${lb.spend.toLocaleString().padStart(8)}  ${lb.verdict}`)
  );
  ```
</CodeGroup>

### Example Output

```text theme={"dark"}
Rank  Ad Name                       Hook  Behavioral  CTR      Spend     Verdict
-------------------------------------------------------------------------------------
1     UGC Creator — Summer Haul     91    82.4        2.31%    $4,200    SCALE
2     Founder Story — Behind Scene  78    71.0        1.82%    $2,100    SCALE
3     Product Demo — Cinematic      85    68.3        0.94%    $800      TEST
4     Transition Edit — Before/Aft  62    55.1        1.45%    $3,600    HOLD
5     Logo Intro — Brand Awareness  28    31.7        0.52%    $8,400    KILL

Insights:
- "Product Demo — Cinematic" has strong behavioral score (68.3) but low spend.
  Recommendation: Increase budget — behavioral metrics predict CTR upside.
- "Logo Intro — Brand Awareness" burns $8.4K with weak hook (28/100).
  Recommendation: Kill or re-cut with UGC-style opening.
```

### Error Handling

<AccordionGroup>
  <Accordion title="Report API pagination">TikTok's integrated report endpoint paginates at `page_size` max 200. For large accounts, loop with `page` increments until `page_info.total_number` is exhausted.</Accordion>
  <Accordion title="Video Analysis timeout">Long-form TikTok ads (60s+) may take 45s+ to analyze. The 90s poll loop handles this. For batch jobs with 30+ creatives, parallelize with `asyncio.gather` or `Promise.all`.</Accordion>
  <Accordion title="Score calibration">Behavioral score weights (hook 40%, emotion 30%, cognitive load 30%) are starting points. Calibrate against your own CTR data after 50+ creatives to build a custom regression model.</Accordion>
</AccordionGroup>

***

<CardGroup cols={2}>
  <Card title="All TikTok jobs" icon="tiktok" href="/integrations/tiktok" />

  <Card title="Video Analysis" icon="video" href="/features/video-analysis" />
</CardGroup>
