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

# NPS Response → Brand Voice Impact

> Segment Typeform NPS respondents by tier and test messaging with Mavera Focus Groups for tier-specific communications

### Scenario

Your NPS survey segments respondents into promoters (9-10), passives (7-8), and detractors (0-6). Each tier has different messaging needs — promoters want to deepen their relationship, passives need a nudge, detractors need recovery. This job pulls NPS scores with their qualitative comments, groups them by tier, then runs a separate Focus Group per tier asking: "Which message improves your experience?" The result is tier-specific messaging validated by personas that match each NPS segment's actual mindset.

**Flow:** Typeform NPS responses → Split by promoter/passive/detractor → Create persona per tier → `POST /api/v1/focus-groups` per tier with candidate messages → "Which message improves your experience?" → Tier-optimized messaging

### Architecture

```mermaid theme={"dark"}
flowchart LR
A["NPS Responses"] --> B["Split by tier: 0-6, 7-8, 9-10"] --> C["POST /api/v1/personas per tier"] --> D["POST /api/v1/focus-groups"] --> E["Tier-specific messaging recommendations"]
```

### Code

<CodeGroup>
  ```python Python theme={"dark"}
  import os, requests, time

  TF = os.environ["TYPEFORM_TOKEN"]
  MV = os.environ["MAVERA_API_KEY"]
  TF_BASE = "https://api.typeform.com"
  MB = "https://app.mavera.io/api/v1"
  TF_H = {"Authorization": f"Bearer {TF}"}
  MV_H = {"Authorization": f"Bearer {MV}", "Content-Type": "application/json"}

  FORM_ID = os.environ.get("NPS_FORM_ID", "your_nps_form_id")

  # 1. Get form structure — find NPS field and comment field
  form = requests.get(f"{TF_BASE}/forms/{FORM_ID}", headers=TF_H).json()
  nps_field = None
  comment_field = None
  for f in form.get("fields", []):
      if f.get("type") == "opinion_scale" or f.get("type") == "rating":
          nps_field = f["id"]
      elif f.get("type") in ("long_text", "short_text") and not comment_field:
          comment_field = f["id"]

  # 2. Pull responses
  responses = []
  params = {"page_size": 1000}
  while True:
      r = requests.get(f"{TF_BASE}/forms/{FORM_ID}/responses",
          headers=TF_H, params=params)
      if r.status_code == 429:
          time.sleep(1); continue
      r.raise_for_status()
      data = r.json()
      responses.extend(data.get("items", []))
      if len(data.get("items", [])) < 1000: break
      params["before"] = data["items"][-1]["token"]
      time.sleep(0.6)

  # 3. Split by NPS tier
  tiers = {"promoter": [], "passive": [], "detractor": []}
  for resp in responses:
      score = None
      comment = ""
      for ans in resp.get("answers", []):
          fid = ans.get("field", {}).get("id")
          if fid == nps_field:
              score = ans.get("number", ans.get("rating"))
          elif fid == comment_field:
              comment = ans.get("text", "")

      if score is None:
          continue
      entry = {"score": score, "comment": comment}
      if score >= 9:
          tiers["promoter"].append(entry)
      elif score >= 7:
          tiers["passive"].append(entry)
      else:
          tiers["detractor"].append(entry)

  for tier, entries in tiers.items():
      avg = sum(e["score"] for e in entries) / max(len(entries), 1)
      print(f"{tier.title()}: {len(entries)} responses (avg: {avg:.1f})")

  # 4. Create persona per tier
  TIER_PROFILES = {
      "promoter": {
          "desc": "NPS 9-10. Enthusiastic advocates. Want deeper engagement and referral programs.",
          "mindset": "Loyal, engaged, willing to advocate",
      },
      "passive": {
          "desc": "NPS 7-8. Satisfied but not enthusiastic. Could switch if competitor offers more.",
          "mindset": "Content but uncommitted, comparing alternatives",
      },
      "detractor": {
          "desc": "NPS 0-6. Frustrated or disappointed. At risk of churn and negative word-of-mouth.",
          "mindset": "Frustrated, feeling unheard, considering alternatives",
      },
  }

  MESSAGE_CANDIDATES = [
      "We just launched our biggest update ever — 3 features your team has been asking for.",
      "Your feedback shaped our roadmap. Here's what we built because of you.",
      "We know we can do better. Here's our improvement plan for the next 90 days.",
      "Unlock premium features at no extra cost — as a thank you for being with us.",
      "Your peers are seeing 40% time savings. Let us help you get there too.",
  ]

  persona_ids = {}
  for tier, profile in TIER_PROFILES.items():
      entries = tiers[tier]
      comments = [e["comment"] for e in entries if e["comment"]][:10]
      comment_summary = "; ".join(comments[:5]) if comments else "No comments"

      p = requests.post(f"{MB}/personas", headers=MV_H, json={
          "name": f"NPS {tier.title()}",
          "description": (
              f"{profile['desc']} Based on {len(entries)} responses. "
              f"Sample feedback: {comment_summary[:300]}"
          ),
          "psychographic": {"mindset": profile["mindset"], "nps_tier": tier},
      })
      p.raise_for_status()
      persona_ids[tier] = p.json()["id"]
      time.sleep(0.3)

  # 5. Run Focus Group per tier
  for tier, pid in persona_ids.items():
      entries = tiers[tier]
      comments = [e["comment"] for e in entries if e["comment"]][:5]

      fg = requests.post(f"{MB}/focus-groups", headers=MV_H, json={
          "name": f"NPS {tier.title()} — Message Testing",
          "persona_ids": [pid],
          "context": (
              f"You are a {tier} (NPS {tiers[tier][0]['score'] if tiers[tier] else '?'}). "
              f"Recent feedback from this tier: {'; '.join(comments[:3])}"
          ),
          "questions": [
              f"Read these 5 messages. Which one would most improve your experience?\n\n" +
              "\n".join(f"{i+1}. {m}" for i, m in enumerate(MESSAGE_CANDIDATES)),
              "What specifically about your chosen message resonates?",
              "Which message would make things worse? Why?",
              "Write the message YOU would want to receive right now.",
              "What's the #1 thing that would change your NPS score?",
          ],
          "responses_per_persona": 3,
      }).json()

      for _ in range(20):
          time.sleep(5)
          result = requests.get(f"{MB}/focus-groups/{fg['id']}",
              headers=MV_H).json()
          if result.get("status") == "completed":
              break

      print(f"\n{'='*50}")
      print(f"NPS {tier.title()} Focus Group: {fg['id']}")
      print(f"{'='*50}")
      for resp in result.get("responses", []):
          print(f"Q: {resp.get('question', '')[:60]}...")
          print(f"A: {resp.get('answer', '')[:200]}\n")
  ```

  ```javascript JavaScript theme={"dark"}
  const TF = process.env.TYPEFORM_TOKEN;
  const MV = process.env.MAVERA_API_KEY;
  const TF_BASE = "https://api.typeform.com";
  const MB = "https://app.mavera.io/api/v1";
  const tfH = { Authorization: `Bearer ${TF}` };
  const mvH = { Authorization: `Bearer ${MV}`, "Content-Type": "application/json" };
  const FORM_ID = process.env.NPS_FORM_ID || "your_nps_form_id";

  // 1. Form structure
  const form = await fetch(`${TF_BASE}/forms/${FORM_ID}`, { headers: tfH }).then(r => r.json());
  let npsField = null, commentField = null;
  for (const f of form.fields || []) {
    if (["opinion_scale", "rating"].includes(f.type)) npsField = f.id;
    else if (["long_text", "short_text"].includes(f.type) && !commentField) commentField = f.id;
  }

  // 2. Pull responses
  const responses = [];
  const params = new URLSearchParams({ page_size: "1000" });
  while (true) {
    let res = await fetch(`${TF_BASE}/forms/${FORM_ID}/responses?${params}`, { headers: tfH });
    if (res.status === 429) { await new Promise(r => setTimeout(r, 1000)); continue; }
    const data = await res.json();
    responses.push(...(data.items || []));
    if ((data.items || []).length < 1000) break;
    params.set("before", data.items[data.items.length - 1].token);
    await new Promise(r => setTimeout(r, 600));
  }

  // 3. Split tiers
  const tiers = { promoter: [], passive: [], detractor: [] };
  for (const resp of responses) {
    let score = null, comment = "";
    for (const ans of resp.answers || []) {
      if (ans.field?.id === npsField) score = ans.number ?? ans.rating;
      if (ans.field?.id === commentField) comment = ans.text || "";
    }
    if (score == null) continue;
    const entry = { score, comment };
    if (score >= 9) tiers.promoter.push(entry);
    else if (score >= 7) tiers.passive.push(entry);
    else tiers.detractor.push(entry);
  }

  const MESSAGES = [
    "We just launched our biggest update ever — 3 features your team asked for.",
    "Your feedback shaped our roadmap. Here's what we built because of you.",
    "We know we can do better. Here's our improvement plan for the next 90 days.",
    "Unlock premium features at no extra cost — as a thank you.",
    "Your peers see 40% time savings. Let us help you get there too.",
  ];

  const PROFILES = {
    promoter: { desc: "NPS 9-10. Enthusiastic advocates.", mindset: "Loyal, willing to advocate" },
    passive: { desc: "NPS 7-8. Satisfied but not loyal.", mindset: "Content but comparing alternatives" },
    detractor: { desc: "NPS 0-6. Frustrated, at risk.", mindset: "Frustrated, considering switching" },
  };

  // 4. Create personas + focus groups
  for (const [tier, profile] of Object.entries(PROFILES)) {
    const entries = tiers[tier];
    const comments = entries.filter(e => e.comment).map(e => e.comment).slice(0, 5);

    const p = await fetch(`${MB}/personas`, { method: "POST", headers: mvH,
      body: JSON.stringify({ name: `NPS ${tier.charAt(0).toUpperCase() + tier.slice(1)}`,
        description: `${profile.desc} N=${entries.length}. Feedback: ${comments.slice(0, 3).join("; ").slice(0, 300)}`,
        psychographic: { mindset: profile.mindset, nps_tier: tier } }),
    }).then(r => r.json());

    const fg = await fetch(`${MB}/focus-groups`, { method: "POST", headers: mvH,
      body: JSON.stringify({
        name: `NPS ${tier.charAt(0).toUpperCase() + tier.slice(1)} — Messages`,
        persona_ids: [p.id],
        context: `You are a ${tier}. Recent feedback: ${comments.slice(0, 3).join("; ")}`,
        questions: [
          `Read these 5 messages. Which improves your experience most?\n${MESSAGES.map((m, i) => `${i + 1}. ${m}`).join("\n")}`,
          "What about your chosen message resonates?",
          "Which message would make things worse? Why?",
          "Write the message YOU would want to receive.",
          "What's the #1 thing that would change your NPS score?",
        ],
        responses_per_persona: 3,
      }),
    }).then(r => r.json());

    let result;
    for (let i = 0; i < 20; i++) {
      await new Promise(r => setTimeout(r, 5000));
      result = await fetch(`${MB}/focus-groups/${fg.id}`, { headers: mvH }).then(r => r.json());
      if (result.status === "completed") break;
    }

    console.log(`\n${"=".repeat(50)}\nNPS ${tier.toUpperCase()}: ${fg.id}\n${"=".repeat(50)}`);
    for (const resp of result.responses || [])
      console.log(`Q: ${(resp.question || "").slice(0, 60)}...\nA: ${(resp.answer || "").slice(0, 200)}\n`);
  }
  ```
