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 Company Page has 15,000 followers, but your personas are based on assumptions from a strategy deck written two years ago. This job pulls real follower demographics — industry verticals, job functions, and seniority levels — from LinkedIn’s organizational statistics API. It compares those distributions against your existing Mavera personas, then updates (or creates) Custom Personas with actual audience data. The result: personas that reflect who actually follows you, not who you wish followed you.

Architecture

Code

import os, requests, time
from collections import defaultdict

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"}

ORG_URN = "urn:li:organization:12345678"

# 1. Pull follower statistics
r = requests.get(f"{LI_BASE}/organizationalEntityFollowerStatistics",
    headers=LI_H,
    params={
        "q": "organizationalEntity",
        "organizationalEntity": ORG_URN,
    })
if r.status_code == 429:
    time.sleep(int(r.headers.get("Retry-After", 60)))
    r = requests.get(f"{LI_BASE}/organizationalEntityFollowerStatistics",
        headers=LI_H,
        params={"q": "organizationalEntity", "organizationalEntity": ORG_URN})
r.raise_for_status()
stats = r.json().get("elements", [{}])[0]

# 2. Parse demographic breakdowns
def parse_breakdown(data, key):
    result = {}
    for entry in data:
        name = entry.get(key, {}).get("localized", {}).get("en_US", "")
        if not name:
            name = entry.get(key, "Unknown")
        organic = entry.get("followerCounts", {}).get("organicFollowerCount", 0)
        paid = entry.get("followerCounts", {}).get("paidFollowerCount", 0)
        result[name] = organic + paid
    return dict(sorted(result.items(), key=lambda x: -x[1]))

industries = parse_breakdown(stats.get("followerCountsByIndustry", []), "industry")
functions = parse_breakdown(stats.get("followerCountsByFunction", []), "function")
seniorities = parse_breakdown(stats.get("followerCountsBySeniority", []), "seniority")

total = sum(industries.values()) or 1
print(f"Total followers: {total}")
print(f"Top industries: {list(industries.items())[:5]}")
print(f"Top functions: {list(functions.items())[:5]}")
print(f"Top seniorities: {list(seniorities.items())[:5]}")

# 3. Fetch existing Mavera personas
existing = requests.get(f"{MV_BASE}/personas", headers=MV_H).json()
li_personas = {p["name"]: p for p in (existing if isinstance(existing, list) else [])
               if "LinkedIn Follower" in p.get("name", "")}

# 4. Build persona segments from top combinations
top_industries = list(industries.keys())[:4]
top_functions = list(functions.keys())[:3]
top_seniorities = list(seniorities.keys())[:3]

created, updated = [], []
for industry in top_industries:
    ind_pct = round((industries[industry] / total) * 100, 1)
    if ind_pct < 3:
        continue

    top_func = top_functions[0] if top_functions else "General"
    top_sen = top_seniorities[0] if top_seniorities else "Senior"
    name = f"LinkedIn Follower: {industry}"
    desc = (
        f"Derived from Company Page follower analytics. "
        f"Industry: {industry} ({ind_pct}% of followers). "
        f"Top function: {top_func} ({round((functions.get(top_func, 0) / total) * 100, 1)}%). "
        f"Top seniority: {top_sen} ({round((seniorities.get(top_sen, 0) / total) * 100, 1)}%). "
        f"Total follower base: {total:,}."
    )
    payload = {
        "name": name,
        "description": desc,
        "demographic": {
            "industries": [industry],
            "job_titles": [top_func],
            "seniority": top_sen,
        },
        "psychographic": {
            "source": "linkedin_company_page_followers",
            "audience_share_pct": ind_pct,
        },
    }

    if name in li_personas:
        r = requests.patch(f"{MV_BASE}/personas/{li_personas[name]['id']}",
            headers=MV_H, json=payload)
        r.raise_for_status()
        updated.append({"name": name, "id": li_personas[name]["id"], "pct": ind_pct})
    else:
        r = requests.post(f"{MV_BASE}/personas", headers=MV_H, json=payload)
        r.raise_for_status()
        created.append({"name": name, "id": r.json()["id"], "pct": ind_pct})
    time.sleep(0.3)

print(f"\nCreated {len(created)} | Updated {len(updated)} personas")
for p in created + updated:
    action = "Created" if p in created else "Updated"
    print(f"  {action}: {p['name']} ({p['pct']}%) → {p['id']}")

Example Output

Total followers: 14,832
Top industries: [('Technology', 4218), ('Financial Services', 2107), ('Marketing & Advertising', 1890), ('Healthcare', 1260), ('Education', 947)]
Top functions: [('Marketing', 3415), ('Business Development', 2890), ('Engineering', 1740)]
Top seniorities: [('Senior', 4120), ('Manager', 3280), ('Director', 2950)]

Created 1 | Updated 3 personas
  Updated: LinkedIn Follower: Technology (28.4%) → per_li_tech_01
  Updated: LinkedIn Follower: Financial Services (14.2%) → per_li_fin_02
  Updated: LinkedIn Follower: Marketing & Advertising (12.7%) → per_li_mktg_03
  Created: LinkedIn Follower: Healthcare (8.5%) → per_li_hc_04

Error Handling

Follower statistics need the r_organization_followers scope, which requires the Community Management API product approval. Without it, the endpoint returns 403.
The organization URN must be urn:li:organization:{numericId}. Find your numeric ID on the Company Page admin URL or via GET /organizationAcls.
Industry and function names are returned in a localized object. The code extracts en_US first, falling back to the raw value. Adjust for other locales.
Pages with under 300 followers produce unreliable demographic distributions. Wait for a larger base before running persona refinement.

LinkedIn Content Integration

Personas