Scenario
Your Lever postings attract candidates from different sources — LinkedIn, Indeed, referrals, your careers page, agencies. But which sources produce the best candidates? And how should your recruitment marketing differ by source? You pull posting performance data with source attribution, analyze source effectiveness with Mave, then generate source-optimized recruitment content. Flow: LeverGET /postings + GET /opportunities (source data) → Mave POST /mave/chat (source analysis) → POST /generations (source-optimized content)
Architecture
Code
import os, requests, time, base64
from collections import defaultdict
LV_KEY = os.environ["LEVER_API_KEY"]
MV = os.environ["MAVERA_API_KEY"]
LV_BASE = "https://api.lever.co/v1"
MV_BASE = "https://app.mavera.io/api/v1"
MV_H = {"Authorization": f"Bearer {MV}", "Content-Type": "application/json"}
lv_auth = base64.b64encode(f"{LV_KEY}:".encode()).decode()
LV_H = {"Authorization": f"Basic {lv_auth}"}
def lv_get(path, params=None):
r = requests.get(f"{LV_BASE}{path}", headers=LV_H, params=params or {})
if r.status_code == 429:
time.sleep(1)
return lv_get(path, params)
r.raise_for_status()
return r.json()
# 1. Pull active postings
postings = lv_get("/postings", {"state": "published"}).get("data", [])
# 2. Pull opportunities with source data
opps = []
offset = None
while len(opps) < 800:
params = {"limit": 100}
if offset:
params["offset"] = offset
resp = lv_get("/opportunities", params)
opps.extend(resp.get("data", []))
offset = resp.get("next")
if not offset:
break
time.sleep(0.15)
# 3. Aggregate source performance
source_stats = defaultdict(lambda: {"applied": 0, "screened": 0, "onsited": 0, "offered": 0, "hired": 0})
stage_resp = lv_get("/stages")
stage_names = {s["id"]: s["text"].lower() for s in stage_resp.get("data", [])}
for opp in opps:
sources = opp.get("sources", [])
source = sources[0] if sources else "Direct"
stage_name = stage_names.get(opp.get("stage", ""), "unknown")
source_stats[source]["applied"] += 1
if any(kw in stage_name for kw in ["screen", "phone"]):
source_stats[source]["screened"] += 1
if "onsite" in stage_name or "final" in stage_name:
source_stats[source]["onsited"] += 1
if "offer" in stage_name:
source_stats[source]["offered"] += 1
if opp.get("isArchived") and "hired" in stage_name:
source_stats[source]["hired"] += 1
# 4. Mave analysis
source_block = "\n".join(
f"- {src}: Applied={s['applied']} Screen={s['screened']} Onsite={s['onsited']} Offer={s['offered']} Hired={s['hired']}"
for src, s in sorted(source_stats.items(), key=lambda x: -x[1]["applied"])[:10]
)
analysis = requests.post(f"{MV_BASE}/mave/chat", headers=MV_H, json={
"message": f"""Analyze recruitment source effectiveness from this Lever data:
{source_block}
For each source:
1. Conversion rate through funnel stages
2. Quality indicator (hired/applied ratio)
3. Where candidates drop off
4. Recommended recruitment marketing strategy for this channel
5. What messaging resonates on this channel vs others"""
}).json()
print("=== Source Effectiveness Analysis ===")
print(analysis.get("content", "")[:1500])
# 5. Generate source-optimized content
for source in list(source_stats.keys())[:3]:
stats = source_stats[source]
gen = requests.post(f"{MV_BASE}/generations", headers=MV_H, json={
"prompt": (
f"Write a recruitment marketing post optimized for {source}. "
f"This source has {stats['applied']} applicants with {stats['hired']} hires "
f"({stats['hired']/max(stats['applied'],1)*100:.0f}% conversion). "
f"Match the tone and format conventions of {source}. 150-200 words."
),
}).json()
print(f"\n--- {source} Content ---")
print(gen.get("output", gen.get("content", gen.get("text", "")))[:500]
time.sleep(0.5)
const LV_KEY = process.env.LEVER_API_KEY;
const MV = process.env.MAVERA_API_KEY;
const LV_BASE = "https://api.lever.co/v1";
const MV_BASE = "https://app.mavera.io/api/v1";
const MV_H = { Authorization: `Bearer ${MV}`, "Content-Type": "application/json" };
const LV_H = { Authorization: `Basic ${btoa(`${LV_KEY}:`)}` };
async function lvGet(path, params = {}) {
const qs = new URLSearchParams(params).toString();
const res = await fetch(`${LV_BASE}${path}?${qs}`, { headers: LV_H });
if (res.status === 429) { await new Promise((r) => setTimeout(r, 1000)); return lvGet(path, params); }
if (!res.ok) throw new Error(`Lever ${res.status}`);
return res.json();
}
// 1. Active postings
const postings = (await lvGet("/postings", { state: "published" })).data || [];
// 2. Opportunities
const opps = [];
let offset = null;
while (opps.length < 800) {
const params = { limit: 100 };
if (offset) params.offset = offset;
const resp = await lvGet("/opportunities", params);
opps.push(...(resp.data || []));
offset = resp.next;
if (!offset) break;
await new Promise((r) => setTimeout(r, 150));
}
// 3. Stage map + source stats
const stageNames = Object.fromEntries(
((await lvGet("/stages")).data || []).map((s) => [s.id, s.text.toLowerCase()])
);
const sourceStats = {};
for (const opp of opps) {
const source = opp.sources?.[0] || "Direct";
const stage = stageNames[opp.stage] || "unknown";
const s = (sourceStats[source] ??= { applied: 0, screened: 0, onsited: 0, offered: 0, hired: 0 });
s.applied++;
if (stage.includes("screen") || stage.includes("phone")) s.screened++;
if (stage.includes("onsite") || stage.includes("final")) s.onsited++;
if (stage.includes("offer")) s.offered++;
if (opp.isArchived && stage.includes("hired")) s.hired++;
}
// 4. Mave analysis
const sourceBlock = Object.entries(sourceStats)
.sort(([, a], [, b]) => b.applied - a.applied).slice(0, 10)
.map(([src, s]) => `- ${src}: Applied=${s.applied} Screen=${s.screened} Onsite=${s.onsited} Offer=${s.offered} Hired=${s.hired}`)
.join("\n");
const analysis = await fetch(`${MV_BASE}/mave/chat`, {
method: "POST", headers: MV_H,
body: JSON.stringify({
message: `Analyze source effectiveness:\n\n${sourceBlock}\n\nPer source: 1) Conversion rates 2) Quality (hired/applied) 3) Drop-off 4) Marketing strategy 5) Messaging approach`,
}),
}).then((r) => r.json());
console.log("=== Source Effectiveness ===");
console.log((analysis.content || "").slice(0, 1500));
// 5. Generate per-source content
for (const [source, stats] of Object.entries(sourceStats).slice(0, 3)) {
const gen = await fetch(`${MV_BASE}/generations`, {
method: "POST", headers: MV_H,
body: JSON.stringify({
prompt: `Write recruitment marketing for ${source}. ${stats.applied} applicants, ${stats.hired} hires (${(stats.hired / Math.max(stats.applied, 1) * 100).toFixed(0)}% conversion). Match ${source} conventions. 150-200 words.`,
}),
}).then((r) => r.json());
console.log(`\n--- ${source} ---`);
console.log((gen.output || gen.content || gen.text || "").slice(0, 500));
await new Promise((r) => setTimeout(r, 500));
}
Example Output
=== Source Effectiveness Analysis ===
## Source Performance Ranking
| Source | Applied | Hired | Conversion | Quality |
|--------|---------|-------|-----------|---------|
| Referral | 89 | 23 | 25.8% | ★★★★★ |
| LinkedIn | 312 | 18 | 5.8% | ★★★ |
| Careers Page | 201 | 12 | 6.0% | ★★★ |
| Indeed | 187 | 4 | 2.1% | ★★ |
## Key Insights
- **Referrals** convert at 4.5x the rate of LinkedIn but generate 3.5x fewer applicants.
→ Invest in referral bonus program and make sharing easier.
- **Indeed** has highest volume but lowest conversion — candidates drop at Screen stage.
→ Tighten job description requirements to pre-filter.
- **Careers Page** converts well but lacks volume.
→ SEO + content marketing to drive organic traffic.
--- Referral Content ---
Know someone who'd thrive here? Our best hires come from people like you.
We're looking for [role] — someone who [specific trait]. Your referral gets
fast-tracked to a hiring manager call within 48 hours...
Error Handling
Source field is an array
Source field is an array
Lever stores sources as an array (candidates can have multiple). The code uses the first source. For multi-source attribution, count each source separately.
Archived vs active
Archived vs active
Hired candidates are often
isArchived: true with their last stage containing “hired”. Check both fields for accurate hire counts.Custom stage names
Custom stage names
Stage names are fully customizable in Lever. The keyword matching (
screen, onsite, offer) may miss custom names. Audit your stages with GET /stages first.