</CodeGroup>

### Example Output

```text theme={"dark"}
Promoter: 142 responses (avg: 9.4)
Passive: 98 responses (avg: 7.6)
Detractor: 67 responses (avg: 4.2)

==================================================
NPS PROMOTER Focus Group
==================================================
Q: Read these 5 messages. Which one would most improve your ex...
A: Message 2 — "Your feedback shaped our roadmap." This validates
   that my input matters. I already love the product; what keeps me
   engaged is feeling like a co-creator, not just a customer.

Q: Write the message YOU would want to receive right now.
A: "You're one of our top 50 power users. We're building a beta
   program — want early access and a direct line to our PM?"

==================================================
NPS DETRACTOR Focus Group
==================================================
Q: Read these 5 messages. Which one would most improve your ex...
A: Message 3 — "We know we can do better." The others feel tone-deaf
   when I'm already frustrated. Acknowledging the problem first is
   the only credible starting point.

Q: Which message would make things worse? Why?
A: Message 1 — "Our biggest update ever." I don't care about new
   features when the existing ones don't work reliably. Ship features
   before you celebrate them.
```

### Error Handling

<AccordionGroup>
  <Accordion title="NPS field detection">The code looks for `opinion_scale` or `rating` fields. If your NPS uses a different Typeform field type (e.g., `number`), adjust the detection logic.</Accordion>
  <Accordion title="Tier-specific focus groups are sequential">Running 3 focus groups sequentially takes 3-5 minutes. For parallel execution, use `asyncio` (Python) or `Promise.all` (JavaScript) — but monitor Mavera rate limits.</Accordion>
  <Accordion title="Small tier sizes">Detractor tiers often have fewer responses. Even 10 NPS responses per tier provide enough context for meaningful persona creation.</Accordion>
</AccordionGroup>
