Scenario
The same video creative performs differently on Facebook Feed, Instagram Stories, and Audience Network. You’re making budget allocation decisions in the dark. This job takes one creative, runs separate Video Analyses for each placement context, then uses Mave to compare placement-specific scores and recommend edits — aspect ratio crops, hook timing adjustments, CTA overlay placement — for each surface.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 performance by placement for recent ads
placement_data = requests.get(
f"{GRAPH}/{ACCT}/insights",
params={
"access_token": META,
"fields": "ad_id,ad_name,impressions,clicks,ctr,cpc,spend,actions",
"breakdowns": "publisher_platform,platform_position",
"level": "ad",
"date_preset": "last_30d",
"limit": 200,
},
).json().get("data", [])
# 2. Group by ad, find ads with multi-placement data
from collections import defaultdict
ad_placements = defaultdict(list)
for row in placement_data:
ad_placements[row["ad_id"]].append({
"platform": row.get("publisher_platform", "unknown"),
"position": row.get("platform_position", "unknown"),
"impressions": int(row.get("impressions", 0)),
"clicks": int(row.get("clicks", 0)),
"ctr": float(row.get("ctr", 0)),
"spend": float(row.get("spend", 0)),
"name": row.get("ad_name", ""),
})
multi_placement = {k: v for k, v in ad_placements.items() if len(v) >= 2}
print(f"Ads with 2+ placements: {len(multi_placement)}")
# 3. Pick the top ad by total spend and analyze
if not multi_placement:
print("No multi-placement ads found.")
exit()
target_ad_id = max(multi_placement, key=lambda k: sum(p["spend"] for p in multi_placement[k]))
placements = multi_placement[target_ad_id]
ad_name = placements[0]["name"]
print(f"Analyzing: {ad_name} across {len(placements)} placements")
# 4. Get the creative video
ad_detail = requests.get(
f"{GRAPH}/{target_ad_id}",
params={"access_token": META, "fields": "creative{video_id,title,body}"},
).json()
vid = ad_detail.get("creative", {}).get("video_id")
if not vid:
print("Selected ad has no video creative.")
exit()
video_info = requests.get(f"{GRAPH}/{vid}",
params={"access_token": META, "fields": "source,length"}).json()
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()
# 5. Run Video Analysis for each placement context
placement_analyses = []
for pl in placements:
label = f"{pl['platform']} — {pl['position']}"
with open(tmp.name, "rb") as f:
asset = requests.post(f"{MB}/assets",
headers={"Authorization": f"Bearer {MV}"},
files={"file": (f"{vid}_{pl['platform']}_{pl['position']}.mp4", f, "video/mp4")}).json()
analysis = requests.post(f"{MB}/video-analyses", headers=MH, json={
"asset_id": asset["id"],
"name": f"{ad_name} — {label}",
}).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":
placement_analyses.append({
"placement": label,
"platform": pl["platform"],
"position": pl["position"],
"ctr": pl["ctr"],
"spend": pl["spend"],
"scores": status.get("scores", {}),
})
time.sleep(1)
os.unlink(tmp.name)
# 6. Mave comparison
analysis_summary = "\n".join(
f"- {pa['placement']}: CTR={pa['ctr']:.2f}%, spend=${pa['spend']:.0f}, "
f"emotional={pa['scores'].get('emotional','?')}, cognitive={pa['scores'].get('cognitive','?')}, "
f"behavioral={pa['scores'].get('behavioral','?')}"
for pa in placement_analyses
)
comparison = requests.post(f"{MB}/mave/chat", headers=MH, json={
"message": f"""Compare this video creative's performance across Meta placements.
CREATIVE: "{ad_name}" ({video_info.get('length',0)}s video)
PLACEMENT ANALYSIS:
{analysis_summary}
For each placement:
1. Why does performance differ? (viewing context, user intent, format fit)
2. Specific edits for this placement (aspect ratio, hook timing, CTA overlay position)
3. Budget reallocation recommendation based on efficiency
4. Which placement should get the most budget and why
5. Should any placement get a dedicated creative variant?"""
}).json()
print("\n=== Cross-Platform Creative Analysis ===")
print(comparison.get("content", ""))
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. Placement breakdown
const placementData = await fetch(
`${GRAPH}/${ACCT}/insights?access_token=${META}&fields=ad_id,ad_name,impressions,clicks,ctr,cpc,spend,actions&breakdowns=publisher_platform,platform_position&level=ad&date_preset=last_30d&limit=200`
).then(r => r.json()).then(d => d.data || []);
// 2. Group by ad
const adPlacements = {};
for (const row of placementData) {
(adPlacements[row.ad_id] ??= []).push({
platform: row.publisher_platform || "unknown",
position: row.platform_position || "unknown",
impressions: parseInt(row.impressions || "0"),
clicks: parseInt(row.clicks || "0"),
ctr: parseFloat(row.ctr || "0"),
spend: parseFloat(row.spend || "0"),
name: row.ad_name || "",
});
}
const multiPlacement = Object.entries(adPlacements).filter(([, v]) => v.length >= 2);
console.log(`Ads with 2+ placements: ${multiPlacement.length}`);
if (!multiPlacement.length) { console.log("No multi-placement ads."); process.exit(); }
// 3. Pick top ad
const [targetAdId, placements] = multiPlacement.sort(
([, a], [, b]) => b.reduce((s, p) => s + p.spend, 0) - a.reduce((s, p) => s + p.spend, 0)
)[0];
const adName = placements[0].name;
console.log(`Analyzing: ${adName} across ${placements.length} placements`);
// 4. Get video
const adDetail = await fetch(
`${GRAPH}/${targetAdId}?access_token=${META}&fields=creative{video_id,title,body}`
).then(r => r.json());
const vid = adDetail.creative?.video_id;
if (!vid) { console.log("No video creative."); process.exit(); }
const videoInfo = await fetch(
`${GRAPH}/${vid}?access_token=${META}&fields=source,length`
).then(r => r.json());
const vidBuffer = Buffer.from(await fetch(videoInfo.source).then(r => r.arrayBuffer()));
// 5. Analyze per placement
const placementAnalyses = [];
for (const pl of placements) {
const label = `${pl.platform} — ${pl.position}`;
const form = new FormData();
form.append("file", new Blob([vidBuffer], { type: "video/mp4" }), `${vid}_${pl.platform}_${pl.position}.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: `${adName} — ${label}` }),
}).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") {
placementAnalyses.push({
placement: label, platform: pl.platform, position: pl.position,
ctr: pl.ctr, spend: pl.spend, scores: status.scores || {},
});
}
await new Promise(r => setTimeout(r, 1000));
}
// 6. Mave comparison
const summary = placementAnalyses.map(pa =>
`- ${pa.placement}: CTR=${pa.ctr.toFixed(2)}%, spend=$${pa.spend.toFixed(0)}, emotional=${pa.scores.emotional ?? "?"}, cognitive=${pa.scores.cognitive ?? "?"}, behavioral=${pa.scores.behavioral ?? "?"}`
).join("\n");
const comparison = await fetch(`${MB}/mave/chat`, {
method: "POST", headers: MH,
body: JSON.stringify({
message: `Compare "${adName}" across placements:\n\n${summary}\n\nFor each: 1) Why different 2) Specific edits 3) Budget reallocation 4) Best placement 5) Need dedicated variant?`,
}),
}).then(r => r.json());
console.log("\n=== Cross-Platform Analysis ===");
console.log(comparison.content || "");
Example Output
=== Cross-Platform Creative Analysis ===
## Placement Performance
| Placement | CTR | Emotional | Behavioral | Verdict |
|-----------|-----|-----------|------------|---------|
| Facebook Feed | 2.1% | 7.5 | 7.2 | Strong — scale |
| Instagram Stories | 3.4% | 8.8 | 8.1 | Best — double budget |
| Audience Network | 0.8% | 4.2 | 3.1 | Weak — pause or rework |
## Why Performance Differs
- **Stories** wins because the full-screen vertical format maximizes emotional impact.
The hook at 0:03 fills the viewport — no competing content.
- **Feed** performs well but the 16:9 crop loses the bottom CTA overlay. Horizontal
crops are shown smaller in-feed.
- **Audience Network** has low-intent placements (interstitials, banner slots).
The creative wasn't designed for these contexts.
## Recommendations
1. **Stories:** Create a 9:16 native cut. Move CTA to 0:08 (swipe-up zone).
Add text overlay for sound-off viewing. Allocate 50% of budget here.
2. **Feed:** Add 1:1 square crop variant. Pin CTA as persistent text overlay
(many Feed users scroll without tapping). Allocate 40%.
3. **Audience Network:** Pause unless CPA is acceptable. If keeping, create
a static 300x250 companion with the strongest frame as hero image. 10% max.
Error Handling
Same video, different analysis
Same video, different analysis
Uploading the same video file multiple times to Mavera creates separate assets. This is intentional — each analysis can capture placement-specific context in the name and metadata.
Placement breakdown returns many rows
Placement breakdown returns many rows
A single ad across 6 placements × 30 days = 180 rows. Use
date_preset=last_7d for faster iteration during testing.Audience Network data is sparse
Audience Network data is sparse
Low-impression Audience Network placements produce unreliable CTR. Filter placements with fewer than 1,000 impressions.
All Meta Ads Jobs
Browse all Meta Ads integration jobs
Video Analysis
Full guide to Mavera Video Analysis