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

# Campaign-to-Content Pipeline

> Pull top-performing Meta ad campaigns by CTR, extract winning themes, create a Brand Voice, and generate long-form content that extends proven messaging across channels.

### Scenario

Your top-performing ad campaigns reveal what messaging resonates with your audience, but that intelligence stays siloed in Ads Manager. This job pulls the highest-CTR campaigns, extracts winning themes and copy patterns, establishes a Brand Voice from ad copy, then generates long-form content (blog posts, social posts) that extends those proven themes across channels.

### Architecture

```mermaid theme={"dark"}
flowchart LR
    A["Meta GET insights (by campaign, sort CTR)"] --> B[Extract themes] --> C["POST /api/v1/brand-voices"] --> D["POST /api/v1/generations"] --> E[Content library]
```

### 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 campaign insights sorted by CTR
  campaigns = requests.get(
      f"{GRAPH}/{ACCT}/insights",
      params={
          "access_token": META,
          "fields": "campaign_name,campaign_id,impressions,clicks,ctr,conversions,spend,actions",
          "level": "campaign",
          "date_preset": "last_30d",
          "sort": "ctr_descending",
          "limit": 20,
      },
  ).json().get("data", [])

  top_campaigns = [c for c in campaigns if float(c.get("ctr", 0)) > 1.0][:5]
  print(f"Top {len(top_campaigns)} campaigns by CTR")

  # 2. Pull ad copy from top campaigns
  all_copy = []
  for camp in top_campaigns:
      cid = camp.get("campaign_id")
      if not cid:
          continue
      ads = requests.get(
          f"{GRAPH}/{cid}/ads",
          params={
              "access_token": META,
              "fields": "creative{title,body,link_description}",
              "limit": 10,
          },
      ).json().get("data", [])

      for ad in ads:
          c = ad.get("creative", {})
          copy_text = f"Headline: {c.get('title', '')}\nBody: {c.get('body', '')}\nDescription: {c.get('link_description', '')}"
          if c.get("title") or c.get("body"):
              all_copy.append(copy_text)
      time.sleep(0.5)

  # 3. Create Brand Voice from winning ad copy
  samples = "\n\n---\n\n".join(all_copy[:15])
  bv = requests.post(f"{MB}/brand-voices", headers=MH, json={
      "name": "Meta Top Campaign Voice",
      "samples": [samples],
  }).json()
  print(f"Brand Voice: {bv['id']}")

  # 4. Extract themes
  themes = [
      f"Campaign: {c.get('campaign_name','')}, CTR: {c.get('ctr','?')}%, "
      f"Clicks: {c.get('clicks','?')}, Spend: ${c.get('spend','?')}"
      for c in top_campaigns
  ]
  theme_summary = "\n".join(themes)

  # 5. Generate blog post
  blog = requests.post(f"{MB}/generations", headers=MH, json={
      "brand_voice_id": bv["id"],
      "prompt": f"""Based on these top-performing Meta ad campaign themes, write a 600-word blog post 
  that extends the most resonant messaging into long-form content.

  TOP CAMPAIGNS:
  {theme_summary}

  WINNING COPY EXCERPTS:
  {samples[:2000]}

  Write a blog post that: uses the same tone and key phrases, expands on the core value proposition,
  includes a clear CTA, and is optimized for SEO.""",
  }).json()

  # 6. Generate social posts
  social = requests.post(f"{MB}/generations", headers=MH, json={
      "brand_voice_id": bv["id"],
      "prompt": f"""Create 5 social media posts (mix of LinkedIn, Twitter/X, Instagram caption) 
  inspired by these top campaign themes:

  {theme_summary}

  Each post should: match the ad copy voice, be platform-appropriate length,
  include a hook in the first line, end with CTA.""",
  }).json()

  print("\n=== Blog Post ===")
  print((blog.get("output") or blog.get("content", ""))[:500])
  print("\n=== Social Posts ===")
  print((social.get("output") or social.get("content", ""))[:500])
  ```

  ```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. Top campaigns by CTR
  const campaigns = await fetch(
    `${GRAPH}/${ACCT}/insights?access_token=${META}&fields=campaign_name,campaign_id,impressions,clicks,ctr,conversions,spend,actions&level=campaign&date_preset=last_30d&sort=ctr_descending&limit=20`
  ).then(r => r.json()).then(d => d.data || []);

  const topCampaigns = campaigns.filter(c => parseFloat(c.ctr || "0") > 1.0).slice(0, 5);
  console.log(`Top ${topCampaigns.length} campaigns by CTR`);

  // 2. Pull copy from top campaigns
  const allCopy = [];
  for (const camp of topCampaigns) {
    if (!camp.campaign_id) continue;
    const ads = await fetch(
      `${GRAPH}/${camp.campaign_id}/ads?access_token=${META}&fields=creative{title,body,link_description}&limit=10`
    ).then(r => r.json()).then(d => d.data || []);
    for (const ad of ads) {
      const c = ad.creative || {};
      if (c.title || c.body)
        allCopy.push(`Headline: ${c.title || ""}\nBody: ${c.body || ""}\nDescription: ${c.link_description || ""}`);
    }
    await new Promise(r => setTimeout(r, 500));
  }

  // 3. Brand Voice
  const samples = allCopy.slice(0, 15).join("\n\n---\n\n");
  const bv = await fetch(`${MB}/brand-voices`, {
    method: "POST", headers: MH,
    body: JSON.stringify({ name: "Meta Top Campaign Voice", samples: [samples] }),
  }).then(r => r.json());
  console.log(`Brand Voice: ${bv.id}`);

  // 4. Theme summary
  const themeSummary = topCampaigns.map(c =>
    `Campaign: ${c.campaign_name}, CTR: ${c.ctr}%, Clicks: ${c.clicks}, Spend: $${c.spend}`
  ).join("\n");

  // 5. Generate blog + social in parallel
  const [blog, social] = await Promise.all([
    fetch(`${MB}/generations`, {
      method: "POST", headers: MH,
      body: JSON.stringify({
        brand_voice_id: bv.id,
        prompt: `Write a 600-word blog post extending these top Meta campaigns:\n\n${themeSummary}\n\nCopy excerpts:\n${samples.slice(0, 2000)}\n\nMatch tone, expand value prop, include CTA.`,
      }),
    }).then(r => r.json()),
    fetch(`${MB}/generations`, {
      method: "POST", headers: MH,
      body: JSON.stringify({
        brand_voice_id: bv.id,
        prompt: `Create 5 social posts (LinkedIn, Twitter/X, Instagram) from these themes:\n\n${themeSummary}\n\nPlatform-appropriate, hook first line, CTA at end.`,
      }),
    }).then(r => r.json()),
  ]);

  console.log("\n=== Blog Post ===");
  console.log((blog.output || blog.content || "").slice(0, 500));
  console.log("\n=== Social Posts ===");
  console.log((social.output || social.content || "").slice(0, 500));
  ```
</CodeGroup>

### Example Output

```text theme={"dark"}
Brand Voice: bv_meta_top_8k3n

