Scenario
When a story breaks in your industry, the first brand to publish a relevant take captures attention. This job monitors top headlines, identifies breaking stories via Mave alignment scoring, and immediately generates content pieces — a LinkedIn post, a tweet thread hook, and a blog intro — all aligned to your brand voice. Flow: NewsAPIGET /top-headlines → Mavera POST /mave/chat (alignment) → POST /generations (content) → Rapid content library
Code
import os, requests, time
NA_KEY = os.environ["NEWSAPI_KEY"]
NA_BASE = "https://newsapi.org/v2"
NA_H = {"X-Api-Key": NA_KEY}
MV = os.environ["MAVERA_API_KEY"]
MV_BASE = "https://app.mavera.io/api/v1"
MV_H = {"Authorization": f"Bearer {MV}", "Content-Type": "application/json"}
BRAND = "B2B marketing analytics platform. Audience: CMOs and marketing directors. Tone: data-driven, authoritative, contrarian."
CATEGORY = "business"
# 1. Fetch breaking headlines
r = requests.get(f"{NA_BASE}/top-headlines", headers=NA_H, params={
"category": CATEGORY, "country": "us", "pageSize": 15,
})
r.raise_for_status()
headlines = r.json().get("articles", [])
print(f"Fetched {len(headlines)} breaking headlines")
# 2. Score alignment via Mave
aligned = []
for article in headlines:
check = requests.post(f"{MV_BASE}/mave/chat", headers=MV_H, json={
"message": f"Rate brand-news alignment (1-10).\n\nHEADLINE: {article['title']}\n"
f"DESCRIPTION: {article.get('description','')[:300]}\nSOURCE: {article.get('source',{}).get('name','')}\n\n"
f"BRAND: {BRAND}\n\nIf 7+: suggest angle, format, and urgency level (hours/today/this-week).",
}).json()
content = check.get("content", "")
try:
score_line = [l for l in content.split("\n") if "/10" in l][0]
score = int("".join(c for c in score_line if c.isdigit())[:2])
except (IndexError, ValueError):
score = 0
if score >= 7:
aligned.append({"title": article["title"], "desc": article.get("description",""),
"source": article.get("source",{}).get("name",""),
"score": score, "angle": content[:400]})
time.sleep(0.5)
print(f"Aligned: {len(aligned)} of {len(headlines)}")
# 3. Generate rapid content for top stories
for story in aligned[:3]:
gen = requests.post(f"{MV_BASE}/generations", headers=MV_H, json={
"prompt": f"Breaking news rapid content. Generate ALL three formats:\n\n"
f"NEWS: {story['title']}\nDESCRIPTION: {story['desc'][:300]}\n"
f"ANGLE: {story['angle'][:200]}\nBRAND: {BRAND}\n\n"
"1. LINKEDIN POST (200-300 words): Hook with the news, add contrarian take, end with question.\n"
"2. TWEET THREAD HOOK (3 tweets, 280 chars each): Thread starter that drives clicks.\n"
"3. BLOG INTRO (150 words): SEO-optimized opening paragraph for a longer analysis piece.\n\n"
"Tone: data-driven, authoritative. Reference the source.",
}).json()
print(f"\n{'='*60}\nSTORY: {story['title']} (Alignment: {story['score']}/10)")
print(f"{'='*60}")
print(gen.get("output", gen.get("content", ""))[:1200])
const NA_KEY = process.env.NEWSAPI_KEY;
const NA_BASE = "https://newsapi.org/v2";
const NA_H = { "X-Api-Key": NA_KEY };
const MV = process.env.MAVERA_API_KEY;
const MV_BASE = "https://app.mavera.io/api/v1";
const MV_H = { Authorization: `Bearer ${MV}`, "Content-Type": "application/json" };
const BRAND = "B2B marketing analytics. Audience: CMOs. Tone: data-driven, authoritative, contrarian.";
const CATEGORY = "business";
// 1. Breaking headlines
const headlines = (await (await fetch(
`${NA_BASE}/top-headlines?category=${CATEGORY}&country=us&pageSize=15`,
{ headers: NA_H })).json()).articles || [];
console.log(`Fetched ${headlines.length} breaking headlines`);
// 2. Alignment scoring
const aligned = [];
for (const article of headlines) {
const check = await fetch(`${MV_BASE}/mave/chat`, { method: "POST", headers: MV_H,
body: JSON.stringify({ message: `Rate alignment (1-10).\n\nHEADLINE: ${article.title}\nDESCRIPTION: ${(article.description||"").slice(0,300)}\n\nBRAND: ${BRAND}\n\nIf 7+: angle, format, urgency.` }),
}).then(r => r.json());
const line = (check.content || "").split("\n").find(l => l.includes("/10")) || "";
const score = parseInt((line.match(/\d+/) || ["0"])[0], 10);
if (score >= 7)
aligned.push({ title: article.title, desc: article.description||"",
source: article.source?.name||"", score, angle: (check.content||"").slice(0,400) });
await new Promise(r => setTimeout(r, 500));
}
console.log(`Aligned: ${aligned.length} of ${headlines.length}`);
// 3. Generate
for (const story of aligned.slice(0, 3)) {
const gen = await fetch(`${MV_BASE}/generations`, { method: "POST", headers: MV_H,
body: JSON.stringify({ prompt: `Breaking news rapid content.\n\nNEWS: ${story.title}\nDESC: ${story.desc.slice(0,300)}\nANGLE: ${story.angle.slice(0,200)}\nBRAND: ${BRAND}\n\n1. LINKEDIN (200-300 words): Hook, contrarian, question.\n2. TWEET THREAD (3×280 chars): Drive clicks.\n3. BLOG INTRO (150 words): SEO opening.\n\nReference the source.` }),
}).then(r => r.json());
console.log(`\n${"=".repeat(60)}\nSTORY: ${story.title} (Alignment: ${story.score}/10)`);
console.log((gen.output || gen.content || "").slice(0, 1200));
}
Example Output
Fetched 15 breaking headlines
Aligned: 4 of 15
STORY: Google Ads Deprecates Last-Click Attribution (Alignment: 10/10)
============================================================
1. LINKEDIN POST:
Google just deprecated last-click attribution. If your marketing team is
panicking, you were already measuring wrong.
Here's the uncomfortable truth: 73% of B2B companies still report on
last-click. They've been over-crediting bottom-funnel tactics and
under-investing in brand for years...
2. TWEET THREAD:
🧵 Google killed last-click attribution today. Most marketers are panicking.
Smart ones are relieved. Here's why ↓ (1/3)
3. BLOG INTRO:
Google's deprecation of last-click attribution isn't a technical change —
it's an admission that single-touch measurement never worked...
Error Handling
Stale headlines
Stale headlines
Top headlines update every 15 minutes. For true breaking news, poll on a cron. Cache previous results to avoid duplicate content generation.
Low alignment rates
Low alignment rates
If fewer than 2 stories align, broaden
category or switch to /everything with industry-specific queries.Content length
Content length
Generation may exceed platform limits (LinkedIn: 3,000 chars, Twitter: 280). Validate lengths before posting.