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 Meta ad account accumulates rich demographic data — age, gender, location, device, interest categories — but it lives in aggregate dashboard charts. This job pulls breakdowns from the Insights API, identifies your strongest audience segments, maps them to existing Mavera personas where possible, and creates custom personas for unmapped segments. The result is a persona library that mirrors your actual paid audience.

Architecture

Code

import os, requests, time
from collections import defaultdict

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 audience insights with demographic breakdowns
insights = requests.get(
    f"{GRAPH}/{ACCT}/insights",
    params={
        "access_token": META,
        "fields": "impressions,clicks,spend,actions",
        "breakdowns": "age,gender",
        "date_preset": "last_30d",
        "limit": 100,
    },
).json().get("data", [])

# 2. Score segments by conversion efficiency
segments = []
for row in insights:
    conversions = 0
    for action in row.get("actions", []):
        if action.get("action_type") in ("offsite_conversion", "lead", "purchase"):
            conversions += int(action.get("value", 0))

    segments.append({
        "age": row.get("age", "unknown"),
        "gender": row.get("gender", "unknown"),
        "impressions": int(row.get("impressions", 0)),
        "clicks": int(row.get("clicks", 0)),
        "spend": float(row.get("spend", 0)),
        "conversions": conversions,
        "ctr": int(row.get("clicks", 0)) / max(int(row.get("impressions", 1)), 1),
        "cpa": float(row.get("spend", 0)) / max(conversions, 1),
    })

top_segments = sorted(segments, key=lambda s: s["conversions"], reverse=True)[:10]

# 3. Pull location breakdown separately
geo_insights = requests.get(
    f"{GRAPH}/{ACCT}/insights",
    params={
        "access_token": META,
        "fields": "impressions,clicks,spend",
        "breakdowns": "country",
        "date_preset": "last_30d",
        "limit": 20,
    },
).json().get("data", [])
top_countries = sorted(geo_insights, key=lambda g: int(g.get("clicks", 0)), reverse=True)[:5]
country_list = [g.get("country", "Unknown") for g in top_countries]

# 4. Fetch existing Mavera personas
existing = requests.get(f"{MB}/personas", headers=MH).json()
existing_names = {p.get("name", "").lower(): p for p in (existing if isinstance(existing, list) else [])}

# 5. Map or create personas
created, mapped = [], []
for seg in top_segments:
    name = f"Meta {seg['gender'].title()} {seg['age']}"
    search_key = name.lower()

    if search_key in existing_names:
        mapped.append({"name": name, "id": existing_names[search_key]["id"], "action": "mapped"})
        continue

    desc = (
        f"Meta Ads audience segment: {seg['gender']}, age {seg['age']}. "
        f"30-day stats: {seg['impressions']:,} impressions, {seg['clicks']:,} clicks, "
        f"CTR {seg['ctr']:.2%}, {seg['conversions']} conversions, CPA ${seg['cpa']:.2f}. "
        f"Top markets: {', '.join(country_list[:3])}."
    )

    r = requests.post(f"{MB}/personas", headers=MH, json={
        "name": name,
        "description": desc,
        "demographic": {
            "age_range": seg["age"],
            "gender": seg["gender"],
            "countries": country_list[:3],
        },
        "psychographic": {
            "conversion_propensity": "high" if seg["conversions"] > 10 else "medium",
            "engagement_level": "high" if seg["ctr"] > 0.02 else "moderate",
        },
    })
    r.raise_for_status()
    created.append({"name": name, "id": r.json()["id"], "action": "created"})
    time.sleep(0.3)

print(f"Mapped: {len(mapped)} existing | Created: {len(created)} new")
for p in mapped + created:
    print(f"  [{p['action'].upper()}] {p['name']}{p['id']}")

Example Output

{
  "mapped": 3,
  "created": 7,
  "personas": [
    { "action": "mapped", "name": "Meta Female 25-34", "id": "per_existing_1" },
    { "action": "created", "name": "Meta Male 35-44", "id": "per_meta_m35_2",
      "stats": { "impressions": 245000, "ctr": "3.2%", "conversions": 89, "cpa": "$12.40" } },
    { "action": "created", "name": "Meta Female 18-24", "id": "per_meta_f18_3",
      "stats": { "impressions": 180000, "ctr": "4.1%", "conversions": 67, "cpa": "$9.80" } }
  ],
  "top_countries": ["US", "UK", "CA", "AU", "DE"]
}

Error Handling

Insights require at least one active campaign in the date range. Use date_preset=last_90d for broader coverage. Verify the ad account has had spend.
Some breakdowns can’t be combined (e.g., age,gender works but age,placement may not). Check Meta’s breakdown matrix.
The actions field contains different action types per objective. Filter for your conversion action (e.g., purchase, lead, offsite_conversion).

Meta Ads Integration

All Meta Ads jobs

Personas

Creating and managing personas