=== Blog Post ===
# Your Competitors Already Know This — Here's What They're Doing Differently

The data doesn't lie. After analyzing 50,000 ad impressions last month,
one pattern stands out: teams that centralize their analytics make decisions
40% faster. Not incrementally — fundamentally faster.

But here's what most articles about analytics won't tell you...

=== Social Posts ===
1. [LinkedIn] Most teams spend 60% of their time building reports nobody reads.
   We asked 200 marketing leaders what changed when they stopped. The answer
   surprised us. → Link in comments

2. [Twitter/X] "Stop guessing. Start growing." isn't just a tagline.
   We tracked 1,200 teams for 6 months. The ones who centralized analytics?
   3.2x faster campaign iterations. Here's the breakdown: 🧵

3. [Instagram] POV: You just cut your reporting time from 4 hours to 15 minutes.
   That's not a dream — it's what happens when you stop duct-taping dashboards.
   Link in bio for the free guide.
```

### Error Handling

<AccordionGroup>
  <Accordion title="CTR field is a string">Meta returns `ctr` as a string like `"2.345"`. Always parse with `float()` / `parseFloat()` before comparing.</Accordion>
  <Accordion title="Campaign-level insights vs ad-level">Use `level=campaign` param. Without it, you get ad-account-level aggregates which won't have `campaign_id`.</Accordion>
  <Accordion title="Brand Voice needs enough samples">Under 5 copy samples produces a generic voice. The code caps at 15 samples from top campaigns — aim for 8+ distinct pieces.</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="Brand Voices" icon="microphone" href="/features/brand-voices">
    Full guide to creating and managing Brand Voices
  </Card>
</CardGroup>
