Scenario
Analyze what earns backlinks in your niche, then generate outreach emails per linking site type. Pull a competitor’s backlink overview and individual links, categorize domains (blog, news, directory, resource), extract brand voice, then generate email variants.Architecture
Code
import os, requests, csv, io, time
SR, MV = os.environ["SEMRUSH_API_KEY"], os.environ["MAVERA_API_KEY"]
MB = "https://app.mavera.io/api/v1"
MH = {"Authorization": f"Bearer {MV}", "Content-Type": "application/json"}
COMP = "competitor.com"
ov = requests.get("https://api.semrush.com/analytics/v1/", params={
"type": "backlinks_overview", "key": SR, "target": COMP,
"target_type": "root_domain",
"export_columns": "total,domains_num,urls_num,follows_num,nofollows_num",
}).text
ov_l = ov.strip().split("\n")
if len(ov_l) >= 2:
print("Overview:", dict(zip(ov_l[0].split(";"), ov_l[1].split(";"))))
lr = requests.get("https://api.semrush.com/analytics/v1/", params={
"type": "backlinks", "key": SR, "target": COMP,
"target_type": "root_domain", "display_limit": 100,
"display_sort": "page_ascore_desc",
"export_columns": "source_url,source_title,target_url,anchor,external_num,internal_num,page_ascore",
})
reader = csv.reader(io.StringIO(lr.text), delimiter=";")
next(reader)
links = [{"url": r[0], "title": r[1], "anchor": r[3], "auth": int(r[6] or 0)}
for r in reader if len(r) >= 7]
cats = {"blog": [], "news": [], "directory": [], "resource": [], "other": []}
for l in links:
u, t = l["url"].lower(), l["title"].lower()
if any(w in u for w in ["/blog","blog.","medium.com"]): cats["blog"].append(l)
elif any(w in u+t for w in ["news","press","techcrunch"]): cats["news"].append(l)
elif "directory" in u or "/list" in u: cats["directory"].append(l)
elif any(w in t for w in ["resource","tools","guide"]): cats["resource"].append(l)
else: cats["other"].append(l)
bv = requests.post(f"{MB}/brand-voices", headers=MH, json={
"name": "Outreach Voice",
"samples": ["Hi [Name], saw your post on [Topic] — we have a study that adds data. Open to a look?"],
"description": "Casual, helpful, concise. No hard sells.",
}).json()
for cat, cl in cats.items():
if not cl: continue
block = "\n".join(f"- {l['title'][:50]} ({l['url'][:60]}) — \"{l['anchor']}\""
for l in cl[:5])
gen = requests.post(f"{MB}/generations", headers=MH, json={
"prompt": f"2 outreach emails for {cat} sites linking to competitor.\n\n"
f"SITES:\n{block}\n\n<150 words each, reference site, offer value, soft CTA.",
"brand_voice_id": bv["id"],
}).json()
print(f"\n=== {cat} ({len(cl)} links) ===")
print(gen.get("output", gen.get("content", ""))[:500])
time.sleep(0.5)
const SR = process.env.SEMRUSH_API_KEY, MV = process.env.MAVERA_API_KEY;
const MB = "https://app.mavera.io/api/v1";
const MH = { Authorization: `Bearer ${MV}`, "Content-Type": "application/json" };
const COMP = "competitor.com";
const ovP = new URLSearchParams({ type: "backlinks_overview", key: SR, target: COMP,
target_type: "root_domain", export_columns: "total,domains_num,urls_num,follows_num,nofollows_num" });
const ovT = await fetch(`https://api.semrush.com/analytics/v1/?${ovP}`).then((r) => r.text());
const ovL = ovT.trim().split("\n");
if (ovL.length >= 2) console.log("Overview:", Object.fromEntries(
ovL[0].split(";").map((k, i) => [k, ovL[1].split(";")[i]])));
const lP = new URLSearchParams({ type: "backlinks", key: SR, target: COMP,
target_type: "root_domain", display_limit: "100", display_sort: "page_ascore_desc",
export_columns: "source_url,source_title,target_url,anchor,external_num,internal_num,page_ascore" });
const lT = await fetch(`https://api.semrush.com/analytics/v1/?${lP}`).then((r) => r.text());
const links = lT.trim().split("\n").slice(1).map((line) => {
const c = line.split(";");
return c.length >= 7 ? { url: c[0], title: c[1], anchor: c[3], auth: parseInt(c[6]) || 0 } : null;
}).filter(Boolean);
const cats = { blog: [], news: [], directory: [], resource: [], other: [] };
for (const l of links) {
const u = l.url.toLowerCase(), t = l.title.toLowerCase();
if (/\/blog|blog\.|medium\.com/.test(u)) cats.blog.push(l);
else if (/news|press|techcrunch/.test(u+t)) cats.news.push(l);
else if (/directory|\/list/.test(u)) cats.directory.push(l);
else if (/resource|tools|guide/.test(t)) cats.resource.push(l);
else cats.other.push(l);
}
const bv = await fetch(`${MB}/brand-voices`, { method: "POST", headers: MH,
body: JSON.stringify({ name: "Outreach Voice",
samples: ["Hi [Name], saw your post — we have data that adds to it. Look?"],
description: "Casual, helpful, concise." }),
}).then((r) => r.json());
for (const [cat, cl] of Object.entries(cats)) {
if (!cl.length) continue;
const block = cl.slice(0, 5).map((l) => `- ${l.title.slice(0,50)} — "${l.anchor}"`).join("\n");
const gen = await fetch(`${MB}/generations`, { method: "POST", headers: MH,
body: JSON.stringify({ prompt: `2 emails for ${cat} sites.\nSITES:\n${block}\n<150 words, value, soft CTA.`,
brand_voice_id: bv.id }),
}).then((r) => r.json());
console.log(`\n=== ${cat} (${cl.length}) ===\n${(gen.output||gen.content||"").slice(0,500)}`);
await new Promise((r) => setTimeout(r, 500));
}
Example Output
Overview: {total: 24853, domains: 1247, follow: 19201, nofollow: 5652}
=== blog (34 links) ===
Variant 1: Hi Sarah, your post on "Why Content Teams Need Better Feedback
Loops" resonated. We built synthetic focus groups for drafts. Worth a demo?
Variant 2: Your Roundup included AI writing tools but nothing on audience
testing. Our case study: pre-publish focus groups cut revisions by 60%.
Error Handling
Different base path for backlinks API
Different base path for backlinks API
Backlinks endpoints use
https://api.semrush.com/analytics/v1/ — not the root. Check the URL if you get 404 errors.High unit cost
High unit cost
Backlink calls consume 20-70 API units per request. Monitor your balance before full audits.
Categorization is heuristic
Categorization is heuristic
URL/title-based categorization won’t catch every case. Supplement with domain metadata or manual review for production.