The Scenario
You’re preparing a campaign that needs to land across age groups. A single message rarely resonates the same way with a 22-year-old and a 62-year-old. Instead of guessing, you run the same Focus Group questions through Mavera’s generational personas and compare responses side by side. The output is a generation-specific content playbook built from structured audience data.Mavera-only workflow. No survey tools, no panel recruitment, no incentive budgets. Just Mavera’s Personas and Focus Groups surfaces.
When to Use This
- Campaign planning where the audience spans multiple age demographics.
- Content strategy reviews — which generation does your messaging over-index for?
- Product launches targeting “everyone” — find which generation is the best beachhead.
- Social media strategy — match message to the generational skew of each platform.
Architecture
| Mavera Surface | Role in Pipeline |
|---|---|
Personas (POST /personas) | Create 4 generational archetypes |
Focus Groups (POST /focus-groups) | Run identical questions with each generation |
| Chat (OpenAI-compatible) | Synthesize comparison into per-generation recommendations |
What You Need
| Requirement | Details |
|---|---|
| Mavera API key | Starts with mvra_live_. Get one at Developer Settings. |
| Python 3.8+ or Node.js 18+ | requests for Python; native fetch for Node. |
| Credits | ~85–190 total. See Credits Estimate. |
MAVERA_API_KEY=mvra_live_your_key_here
Step 1 — Create Generational Personas
import os, time, json, requests
API_KEY = os.environ["MAVERA_API_KEY"]
BASE = "https://app.mavera.io/api/v1"
HEADERS = {"Authorization": f"Bearer {API_KEY}", "Content-Type": "application/json"}
GENERATIONS = [
{"name": "Jordan — Gen Z Consumer", "role": "Gen Z (born 1997–2012)",
"description": "Digital native, 24 years old, lives on TikTok and Instagram. Values authenticity and social proof. Skeptical of corporate messaging, responds to creator-style content. Short attention span for traditional ads.",
"traits": ["digitally native", "authenticity-driven", "socially conscious", "short-form preference"], "gen": "gen_z"},
{"name": "Morgan — Millennial Professional", "role": "Millennial (born 1981–1996)",
"description": "35, mid-career professional. Responds to experiences over things, purpose-driven brands, and data-backed claims. Active on Instagram, LinkedIn, and podcasts. Willing to pay premium for convenience.",
"traits": ["experience-oriented", "purpose-driven", "research-heavy", "subscription-comfortable"], "gen": "millennial"},
{"name": "Casey — Gen X Decision-Maker", "role": "Gen X (born 1965–1980)",
"description": "48, peak earning years. Pragmatic and self-reliant. Responds to straightforward value propositions without hype. Brand-loyal when value is consistent. Email and web-first.",
"traits": ["pragmatic", "self-reliant", "brand-loyal", "hype-resistant"], "gen": "gen_x"},
{"name": "Pat — Boomer Evaluator", "role": "Baby Boomer (born 1946–1964)",
"description": "65, retired or senior advisory role. Values trust, credentials, and track record over novelty. Prefers longer-form content — articles, whitepapers, webinars. Active on Facebook and email.",
"traits": ["trust-driven", "detail-oriented", "authority-responsive", "long-form preference"], "gen": "boomer"},
]
def create_persona(g):
resp = requests.post(f"{BASE}/personas", headers=HEADERS, json={
"name": g["name"], "role": g["role"], "description": g["description"], "traits": g["traits"],
})
resp.raise_for_status()
data = resp.json()
print(f" Created: {data['name']} → {data['id']}")
return data["id"]
gen_personas = {}
for g in GENERATIONS:
gen_personas[g["gen"]] = {"id": create_persona(g), "name": g["name"]}
const API_KEY = process.env.MAVERA_API_KEY;
const BASE = "https://app.mavera.io/api/v1";
const HEADERS = { Authorization: `Bearer ${API_KEY}`, "Content-Type": "application/json" };
const GENERATIONS = [
{ name: "Jordan — Gen Z Consumer", role: "Gen Z (born 1997–2012)",
description: "Digital native, 24. Lives on TikTok/Instagram. Values authenticity and social proof. Skeptical of corporate messaging.",
traits: ["digitally native", "authenticity-driven", "socially conscious", "short-form preference"], gen: "gen_z" },
{ name: "Morgan — Millennial Professional", role: "Millennial (born 1981–1996)",
description: "35, mid-career. Purpose-driven, data-backed claims. Active on Instagram, LinkedIn, podcasts. Premium-for-convenience.",
traits: ["experience-oriented", "purpose-driven", "research-heavy", "subscription-comfortable"], gen: "millennial" },
{ name: "Casey — Gen X Decision-Maker", role: "Gen X (born 1965–1980)",
description: "48, peak earning. Pragmatic, straightforward value props. Brand-loyal, email/web-first.",
traits: ["pragmatic", "self-reliant", "brand-loyal", "hype-resistant"], gen: "gen_x" },
{ name: "Pat — Boomer Evaluator", role: "Baby Boomer (born 1946–1964)",
description: "65, senior advisory. Trust and credentials over novelty. Long-form content, Facebook/email.",
traits: ["trust-driven", "detail-oriented", "authority-responsive", "long-form preference"], gen: "boomer" },
];
async function createPersona(g) {
const resp = await fetch(`${BASE}/personas`, { method: "POST", headers: HEADERS,
body: JSON.stringify({ name: g.name, role: g.role, description: g.description, traits: g.traits }) });
const data = await resp.json();
if (data.error) throw new Error(data.error.message);
return data.id;
}
const genPersonas = {};
for (const g of GENERATIONS) genPersonas[g.gen] = { id: await createPersona(g), name: g.name };
Step 2 — Define Campaign and Run Focus Groups
One campaign concept, identical questions, one Focus Group per generation.CAMPAIGN = """
Campaign: "Own Your Future"
Product: Personal finance app for investing, budgeting, and retirement planning.
Tagline: "Smart money moves start here."
Visual: Split-screen — stressed about finances vs. confident and relaxed.
CTA: "Start your free plan today."
Channels: Instagram Reels, YouTube pre-roll, email nurture, LinkedIn ads.
"""
QUESTIONS = [
{"question": f"Share your honest first impression:\n\n{CAMPAIGN}", "type": "OPEN_ENDED", "order": 1},
{"question": "How compelling is the tagline 'Smart money moves start here'? (0-10)", "type": "NPS", "order": 2},
{"question": "Which channel would most effectively reach you?", "type": "MULTIPLE_CHOICE",
"options": ["Instagram Reels", "YouTube pre-roll", "Email nurture", "LinkedIn ads", "None"], "order": 3},
{"question": "What emotion does the split-screen visual evoke?", "type": "OPEN_ENDED", "order": 4},
{"question": "Would you click 'Start your free plan today'?", "type": "MULTIPLE_CHOICE",
"options": ["Yes — immediately", "Maybe — need more info", "No — not relevant", "No — CTA too generic"], "order": 5},
{"question": "What one change would make this more appealing to you?", "type": "OPEN_ENDED", "order": 6},
]
def run_gen_fg(gen_key, persona_id):
resp = requests.post(f"{BASE}/focus-groups", headers=HEADERS, json={
"name": f"Generational Test — {gen_key}", "persona_ids": [persona_id],
"sample_size": 10, "questions": QUESTIONS,
})
resp.raise_for_status()
return resp.json()
def poll_fg(fg_id, timeout_min=10):
for _ in range(timeout_min * 6):
resp = requests.get(f"{BASE}/focus-groups/{fg_id}", headers=HEADERS).json()
if "error" in resp: raise Exception(resp["error"]["message"])
if resp["status"] == "COMPLETED": return resp
time.sleep(10)
raise TimeoutError(f"Focus Group {fg_id} timed out")
# Create all FGs before polling — they run concurrently
fg_jobs = {}
for gen_key, info in gen_personas.items():
fg = run_gen_fg(gen_key, info["id"])
fg_jobs[gen_key] = fg["id"]
gen_results = {gk: poll_fg(fid) for gk, fid in fg_jobs.items()}
print("All 4 Focus Groups complete.")
const CAMPAIGN = `Campaign: "Own Your Future"\nProduct: Personal finance app.\nTagline: "Smart money moves start here."\nCTA: "Start your free plan today."`;
const QUESTIONS = [
{ question: `Share your honest first impression:\n\n${CAMPAIGN}`, type: "OPEN_ENDED", order: 1 },
{ question: "How compelling is the tagline? (0-10)", type: "NPS", order: 2 },
{ question: "Which channel would most effectively reach you?", type: "MULTIPLE_CHOICE",
options: ["Instagram Reels", "YouTube pre-roll", "Email nurture", "LinkedIn ads", "None"], order: 3 },
{ question: "What emotion does the split-screen visual evoke?", type: "OPEN_ENDED", order: 4 },
{ question: "Would you click 'Start your free plan today'?", type: "MULTIPLE_CHOICE",
options: ["Yes — immediately", "Maybe — need more info", "No — not relevant", "No — CTA too generic"], order: 5 },
{ question: "What one change would make this more appealing?", type: "OPEN_ENDED", order: 6 },
];
async function pollFG(fgId, timeoutMin = 10) {
for (let i = 0; i < timeoutMin * 6; i++) {
const resp = await fetch(`${BASE}/focus-groups/${fgId}`, { headers: HEADERS }).then((r) => r.json());
if (resp.error) throw new Error(resp.error.message);
if (resp.status === "COMPLETED") return resp;
await new Promise((r) => setTimeout(r, 10000));
}
throw new Error(`Focus Group ${fgId} timed out`);
}
const fgJobs = {};
for (const [gk, info] of Object.entries(genPersonas)) {
const resp = await fetch(`${BASE}/focus-groups`, { method: "POST", headers: HEADERS,
body: JSON.stringify({ name: `Gen Test — ${gk}`, persona_ids: [info.id], sample_size: 10, questions: QUESTIONS }) });
fgJobs[gk] = (await resp.json()).id;
}
const genResults = {};
for (const [gk, fid] of Object.entries(fgJobs)) genResults[gk] = await pollFG(fid);
All four Focus Groups are created before polling. Mavera processes them concurrently, so total wait time is roughly the same as a single group.
Step 3 — Compare Results Across Generations
def compare_generations(results_by_gen):
print("\n" + "=" * 70)
print("GENERATIONAL COMPARISON REPORT")
print("=" * 70)
labels = {"gen_z": "Gen Z", "millennial": "Millennial", "gen_x": "Gen X", "boomer": "Boomer"}
order = ["gen_z", "millennial", "gen_x", "boomer"]
for q_idx, q in enumerate(QUESTIONS):
print(f"\n{'─' * 60}\nQ{q_idx+1}: {q['question'].split(chr(10))[0][:70]}\n{'─' * 60}")
for gk in order:
qr = results_by_gen[gk]["results"][q_idx]
lbl = labels[gk]
if q["type"] == "NPS":
print(f" {lbl:12s} NPS: {qr.get('nps_score', 'N/A')}")
elif q["type"] == "MULTIPLE_CHOICE":
counts = qr.get("option_counts", {})
top = max(counts, key=counts.get) if counts else "N/A"
print(f" {lbl:12s} Top: {top}")
else:
print(f" {lbl:12s} {(qr.get('summary', 'N/A'))[:90]}")
compare_generations(gen_results)
function compareGenerations(resultsByGen) {
console.log("\n" + "=".repeat(70));
console.log("GENERATIONAL COMPARISON REPORT");
console.log("=".repeat(70));
const labels = { gen_z: "Gen Z", millennial: "Millennial", gen_x: "Gen X", boomer: "Boomer" };
const order = ["gen_z", "millennial", "gen_x", "boomer"];
QUESTIONS.forEach((q, idx) => {
console.log(`\n${"─".repeat(60)}\nQ${idx+1}: ${q.question.split("\n")[0].slice(0, 70)}\n${"─".repeat(60)}`);
for (const gk of order) {
const qr = resultsByGen[gk].results[idx];
if (q.type === "NPS") console.log(` ${labels[gk].padEnd(12)} NPS: ${qr.nps_score ?? "N/A"}`);
else if (q.type === "MULTIPLE_CHOICE") {
const top = Object.entries(qr.option_counts || {}).sort((a, b) => b[1] - a[1])[0];
console.log(` ${labels[gk].padEnd(12)} Top: ${top ? top[0] : "N/A"}`);
} else console.log(` ${labels[gk].padEnd(12)} ${(qr.summary || "N/A").slice(0, 90)}`);
}
});
}
compareGenerations(genResults);
Step 4 — Generate Recommendations via Chat
from openai import OpenAI
mavera = OpenAI(api_key=API_KEY, base_url="https://app.mavera.io/api/v1")
def generate_recs(results_by_gen):
labels = {"gen_z": "Gen Z", "millennial": "Millennial", "gen_x": "Gen X", "boomer": "Boomer"}
parts = []
for gk in ["gen_z", "millennial", "gen_x", "boomer"]:
p = f"## {labels[gk]}\n"
for qr in results_by_gen[gk].get("results", []):
q_short = qr["question"][:50]
if qr["type"] == "NPS": p += f"- {q_short}: NPS {qr.get('nps_score')}\n"
elif qr["type"] == "MULTIPLE_CHOICE": p += f"- {q_short}: {qr.get('option_counts', {})}\n"
else: p += f"- {q_short}: {qr.get('summary', 'N/A')}\n"
parts.append(p)
resp = mavera.responses.create(model="mavera-1", input=[{"role": "user", "content":
"Analyze these Focus Group results from 4 generational segments.\n\n" + "\n".join(parts) +
"\n\nProduce: 1) Generation resonance ranking 2) Per-generation content recs "
"3) A universal version 4) Channel allocation by generation"}])
return resp.output[0].content[0].text
recs = generate_recs(gen_results)
print("\n=== RECOMMENDATIONS ===\n")
print(recs)
const OpenAI = require("openai").default;
const mavera = new OpenAI({ apiKey: API_KEY, baseURL: "https://app.mavera.io/api/v1" });
async function generateRecs(resultsByGen) {
const labels = { gen_z: "Gen Z", millennial: "Millennial", gen_x: "Gen X", boomer: "Boomer" };
const parts = ["gen_z", "millennial", "gen_x", "boomer"].map((gk) => {
let p = `## ${labels[gk]}\n`;
for (const qr of resultsByGen[gk].results || []) {
const qs = (qr.question || "").slice(0, 50);
if (qr.type === "NPS") p += `- ${qs}: NPS ${qr.nps_score}\n`;
else if (qr.type === "MULTIPLE_CHOICE") p += `- ${qs}: ${JSON.stringify(qr.option_counts)}\n`;
else p += `- ${qs}: ${qr.summary || "N/A"}\n`;
}
return p;
});
const resp = await mavera.responses.create({ model: "mavera-1", input: [{ role: "user",
content: "Analyze these results from 4 generations.\n\n" + parts.join("\n") +
"\n\nProduce: 1) Resonance ranking 2) Per-gen recs 3) Universal version 4) Channel allocation" }] });
return resp.output[0].content[0].text;
}
console.log(await generateRecs(genResults));
Example Output
NPS by Generation:
Gen Z NPS: 45
Millennial NPS: 62
Gen X NPS: 38
Boomer NPS: 25
Preferred Channel:
Gen Z Top: Instagram Reels
Millennial Top: YouTube pre-roll
Gen X Top: Email nurture
Boomer Top: Email nurture
CTA Click Intent:
Gen Z Top: Maybe — need more info
Millennial Top: Yes — immediately
Gen X Top: Maybe — need more info
Boomer Top: No — CTA too generic
Variations
Add sub-segments within each generation
Add sub-segments within each generation
Create two personas per generation — one urban, one suburban — for 8 Focus Groups:
for g in GENERATIONS:
for locale in ["urban", "suburban"]:
variant = {**g, "name": f"{g['name']} ({locale})",
"description": g["description"] + f" Lives in a {locale} area."}
create_persona(variant)
Weight results by actual audience mix
Weight results by actual audience mix
weights = {"gen_z": 0.20, "millennial": 0.40, "gen_x": 0.30, "boomer": 0.10}
weighted_nps = sum(
gen_results[gk]["results"][1].get("nps_score", 0) * w for gk, w in weights.items()
)
print(f"Weighted NPS: {weighted_nps:.1f}")
Test multiple campaigns per generation
Test multiple campaigns per generation
for concept_idx, concept in enumerate([CAMPAIGN_A, CAMPAIGN_B]):
QUESTIONS[0]["question"] = f"React to this:\n\n{concept}"
for gk, info in gen_personas.items():
fg = run_gen_fg(gk, info["id"])
Generate per-generation ad copy with Generate
Generate per-generation ad copy with Generate
for gk in ["gen_z", "millennial", "gen_x", "boomer"]:
feedback = gen_results[gk]["results"][5].get("summary", "")
gen_resp = requests.post(f"{BASE}/generations", headers=HEADERS, json={
"app": "ad_copy", "inputs": {"audience_feedback": feedback, "generation": gk}
})
print(f"{gk}: {gen_resp.json().get('content', '')[:200]}")
Use Mavera's curated personas
Use Mavera's curated personas
resp = requests.get(f"{BASE}/personas", headers=HEADERS).json()
gen_z_id = next(p["id"] for p in resp if "Gen Z" in p.get("name", ""))
Credits Estimate
| Operation | Typical Cost | Notes |
|---|---|---|
| Persona creation (×4) | 0–20 | One-time; reuse across runs |
| Focus Group (×4, 10 respondents, 6 questions) | 80–160 | ~20–40 per group |
| Chat recommendations | 3–8 | Single synthesis call |
| Total | ~85–190 |
What’s Next
Industry Panel Simulation
Test with 10 buying-committee personas instead of generational segments
Message Testing Matrix
Combine multiple messages with multiple personas for a full fit matrix
Persona Debate
Pit opposing buyer types against each other for pricing insights
Content Localization
Adapt content across cultural regions, not just generations
Persona Selection Guide
Choose the right persona types for your research goal
Credits & Budget
Pre-flight checks and usage tracking