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

TikTok’s audience skews young, but your ads target different generational segments. This job pulls audience demographic data from your campaigns, maps age brackets to Gen Z, Millennial, and Gen Alpha personas with platform-native behaviors, then runs a Focus Group testing your ad concepts before you commit production budget. You get generational feedback calibrated to TikTok’s unique content culture.

Architecture

Code

import os, requests, time

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 audience demographics
demo = requests.post(f"{TT_BASE}/reports/integrated/get/",
    headers=TT_H,
    json={
        "advertiser_id": ADV,
        "report_type": "AUDIENCE",
        "data_level": "AUCTION_ADVERTISER",
        "dimensions": ["age", "gender"],
        "metrics": ["impressions", "clicks", "spend", "conversion"],
        "start_date": "2025-01-01", "end_date": "2025-12-31",
        "page_size": 50,
    }).json()

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

age_data = {}
for row in demo.get("data", {}).get("list", []):
    age = row.get("dimensions", {}).get("age", "Unknown")
    m = row.get("metrics", {})
    existing = age_data.get(age, {"impressions": 0, "clicks": 0, "spend": 0})
    age_data[age] = {
        "impressions": existing["impressions"] + int(m.get("impressions", 0)),
        "clicks": existing["clicks"] + int(m.get("clicks", 0)),
        "spend": existing["spend"] + float(m.get("spend", 0)),
    }

# 2. Map age brackets to generational personas
GEN_MAP = {
    "AGE_13_17": {"gen": "Gen Alpha/Z", "desc": "13-17. Digital native. Discovers brands through TikTok, values authenticity and humor. Skeptical of polished ads. Influenced by creators, not brands."},
    "AGE_18_24": {"gen": "Gen Z", "desc": "18-24. Core TikTok demographic. Values transparency, social impact, and UGC. Short attention span. Responds to trends, not traditional marketing."},
    "AGE_25_34": {"gen": "Millennial", "desc": "25-34. Established spending power. Values convenience and brand story. Uses TikTok for discovery. Responds to aspirational + practical content."},
    "AGE_35_44": {"gen": "Elder Millennial", "desc": "35-44. Decision-makers and parents. Uses TikTok as entertainment. Responds to relatable, informational content. Less trend-driven."},
    "AGE_45_54": {"gen": "Gen X", "desc": "45-54. Fastest-growing TikTok segment. Values expertise and straightforward messaging. Skeptical but engaged when content is relevant."},
}

persona_ids = []
for age_bracket, gen_info in GEN_MAP.items():
    stats = age_data.get(age_bracket, {})
    if stats.get("impressions", 0) < 100:
        continue
    ctr = (stats["clicks"] / stats["impressions"] * 100) if stats["impressions"] else 0

    p = requests.post(f"{MV_BASE}/personas", headers=MV_H, json={
        "name": f"TikTok: {gen_info['gen']}",
        "description": f"{gen_info['desc']} Campaign data: {stats['impressions']:,} impressions, {ctr:.2f}% CTR, ${stats['spend']:,.0f} spend.",
        "demographic": {"age_range": age_bracket, "platform": "TikTok"},
        "psychographic": {"content_preferences": "short-form video", "generation": gen_info["gen"]},
    }).json()
    persona_ids.append({"id": p["id"], "gen": gen_info["gen"], "ctr": ctr})
    time.sleep(0.3)

# 3. Run Focus Group with ad concepts
AD_CONCEPTS = """
CONCEPT A — "Day in My Life" UGC:
Creator shows morning routine using your product. Lo-fi aesthetic, trending audio, text overlays.

CONCEPT B — "3 Things You Didn't Know":
Founder on camera, fast cuts, educational hook. "Stop scrolling if you..." opener.

CONCEPT C — "Transition Reveal":
Before/after transformation. Product reveal at the cut. Trending transition effect.

CONCEPT D — "Stitch Response":
Stitches a popular creator's question. Authentic, conversational, no script.
"""

fg = requests.post(f"{MV_BASE}/focus-groups", headers=MV_H, json={
    "name": "TikTok Ad Concept — Generational Testing",
    "persona_ids": [p["id"] for p in persona_ids],
    "questions": [
        f"Review these TikTok ad concepts:\n\n{AD_CONCEPTS}\n\nWhich concept would stop your scroll? Why?",
        "Which concept feels most authentic to TikTok? Which feels like an ad?",
        "Would you share any of these with friends? Which one and why?",
        "What trending format or audio would make you more likely to engage?",
    ],
    "responses_per_persona": 2,
}).json()

for _ in range(20):
    time.sleep(5)
    data = requests.get(f"{MV_BASE}/focus-groups/{fg['id']}", headers=MV_H).json()
    if data.get("status") == "completed": break

for resp in data.get("responses", []):
    gen = next((p["gen"] for p in persona_ids if p["id"] == resp.get("persona_id")), "?")
    print(f"[{gen}] {resp.get('question','')[:60]}...")
    print(f"  → {resp.get('answer','')[:250]}\n")

Example Output

[Gen Z] Which concept would stop your scroll? Why?...
  → Concept D — Stitch Response. It feels like actual TikTok content, not an ad.
    The conversational format matches how I discover products. If the stitched
    creator is someone I follow, I'd definitely watch to the end.

[Millennial] Which feels most authentic to TikTok?...
  → Concept A feels most native. The "Day in My Life" format is what I watch
    already. Concept B feels like a YouTube ad someone repurposed for TikTok —
    I'd scroll past. Concept C depends entirely on the transition quality.

[Gen X] Would you share any with friends?...
  → I'd share Concept B if the information was genuinely surprising. I don't
    share UGC-style content but I do share "did you know" posts. Make the
    facts verifiable and I'll forward it.

[Gen Alpha/Z] What trending format would make you engage?...
  → Green screen format with a controversial take. Or the "POV" format where
    you role-play a situation. Also, use a trending sound — not the original,
    the remix version that's actually trending.

Error Handling

TikTok may not report all age brackets if your campaigns exclude certain ages. Check your targeting settings if brackets return zero data.
Audience-level reports require the Audience Management permission scope. Without it, the endpoint returns code: 40100.
Age brackets with fewer than 100 impressions are filtered out to avoid unreliable CTR calculations. Extend the date range for newer campaigns.

All TikTok jobs

Focus Groups