> ## 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.

# Source Effectiveness → Recruitment Marketing

> Analyze Lever postings and source data with Mave and Generate for source-optimized recruitment content

## 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:** Lever `GET /postings` + `GET /opportunities` (source data) → Mave `POST /mave/chat` (source analysis) → `POST /generations` (source-optimized content)

### Architecture

```mermaid theme={"dark"}
flowchart LR
    A["Lever GET /postings"] --> B["Cross-reference with /opportunities"]
    B --> C["Aggregate source × conversion rates"]
    C --> D["POST /api/v1/mave/chat"]
    D --> E["POST /api/v1/generations"]
    E --> F["Per-source recruitment content"]
```

### Code

<CodeGroup>
  ```python Python theme={"dark"}
  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)
  ```

  ```javascript JavaScript theme={"dark"}
  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));
  }
  ```
</CodeGroup>

### Example Output

```text theme={"dark"}
=== 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

<AccordionGroup>
  <Accordion title="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.</Accordion>
  <Accordion title="Archived vs active">Hired candidates are often `isArchived: true` with their last stage containing "hired". Check both fields for accurate hire counts.</Accordion>
  <Accordion title="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.</Accordion>
</AccordionGroup>
