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

# Custom Audience → Focus Group Mirror

> Export Custom Audience definitions from Meta, create mirrored Mavera personas matching each targeting criteria, then run Focus Groups to pre-test creative without ad spend.

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

```mermaid theme={"dark"}
flowchart LR
    A[Meta GET customaudiences] --> B[Extract definitions] --> C["POST /api/v1/personas (mirror audience)"] --> D["POST /api/v1/focus-groups"] --> E[Targeting-mirrored feedback]
```

### Code

<CodeGroup>
  ```python Python theme={"dark"}
  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]}")
  ```

  ```javascript JavaScript theme={"dark"}
  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. Pull Custom Audiences
  const audiences = await fetch(
    `${GRAPH}/${ACCT}/customaudiences?access_token=${META}&fields=id,name,description,subtype,approximate_count,data_source,delivery_status&limit=25`
  ).then(r => r.json()).then(d => d.data || []);

  const activeAudiences = audiences
    .filter(a => a.delivery_status?.status !== "deleted" && (a.approximate_count || 0) > 100)
    .slice(0, 6);
  console.log(`Active audiences: ${activeAudiences.length}`);

  // 2. Create mirrored personas
  const personaMap = [];
  for (const aud of activeAudiences) {
    const subtype = aud.subtype || "CUSTOM";
    const sourceDesc = aud.data_source?.sub_type || aud.data_source?.type || "unknown";

    const descMap = {
      LOOKALIKE: `Lookalike: ${aud.name}. ~${(aud.approximate_count || 0).toLocaleString()} users. Similar to seed audience.`,
      WEBSITE: `Website visitors: ${aud.name}. ~${(aud.approximate_count || 0).toLocaleString()} users. Aware, evaluating.`,
      ENGAGEMENT: `Engaged users: ${aud.name}. ~${(aud.approximate_count || 0).toLocaleString()} users. Interested, not converted.`,
    };
    const desc = descMap[subtype] || `Custom: ${aud.name}. Source: ${sourceDesc}. ${aud.description || ""}`;

    const persona = await fetch(`${MB}/personas`, {
      method: "POST", headers: MH,
      body: JSON.stringify({
        name: `Meta Audience: ${(aud.name || "").slice(0, 50)}`,
        description: desc,
        psychographic: {
          audience_type: subtype.toLowerCase(),
          awareness_level: ["WEBSITE", "ENGAGEMENT"].includes(subtype) ? "warm" : "cold",
          source: sourceDesc,
        },
      }),
    }).then(r => r.json());

    personaMap.push({
      audience_id: aud.id, audience_name: aud.name,
      persona_id: persona.id, subtype, size: aud.approximate_count || 0,
    });
    await new Promise(r => setTimeout(r, 300));
  }

  // 3. Focus Group
  const AD_CREATIVE = `Headline: "Your Competitors Already Know This"\nBody: "Teams using centralized analytics make decisions 40% faster."\nCTA: Learn More`;

  const fg = await fetch(`${MB}/focus-groups`, {
    method: "POST", headers: MH,
    body: JSON.stringify({
      name: "Custom Audience Creative Test",
      persona_ids: personaMap.map(p => p.persona_id),
      questions: [
        `You see this ad:\n\n${AD_CREATIVE}\n\nFirst reaction?`,
        "Curiosity or clickbait? Why?",
        "Likelihood to click (1-10)? What would change it?",
        "What would make you stop scrolling?",
        "If you clicked, what do you expect on the landing page?",
      ],
      context: "Testing creative against Meta targeting segments.",
      responses_per_persona: 2,
    }),
  }).then(r => r.json());

  // 4. Poll
  let data;
  for (let i = 0; i < 24; i++) {
    await new Promise(r => setTimeout(r, 5000));
    data = await fetch(`${MB}/focus-groups/${fg.id}`, { headers: MH }).then(r => r.json());
    if (data.status === "completed") break;
  }

  console.log("\n=== Audience-Mirrored Focus Group ===");
  for (const pm of personaMap) {
    console.log(`\n[${pm.audience_name}] (${pm.subtype}, ${pm.size.toLocaleString()} users)`);
    const resps = (data.responses || []).filter(r => r.persona_id === pm.persona_id);
    resps.slice(0, 3).forEach(r => {
      console.log(`  Q: ${(r.question || "").slice(0, 60)}...`);
      console.log(`  A: ${(r.answer || "").slice(0, 200)}`);
    });
  }
  ```
</CodeGroup>

### Example Output

```text theme={"dark"}
=== 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

<AccordionGroup>
  <Accordion title="Custom Audience permissions">The `customaudiences` edge requires `ads_read` permission. Some audience types (Customer File) may be restricted by Business Manager privacy settings.</Accordion>
  <Accordion title="Approximate count accuracy">The `approximate_count` field is an estimate and may lag by 24–48 hours. Don't use it for precise sizing.</Accordion>
  <Accordion title="Persona quality depends on audience definition">Lookalike and Website audiences produce better personas because the behavioral signal is clearer. CRM-upload audiences need manual description enrichment.</Accordion>
</AccordionGroup>

<CardGroup cols={2}>
  <Card title="All Meta Ads Jobs" icon="meta" href="/integrations/meta-ads">
    Browse all Meta Ads integration jobs
  </Card>

  <Card title="Focus Groups" icon="users" href="/features/focus-groups">
    Full reference for synthetic focus groups
  </Card>
</CardGroup>
