Scenario
Your company publishes polished updates, but employees don’t reshare them — the posts sound corporate. This job pulls recent company updates, creates or reuses a conversational Brand Voice, then generates employee-ready versions of each post. The rewrites swap corporate tone for personal, first-person language that employees can paste directly into their feeds. Each variant is tailored for individual sharing: shorter, more conversational, with a personal take that makes the reshare feel authentic.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"}
ORG_URN = "urn:li:organization:12345678"
# 1. Pull recent company posts
r = requests.get(f"{LI_BASE}/posts",
headers=LI_H,
params={"q": "author", "author": ORG_URN, "count": 20, "sortBy": "LAST_MODIFIED"})
if r.status_code == 429:
time.sleep(int(r.headers.get("Retry-After", 60)))
r = requests.get(f"{LI_BASE}/posts", headers=LI_H,
params={"q": "author", "author": ORG_URN, "count": 20, "sortBy": "LAST_MODIFIED"})
r.raise_for_status()
posts = r.json().get("elements", [])
# 2. Filter to shareable posts (skip internal-only or short updates)
shareable = []
for post in posts:
commentary = post.get("commentary", "")
visibility = post.get("visibility", "")
if not commentary or len(commentary) < 50:
continue
shareable.append({
"urn": post.get("id", ""),
"commentary": commentary,
"content_type": post.get("content", {}).get("contentType", "NONE"),
"media_url": post.get("content", {}).get("media", {}).get("id", ""),
})
if not shareable:
raise SystemExit("No shareable posts found.")
# 3. Create conversational Brand Voice for employee shares
employee_voice_samples = """
I've been at [Company] for two years now, and this is the kind of work that
makes me proud to be here. We just shipped something that actually changes
how our customers work — not incrementally, but fundamentally.
---
Hot take: most marketing analytics tools are built for dashboards, not
decisions. We built the opposite. Here's what I mean.
---
Three things I learned this quarter working with our customers:
1. The data isn't the problem — it's getting from data to action
2. Speed matters more than precision in most marketing decisions
3. Teams that automate reporting spend 60% more time on strategy
This is why I love what we're building.
"""
bv = requests.post(f"{MV_BASE}/brand-voices", headers=MV_H, json={
"name": "Employee Advocacy Voice — Conversational",
"samples": [employee_voice_samples],
}).json()
voice_id = bv["id"]
print(f"Brand Voice: {voice_id}")
# 4. Generate employee-ready variants for each post
all_variants = []
for i, post in enumerate(shareable[:5]):
gen = requests.post(f"{MV_BASE}/generations", headers=MV_H, json={
"brand_voice_id": voice_id,
"prompt": f"""Rewrite this LinkedIn company post for individual employees to share on their personal profiles.
ORIGINAL COMPANY POST:
{post['commentary'][:600]}
Rules:
- Write in first person ("I", "we", "my team")
- Add a personal take or reaction (not just a reshare)
- Keep under 200 words (LinkedIn mobile truncation)
- Open with a hook — not "Excited to announce" or "Thrilled to share"
- End with a question or invitation to discuss
- Sound like a real person, not a press release
- Generate 3 variants with different angles: (1) personal story, (2) hot take, (3) lesson learned
CONTENT TYPE: {post['content_type']}""",
"count": 3,
}).json()
variants = gen.get("results", [gen])
all_variants.append({
"original": post["commentary"][:200],
"urn": post["urn"],
"variants": [v.get("content", v.get("text", ""))[:500] for v in variants],
})
time.sleep(0.5)
# 5. Output
for entry in all_variants:
print(f"\n{'='*60}")
print(f"ORIGINAL: {entry['original'][:120]}...")
print(f"{'='*60}")
for j, variant in enumerate(entry["variants"]):
labels = ["Personal Story", "Hot Take", "Lesson Learned"]
print(f"\n [{labels[j] if j < len(labels) else f'Variant {j+1}'}]")
print(f" {variant}")
print(f"\n--- Generated {sum(len(e['variants']) for e in all_variants)} employee variants for {len(all_variants)} posts ---")
print(f"Brand Voice: {voice_id}")
const LI = process.env.LINKEDIN_ACCESS_TOKEN;
const MV = process.env.MAVERA_API_KEY;
const LI_BASE = "https://api.linkedin.com/rest";
const MV_BASE = "https://app.mavera.io/api/v1";
const LI_H = { Authorization: `Bearer ${LI}`, "LinkedIn-Version": "202401", "X-Restli-Protocol-Version": "2.0.0" };
const MV_H = { Authorization: `Bearer ${MV}`, "Content-Type": "application/json" };
const ORG_URN = "urn:li:organization:12345678";
// 1. Pull recent company posts
let res = await fetch(
`${LI_BASE}/posts?q=author&author=${encodeURIComponent(ORG_URN)}&count=20&sortBy=LAST_MODIFIED`,
{ headers: LI_H }
);
if (res.status === 429) {
await new Promise(r => setTimeout(r, parseInt(res.headers.get("Retry-After") || "60", 10) * 1000));
res = await fetch(
`${LI_BASE}/posts?q=author&author=${encodeURIComponent(ORG_URN)}&count=20&sortBy=LAST_MODIFIED`,
{ headers: LI_H }
);
}
if (!res.ok) throw new Error(`LinkedIn ${res.status}`);
const posts = (await res.json()).elements || [];
// 2. Filter shareable
const shareable = posts
.filter(p => p.commentary && p.commentary.length >= 50)
.map(p => ({
urn: p.id, commentary: p.commentary,
content_type: p.content?.contentType || "NONE",
}));
if (!shareable.length) throw new Error("No shareable posts found.");
// 3. Conversational Brand Voice
const voiceSamples = `I've been at [Company] for two years now, and this is the kind of work that makes me proud. We just shipped something that actually changes how our customers work.\n\n---\n\nHot take: most marketing analytics tools are built for dashboards, not decisions. We built the opposite.\n\n---\n\nThree things I learned this quarter:\n1. The data isn't the problem — it's getting from data to action\n2. Speed matters more than precision\n3. Teams that automate reporting spend 60% more time on strategy`;
const bv = await fetch(`${MV_BASE}/brand-voices`, {
method: "POST", headers: MV_H,
body: JSON.stringify({ name: "Employee Advocacy Voice — Conversational", samples: [voiceSamples] }),
}).then(r => r.json());
console.log(`Brand Voice: ${bv.id}`);
// 4. Generate employee variants
const allVariants = [];
for (const post of shareable.slice(0, 5)) {
const gen = await fetch(`${MV_BASE}/generations`, {
method: "POST", headers: MV_H,
body: JSON.stringify({
brand_voice_id: bv.id,
prompt: `Rewrite this company post for employees to share personally.\n\nORIGINAL:\n${post.commentary.slice(0, 600)}\n\nRules:\n- First person ("I", "we", "my team")\n- Personal take, not just reshare\n- Under 200 words\n- Hook first line — no "Excited to announce"\n- End with question\n- 3 variants: (1) personal story, (2) hot take, (3) lesson learned`,
count: 3,
}),
}).then(r => r.json());
allVariants.push({
original: post.commentary.slice(0, 200),
urn: post.urn,
variants: (gen.results || [gen]).map(v => (v.content || v.text || "").slice(0, 500)),
});
await new Promise(r => setTimeout(r, 500));
}
// 5. Output
const labels = ["Personal Story", "Hot Take", "Lesson Learned"];
for (const entry of allVariants) {
console.log(`\n${"=".repeat(60)}`);
console.log(`ORIGINAL: ${entry.original.slice(0, 120)}...`);
console.log("=".repeat(60));
entry.variants.forEach((v, j) => {
console.log(`\n [${labels[j] || `Variant ${j + 1}`}]`);
console.log(` ${v}`);
});
}
const total = allVariants.reduce((s, e) => s + e.variants.length, 0);
console.log(`\n--- Generated ${total} employee variants for ${allVariants.length} posts ---`);
Example Output
Brand Voice: bv_emp_adv_7x3k
============================================================
ORIGINAL: We're proud to announce our Series B funding of $45M led by...
============================================================
[Personal Story]
Two years ago I joined a 12-person team with a big bet on marketing
intelligence. Today we announced our Series B — $45M to keep building.
What convinced me to stay wasn't the funding. It was watching a customer
cancel three other tools after one month with us. That's the kind of
product-market fit you can feel.
If you're building in MarTech, I'd love to hear — what made you stay at
your company past year one?
[Hot Take]
$45M is great. But here's what matters more than the number:
We're not raising to find product-market fit. We're raising because
customers keep asking us to do more. 200+ teams. 94% retention. That's
the story behind the press release.
Funding announcements are noise. Retention is signal. What metrics do
you actually care about when evaluating a vendor?
[Lesson Learned]
Three things I've learned on the road to Series B:
1. Customers who churned taught us more than customers who stayed
2. The feature nobody asked for became our top differentiator
3. "Move fast and break things" is wrong — move fast and fix things
We just raised $45M to keep doing exactly this. What's the most
counterintuitive lesson from your company's growth?
Error Handling
Brand Voice sample quality
Brand Voice sample quality
The employee voice samples define the output tone. Generic samples produce generic rewrites. Include 3–5 real examples of how your best employees already post on LinkedIn.
Post length for mobile
Post length for mobile
LinkedIn truncates posts at ~210 characters on mobile with a “see more” fold. The prompt targets under 200 words total, but the hook (first line) should be under 150 characters to display fully.
Compliance and legal review
Compliance and legal review
Employee advocacy content should be reviewed by legal/comms before distribution, especially for regulated industries (finance, healthcare). Add a review step before publishing.
Reusing an existing Brand Voice
Reusing an existing Brand Voice
To avoid creating duplicate voices, check first:
GET /api/v1/brand-voices?search=Employee+Advocacy. If one exists, pass its id directly to the generation call.