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

Your TikTok ad account has dozens of video creatives, but you only see surface metrics — impressions, CTR, CPM. This job pulls video ad URLs, uploads them to Mavera Assets, and runs Video Analysis to extract behavioral intelligence that TikTok doesn’t provide: hook scoring (first 3 seconds), emotional arc across the full duration, and cognitive load at each cut point. The result is a creative diagnostic that tells you why an ad works, not just that it works.

Architecture

Code

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 active video ads
r = requests.get(f"{TT_BASE}/ad/get/",
    headers=TT_H,
    params={
        "advertiser_id": ADV,
        "filtering": '{"status": "AD_STATUS_DELIVERY_OK", "creative_type": "VIDEO"}',
        "fields": '["ad_id", "ad_name", "video_id", "image_ids", "landing_page_url"]',
        "page_size": 20,
    })
r.raise_for_status()
data = r.json()
if data.get("code") != 0:
    raise SystemExit(f"TikTok API error: {data.get('message')}")
ads = data.get("data", {}).get("list", [])

# 2. Get video URLs for each ad
video_analyses = []
for ad in ads[:10]:
    video_id = ad.get("video_id")
    if not video_id:
        continue

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

    video_url = vid_data[0].get("video_url") or vid_data[0].get("preview_url", "")
    if not video_url:
        continue

    # 3. Download and upload to Mavera
    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_name']}.mp4", open(tmp_path, "rb"), "video/mp4")},
    ).json()

    # 4. Run Video Analysis
    analysis = requests.post(f"{MV_BASE}/video-analysis", headers=MV_H, json={
        "asset_id": upload["id"],
        "analysis_types": ["hook_score", "emotional_arc", "cognitive_load", "visual_complexity", "pacing"],
        "metadata": {"source": "tiktok", "ad_id": ad["ad_id"], "ad_name": ad["ad_name"]},
    }).json()

    # 5. Poll for completion
    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

    video_analyses.append({
        "ad_id": ad["ad_id"],
        "ad_name": ad["ad_name"],
        "hook_score": status.get("results", {}).get("hook_score", {}),
        "emotional_arc": status.get("results", {}).get("emotional_arc", {}),
        "cognitive_load": status.get("results", {}).get("cognitive_load", {}),
    })
    os.unlink(tmp_path)
    time.sleep(1)

# 6. Output scorecard
for va in video_analyses:
    hook = va["hook_score"]
    print(f"\n{'='*50}")
    print(f"AD: {va['ad_name']} ({va['ad_id']})")
    print(f"  Hook Score: {hook.get('score', 'N/A')}/100 — {hook.get('assessment', '')}")
    print(f"  Emotional Peak: {va['emotional_arc'].get('peak_emotion', 'N/A')} at {va['emotional_arc'].get('peak_timestamp', 'N/A')}s")
    print(f"  Avg Cognitive Load: {va['cognitive_load'].get('average', 'N/A')}/10")

Example Output

==================================================
AD: Summer Sale — UGC Creator (ad_12345)
  Hook Score: 87/100 — Strong opening: text overlay + face close-up in first 0.8s
  Emotional Peak: excitement at 4.2s
  Avg Cognitive Load: 3.8/10
==================================================
AD: Product Demo — B-Roll (ad_12346)
  Hook Score: 42/100 — Slow start: logo animation for first 2.1s loses attention
  Emotional Peak: curiosity at 8.5s
  Avg Cognitive Load: 6.2/10
==================================================
AD: Founder Story (ad_12347)
  Hook Score: 71/100 — Direct eye contact + bold claim in first 1.2s
  Emotional Peak: trust at 12.0s
  Avg Cognitive Load: 4.1/10

Error Handling

TikTok returns code: 0 for success. Non-zero codes include 40001 (auth failure), 40100 (permission denied), and 40002 (invalid params). Always check the code field, not just HTTP status.
Some ad video URLs are CDN-signed and expire. If download fails, re-fetch the video info endpoint for a fresh URL.
TikTok ads over 60s or high-resolution may take 30s+ to upload. The poll loop allows 90s. Increase for longer creatives.

All TikTok jobs

Video Analysis