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 LinkedIn ads are performing, but your team recycles the same copy patterns. This job pulls existing ad copy alongside engagement metrics, distills a Brand Voice from top performers, then uses Mavera’s Generate endpoint with the LinkedIn Ad app template to produce fresh variations that match your proven tone. Each variant is ready to paste into Campaign Manager.

Architecture

Code

import os, requests, time

LI = os.environ["LINKEDIN_ACCESS_TOKEN"]
MV = os.environ["MAVERA_API_KEY"]
LI_BASE = "https://api.linkedin.com/rest"
MV_BASE = "https://app.mavera.io/api/v1"
LI_H = {"Authorization": f"Bearer {LI}", "LinkedIn-Version": "202401", "X-Restli-Protocol-Version": "2.0.0"}
MV_H = {"Authorization": f"Bearer {MV}", "Content-Type": "application/json"}

AD_ACCOUNT_ID = "508000001"

# 1. Pull all creatives
creatives_resp = requests.get(f"{LI_BASE}/adAccounts/{AD_ACCOUNT_ID}/creatives",
    headers=LI_H,
    params={"q": "search", "count": 50})
if creatives_resp.status_code == 429:
    time.sleep(int(creatives_resp.headers.get("Retry-After", 60)))
    creatives_resp = requests.get(f"{LI_BASE}/adAccounts/{AD_ACCOUNT_ID}/creatives",
        headers=LI_H, params={"q": "search", "count": 50})
creatives_resp.raise_for_status()
creatives = creatives_resp.json().get("elements", [])

# 2. Pull analytics for each creative
creative_data = []
for cr in creatives:
    cr_urn = f"urn:li:sponsoredCreative:{cr.get('id', '')}"
    r = requests.get(f"{LI_BASE}/adAnalytics",
        headers=LI_H,
        params={
            "q": "analytics", "pivot": "CREATIVE",
            "timeGranularity": "ALL",
            "creatives[0]": cr_urn,
            "dateRange.start.year": 2025, "dateRange.start.month": 1, "dateRange.start.day": 1,
            "dateRange.end.year": 2025, "dateRange.end.month": 12, "dateRange.end.day": 31,
            "fields": "impressions,clicks,likes,comments,shares",
        })
    if r.status_code == 429:
        time.sleep(int(r.headers.get("Retry-After", 60)))
        continue
    metrics = r.json().get("elements", [{}])[0] if r.ok else {}

    copy = cr.get("commentary", "") or cr.get("content", {}).get("textAd", {}).get("text", "")
    headline = cr.get("content", {}).get("textAd", {}).get("headline", "")
    impressions = metrics.get("impressions", 0)
    clicks = metrics.get("clicks", 0)

    if copy and impressions > 0:
        creative_data.append({
            "copy": copy, "headline": headline,
            "impressions": impressions, "clicks": clicks,
            "ctr": round((clicks / impressions) * 100, 2) if impressions else 0,
            "engagement": metrics.get("likes", 0) + metrics.get("comments", 0) + metrics.get("shares", 0),
        })
    time.sleep(0.3)

# 3. Create Brand Voice from top performers
top_ads = sorted(creative_data, key=lambda x: -x["ctr"])[:10]
samples = "\n\n---\n\n".join(
    f"Headline: {a['headline']}\nCopy: {a['copy'][:500]}\nCTR: {a['ctr']}%"
    for a in top_ads
)

bv = requests.post(f"{MV_BASE}/brand-voices", headers=MV_H, json={
    "name": "LinkedIn Top-Performing Ad Voice",
    "samples": [samples],
}).json()
print(f"Brand Voice: {bv['id']}")

# 4. Generate new ad copy variants
gen = requests.post(f"{MV_BASE}/generations", headers=MV_H, json={
    "brand_voice_id": bv["id"],
    "app_template": "linkedin_ad",
    "prompt": (
        "Generate 5 LinkedIn Sponsored Content ad variants for a B2B SaaS product. "
        "Each variant needs: intro text (under 150 chars for mobile), headline, "
        "description, and CTA button text. Match the winning voice from our top ads. "
        "Vary the hooks: use a stat, a question, a bold claim, social proof, and a trend."
    ),
    "count": 5,
}).json()

for i, g in enumerate(gen.get("results", [gen]), 1):
    print(f"\n--- Variant {i} ---")
    print(g.get("content", g.get("text", ""))[:400])

# 5. Performance benchmark
avg_ctr = sum(a["ctr"] for a in top_ads) / len(top_ads) if top_ads else 0
print(f"\nBenchmark: Top-performer avg CTR = {avg_ctr:.2f}%")
print(f"Generated {len(gen.get('results', [gen]))} variants with brand voice {bv['id']}")

Example Output

Brand Voice: bv_li_ads_4k9m

--- Variant 1 (Stat Hook) ---
Intro: Marketing teams waste 12 hrs/week on reports. Here's the fix.
Headline: Cut Reporting Time by 60% — Without New Headcount
Description: See how 200+ B2B teams automated their marketing analytics.
CTA: Get the Case Study

--- Variant 2 (Question Hook) ---
Intro: Still building dashboards manually?
Headline: What Would You Do With 12 Extra Hours Every Week?
Description: AI-powered marketing analytics that actually saves time.
CTA: See It in Action

--- Variant 3 (Bold Claim) ---
Intro: Your marketing stack is lying to you.
Headline: The Real Cost of Fragmented Analytics
Description: One platform. Every channel. Real attribution in minutes.
CTA: Start Free Trial

--- Variant 4 (Social Proof) ---
Intro: Trusted by marketing teams at Stripe, Notion, and Ramp.
Headline: Why 200+ B2B Teams Switched to [Product]
Description: $2.3M in pipeline attributed in the first 90 days.
CTA: Book a Demo

--- Variant 5 (Trend) ---
Intro: AI is changing marketing analytics. Are you keeping up?
Headline: The 2025 Marketing Analytics Playbook
Description: How top B2B teams use AI to move from data to decisions.
CTA: Download Free

Benchmark: Top-performer avg CTR = 2.87%

Error Handling

Analytics require the creative URN in the exact format urn:li:sponsoredCreative:{id}. If analytics return empty, verify the URN format and date range.
Under ~200 words of combined ad copy produces a generic voice. Include at least 5 ads with 30+ words each.
Intro text: 150 chars (mobile truncation). Headline: 70 chars. Description: 100 chars. The prompt specifies these constraints, but verify generated output before uploading.
LinkedIn access tokens expire after 60 days. For batch jobs pulling 50+ creatives, implement token refresh middleware to avoid mid-run failures.

All LinkedIn Marketing jobs

Brand Voice