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

You’ve built Custom Audiences in Meta — lookalikes, website visitors, email lists, engagement-based — but you can’t test messaging against those segments without spending ad budget. This job exports Custom Audience definitions, maps each to a Mavera persona that mirrors the targeting criteria, then runs Focus Groups as if you’re interviewing your actual ad audience. Pre-test creative without burning a dollar.

Architecture

Code

import os, requests, time

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 Custom Audiences
audiences = requests.get(
    f"{GRAPH}/{ACCT}/customaudiences",
    params={
        "access_token": META,
        "fields": "id,name,description,subtype,approximate_count,data_source,delivery_status",
        "limit": 25,
    },
).json().get("data", [])

active_audiences = [a for a in audiences
    if a.get("delivery_status", {}).get("status") != "deleted"
    and a.get("approximate_count", 0) > 100]
print(f"Active audiences: {len(active_audiences)}")

# 2. Create mirrored personas
persona_map = []
for aud in active_audiences[:6]:
    subtype = aud.get("subtype", "CUSTOM")
    data_source = aud.get("data_source", {})
    source_type = data_source.get("type", "unknown")
    source_desc = data_source.get("sub_type", source_type)

    if subtype == "LOOKALIKE":
        persona_desc = (
            f"Lookalike audience: {aud['name']}. ~{aud.get('approximate_count',0):,} users. "
            f"Mirrors users similar to your seed audience. Likely shares demographic and "
            f"behavioral traits with your best customers but hasn't engaged with your brand yet."
        )
    elif subtype == "WEBSITE":
        persona_desc = (
            f"Website visitor audience: {aud['name']}. ~{aud.get('approximate_count',0):,} users. "
            f"Has visited your website — already aware of your product. "
            f"Evaluating or comparing options. Warm prospect mindset."
        )
    elif subtype == "ENGAGEMENT":
        persona_desc = (
            f"Engagement audience: {aud['name']}. ~{aud.get('approximate_count',0):,} users. "
            f"Has interacted with your social content. Aware and interested but not yet converted. "
            f"Responds to social proof and community-oriented messaging."
        )
    else:
        persona_desc = (
            f"Custom audience: {aud['name']}. Source: {source_desc}. "
            f"~{aud.get('approximate_count',0):,} users. {aud.get('description','No description.')}"
        )

    persona = requests.post(f"{MB}/personas", headers=MH, json={
        "name": f"Meta Audience: {aud['name'][:50]}",
        "description": persona_desc,
        "psychographic": {
            "audience_type": subtype.lower(),
            "awareness_level": "warm" if subtype in ("WEBSITE", "ENGAGEMENT") else "cold",
            "source": source_desc,
        },
    }).json()
    persona_map.append({
        "audience_id": aud["id"],
        "audience_name": aud["name"],
        "persona_id": persona["id"],
        "subtype": subtype,
        "size": aud.get("approximate_count", 0),
    })
    time.sleep(0.3)

# 3. Run Focus Group with ad creative stimulus
AD_CREATIVE = """
Headline: "Your Competitors Already Know This"
Body: "Teams using centralized analytics make decisions 40% faster. See how in 2 minutes."
CTA: Learn More
Visual: Split-screen showing cluttered dashboards vs clean single-pane view.
"""

fg = requests.post(f"{MB}/focus-groups", headers=MH, json={
    "name": "Custom Audience Creative Test",
    "persona_ids": [p["persona_id"] for p in persona_map],
    "questions": [
        f"You see this ad in your Instagram feed:\n\n{AD_CREATIVE}\n\nWhat's your first reaction?",
        "Does the headline create curiosity or feel clickbait-y? Why?",
        "Rate your likelihood to click (1-10). What would change your rating?",
        "What would this ad need to say to make you stop scrolling?",
        "If you clicked, what would you expect on the landing page?",
    ],
    "context": "Testing ad creative against different audience segments defined by Meta targeting.",
    "responses_per_persona": 2,
}).json()

# 4. Poll and display
for _ in range(24):
    time.sleep(5)
    data = requests.get(f"{MB}/focus-groups/{fg['id']}", headers=MH).json()
    if data.get("status") == "completed":
        break

print("\n=== Audience-Mirrored Focus Group ===")
for pm in persona_map:
    print(f"\n[{pm['audience_name']}] ({pm['subtype']}, {pm['size']:,} users)")
    audience_responses = [r for r in data.get("responses", [])
        if r.get("persona_id") == pm["persona_id"]]
    for resp in audience_responses[:3]:
        print(f"  Q: {resp.get('question','')[:60]}...")
        print(f"  A: {resp.get('answer','')[:200]}")

Example Output

=== Audience-Mirrored Focus Group ===

[High-Value Lookalike 1%] (LOOKALIKE, 2,400,000 users)
  Q: You see this ad: ... First reaction?
  A: The competitive angle is interesting but I need more specifics. "40% faster" — than what? 
     Show me who these teams are.

  Q: Likelihood to click (1-10)?
  A: 6. I'd click if the visual showed a real product, not a generic split-screen mockup.

[Website Visitors — Last 30 Days] (WEBSITE, 45,000 users)
  Q: First reaction?
  A: I've already been on your site. This headline doesn't tell me anything new. 
     Show me what's changed since I last visited or offer a specific deal.

  Q: What would make you stop scrolling?
  A: "Here's what you missed" or a time-limited trial offer. I'm warm — don't treat me 
     like a cold prospect.

[Email List — Existing Customers] (CUSTOM, 12,000 users)
  Q: Curiosity or clickbait?
  A: Clickbait for me — I'm already a customer. This should be an upsell or a feature 
     announcement, not a cold acquisition ad.

Error Handling

The customaudiences edge requires ads_read permission. Some audience types (Customer File) may be restricted by Business Manager privacy settings.
The approximate_count field is an estimate and may lag by 24–48 hours. Don’t use it for precise sizing.
Lookalike and Website audiences produce better personas because the behavioral signal is clearer. CRM-upload audiences need manual description enrichment.

All Meta Ads Jobs

Browse all Meta Ads integration jobs

Focus Groups

Full reference for synthetic focus groups