The Scenario
Pricing is one of the hardest go-to-market decisions. Ask a budget buyer and they’ll say it’s too expensive. Ask a premium buyer and they’ll say it’s reasonable. The truth lives in the tension between them. This playbook creates two opposing personas and runs them through the same Focus Group evaluating your product at multiple price points. The output is a structured price-sensitivity analysis from opposing perspectives.Mavera-only workflow. No conjoint analysis software, no survey panels, no incentive budgets. Just Mavera’s Personas, Focus Groups, and Chat surfaces.
When to Use This
- Pre-launch pricing decisions — find the range that maximizes both WTP and adoption.
- Tier structure design — which features justify premium vs standard pricing?
- Price increase planning — simulate reactions from loyal customers and price-sensitive prospects.
- Competitive pricing — test your price against a competitor’s with opposing buyer mindsets.
- Freemium vs paid debates — let opposing personas argue for and against free tiers.
Architecture
| Mavera Surface | Role in Pipeline |
|---|---|
Personas (POST /personas) | Create opposing buyer archetypes |
Focus Groups (POST /focus-groups) | Both personas evaluate the product at 4 price points |
| Chat (OpenAI-compatible) | Synthesize opposing viewpoints into a recommendation |
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–205 total. See Credits Estimate. |
MAVERA_API_KEY=mvra_live_your_key_here
Step 1 — Create Opposing Personas
The key is making these genuinely opposed — not just different price preferences, but different value frameworks.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"}
OPPOSING = [
{"name": "Dana — Budget-Conscious Buyer",
"role": "Operations Manager at a bootstrapped 30-person startup",
"description": (
"Every purchase goes through a personal ROI calculator. Uses free tiers and open-source "
"before paying. Negotiates every contract. Measures value in dollars saved per hour. "
"Trusts usage-based pricing over flat fees. Will cancel the moment usage drops below cost."),
"traits": ["price-sensitive", "ROI-calculator mentality", "free-tier-first",
"negotiation-driven", "churn-prone if value drops"]},
{"name": "Victoria — Premium Buyer",
"role": "VP of Marketing at a Series C company ($30M raised)",
"description": (
"Pays significantly more for reliability, support, and time savings. Believes 'you get what you pay for.' "
"Values white-glove onboarding, dedicated support, and SLAs. Sees price as a quality signal — "
"if it's too cheap, something must be wrong. Happy to sign annual contracts."),
"traits": ["quality-over-cost", "time-is-money mentality", "enterprise-expectation",
"annual-contract-comfortable", "brand-as-signal buyer"]},
]
def create_persona(p):
resp = requests.post(f"{BASE}/personas", headers=HEADERS, json={
"name": p["name"], "role": p["role"], "description": p["description"], "traits": p["traits"]})
resp.raise_for_status()
data = resp.json()
print(f" Created: {data['name']} → {data['id']}")
return data["id"]
budget_id = create_persona(OPPOSING[0])
premium_id = create_persona(OPPOSING[1])
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 OPPOSING = [
{ name: "Dana — Budget-Conscious Buyer", role: "Ops Manager, bootstrapped 30-person startup",
description: "ROI calculator for every purchase. Free tiers first. Negotiates everything. " +
"Usage-based pricing preferred. Will cancel when value drops below cost.",
traits: ["price-sensitive", "ROI-calculator mentality", "free-tier-first", "negotiation-driven"] },
{ name: "Victoria — Premium Buyer", role: "VP Marketing, Series C ($30M raised)",
description: "Pays more for reliability and time savings. 'You get what you pay for.' " +
"Wants white-glove onboarding and SLAs. Price = quality signal. Annual contracts fine.",
traits: ["quality-over-cost", "time-is-money", "enterprise-expectation", "brand-as-signal buyer"] },
];
async function createPersona(p) {
const resp = await fetch(`${BASE}/personas`, { method: "POST", headers: HEADERS,
body: JSON.stringify({ name: p.name, role: p.role, description: p.description, traits: p.traits }) });
const data = await resp.json();
if (data.error) throw new Error(data.error.message);
return data.id;
}
const budgetId = await createPersona(OPPOSING[0]);
const premiumId = await createPersona(OPPOSING[1]);
Step 2 — Define Product and Price Points
PRODUCT = {
"name": "Mavera Pro",
"description": "AI-powered audience research. Focus Groups (unlimited), Video Analysis (50/mo), "
"Brand Voice, Chat API, Generate, Speak, dedicated Slack support. 5 seats.",
"features": ["Unlimited Focus Groups", "50 Video Analyses/mo", "Brand Voice extraction",
"Chat API", "Generate (all apps)", "Speak conversations", "Dedicated support", "5 seats"],
}
PRICES = [
{"label": "Starter", "price": "$199/month", "annual": "$1,990/year"},
{"label": "Growth", "price": "$499/month", "annual": "$4,990/year"},
{"label": "Scale", "price": "$999/month", "annual": "$9,990/year"},
{"label": "Enterprise", "price": "$2,499/month", "annual": "$24,990/year"},
]
const PRODUCT = {
name: "Mavera Pro",
description: "AI-powered audience research. Focus Groups, Video Analysis, Brand Voice, Chat API, Generate, Speak, support. 5 seats.",
features: ["Unlimited Focus Groups", "50 Video Analyses/mo", "Brand Voice", "Chat API", "Generate", "Speak", "Support", "5 seats"],
};
const PRICES = [
{ label: "Starter", price: "$199/month", annual: "$1,990/year" },
{ label: "Growth", price: "$499/month", annual: "$4,990/year" },
{ label: "Scale", price: "$999/month", annual: "$9,990/year" },
{ label: "Enterprise", price: "$2,499/month", annual: "$24,990/year" },
];
Step 3 — Run the Debate Focus Group
Both personas participate in the same group. Questions surface the tension between their opposing value frameworks.def build_debate_questions():
product_block = f"**{PRODUCT['name']}**\n{PRODUCT['description']}\n\n" + "\n".join(f"- {f}" for f in PRODUCT["features"])
price_block = "\n".join(f"- **{p['label']}**: {p['price']} (annual: {p['annual']})" for p in PRICES)
return [
{"question": f"Share your impression of value:\n\n{product_block}", "type": "OPEN_ENDED", "order": 1},
{"question": f"Which tier would you choose?\n\n{price_block}", "type": "MULTIPLE_CHOICE",
"options": [f"{p['label']} ({p['price']})" for p in PRICES] + ["None — would not purchase"], "order": 2},
{"question": "At which price is this 'too expensive' — you'd look for alternatives?", "type": "MULTIPLE_CHOICE",
"options": [f"{p['label']} ({p['price']})" for p in PRICES] + ["All acceptable"], "order": 3},
{"question": "At which price is this 'too cheap' — you'd question quality?", "type": "MULTIPLE_CHOICE",
"options": [f"{p['label']} ({p['price']})" for p in PRICES] + ["None too cheap"], "order": 4},
{"question": "What features justify the Enterprise tier?", "type": "OPEN_ENDED", "order": 5},
{"question": "What could be removed to make Starter a fair deal?", "type": "OPEN_ENDED", "order": 6},
{"question": "If a competitor offered 40% less, would you switch? What prevents it?", "type": "OPEN_ENDED", "order": 7},
{"question": "Rate overall value at Growth ($499/mo). (0-10)", "type": "NPS", "order": 8},
]
def poll_fg(fg_id, timeout_min=10):
for i 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")
fg = requests.post(f"{BASE}/focus-groups", headers=HEADERS, json={
"name": "Persona Debate — Price Positioning",
"persona_ids": [budget_id, premium_id], "sample_size": 10,
"questions": build_debate_questions(),
}).json()
print(f"Debate Focus Group: {fg['id']}")
results = poll_fg(fg["id"])
function buildDebateQuestions() {
const productBlock = `**${PRODUCT.name}**\n${PRODUCT.description}\n\n` + PRODUCT.features.map((f) => `- ${f}`).join("\n");
const priceBlock = PRICES.map((p) => `- **${p.label}**: ${p.price} (annual: ${p.annual})`).join("\n");
return [
{ question: `Share your impression:\n\n${productBlock}`, type: "OPEN_ENDED", order: 1 },
{ question: `Which tier?\n\n${priceBlock}`, type: "MULTIPLE_CHOICE",
options: [...PRICES.map((p) => `${p.label} (${p.price})`), "None — would not purchase"], order: 2 },
{ question: "At which price is this 'too expensive'?", type: "MULTIPLE_CHOICE",
options: [...PRICES.map((p) => `${p.label} (${p.price})`), "All acceptable"], order: 3 },
{ question: "At which price is this 'too cheap'?", type: "MULTIPLE_CHOICE",
options: [...PRICES.map((p) => `${p.label} (${p.price})`), "None too cheap"], order: 4 },
{ question: "What features justify Enterprise tier?", type: "OPEN_ENDED", order: 5 },
{ question: "What could be removed for Starter?", type: "OPEN_ENDED", order: 6 },
{ question: "Competitor at 40% less — switch?", type: "OPEN_ENDED", order: 7 },
{ question: "Rate value at Growth ($499/mo). (0-10)", type: "NPS", order: 8 },
];
}
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("Timed out");
}
const fg = await fetch(`${BASE}/focus-groups`, { method: "POST", headers: HEADERS,
body: JSON.stringify({ name: "Persona Debate", persona_ids: [budgetId, premiumId],
sample_size: 10, questions: buildDebateQuestions() }) }).then((r) => r.json());
const results = await pollFG(fg.id);
Step 4 — Parse the Debate
Separate responses by persona type and display side by side.def print_debate_report(fg_results):
print("\n" + "=" * 70)
print("PERSONA DEBATE — PRICING ANALYSIS")
print("=" * 70)
for qr in fg_results.get("results", []):
print(f"\n{'─' * 60}\nQ: {qr['question'][:75]}\n{'─' * 60}")
if qr["type"] == "MULTIPLE_CHOICE":
for opt, cnt in sorted(qr.get("option_counts", {}).items(), key=lambda x: -x[1]):
print(f" {opt:45s} {'█' * cnt} ({cnt})")
budget = [r for r in qr.get("responses", []) if "Budget" in r.get("persona_name", "")]
premium = [r for r in qr.get("responses", []) if "Premium" in r.get("persona_name", "")]
if budget:
choices = [r.get("value", "") for r in budget]
print(f"\n Budget tendency: {max(set(choices), key=choices.count)}")
if premium:
choices = [r.get("value", "") for r in premium]
print(f" Premium tendency: {max(set(choices), key=choices.count)}")
elif qr["type"] == "NPS":
print(f" Overall NPS: {qr.get('nps_score', 'N/A')}")
for label, keyword in [("Budget", "Budget"), ("Premium", "Premium")]:
scores = [int(r["value"]) for r in qr.get("responses", [])
if keyword in r.get("persona_name", "") and r.get("value") is not None]
if scores:
print(f" {label} avg: {sum(scores)/len(scores):.1f}/10")
elif qr["type"] == "OPEN_ENDED":
print(f" Summary: {qr.get('summary', 'N/A')}")
for keyword, label in [("Budget", "Budget"), ("Premium", "Premium")]:
resps = [r for r in qr.get("responses", []) if keyword in r.get("persona_name", "")]
if resps:
print(f"\n {label} perspective:")
print(f" \"{resps[0].get('value', '')[:140]}\"")
print_debate_report(results)
function printDebateReport(fgResults) {
console.log("\n" + "=".repeat(70));
console.log("PERSONA DEBATE — PRICING ANALYSIS");
console.log("=".repeat(70));
for (const qr of fgResults.results || []) {
console.log(`\n${"─".repeat(60)}\nQ: ${(qr.question || "").slice(0, 75)}\n${"─".repeat(60)}`);
if (qr.type === "MULTIPLE_CHOICE") {
for (const [opt, cnt] of Object.entries(qr.option_counts || {}).sort((a, b) => b[1] - a[1]))
console.log(` ${opt.padEnd(45)} ${"█".repeat(cnt)} (${cnt})`);
} else if (qr.type === "NPS") {
console.log(` Overall NPS: ${qr.nps_score ?? "N/A"}`);
for (const kw of ["Budget", "Premium"]) {
const scores = (qr.responses || []).filter((r) => (r.persona_name || "").includes(kw) && r.value != null);
if (scores.length) console.log(` ${kw} avg: ${(scores.reduce((s, r) => s + Number(r.value), 0) / scores.length).toFixed(1)}/10`);
}
} else {
console.log(` Summary: ${qr.summary || "N/A"}`);
}
}
}
printDebateReport(results);
Step 5 — Generate Pricing Recommendation
from openai import OpenAI
mavera = OpenAI(api_key=API_KEY, base_url="https://app.mavera.io/api/v1")
def generate_recommendation(fg_results):
summary = [{"question": qr["question"][:60], "type": qr["type"],
"summary": qr.get("summary", ""),
**({"nps": qr.get("nps_score")} if qr["type"] == "NPS" else {}),
**({"counts": qr.get("option_counts")} if qr["type"] == "MULTIPLE_CHOICE" else {})}
for qr in fg_results.get("results", [])]
prices = ", ".join(f"{p['label']}={p['price']}" for p in PRICES)
resp = mavera.responses.create(model="mavera-1", input=[{"role": "user", "content":
f"Two opposing buyer personas evaluated this product at these prices: {prices}\n\n"
f"Results:\n{json.dumps(summary, indent=2)}\n\n"
"Produce: 1) Van Westendorp interpretation 2) Recommended price point "
"3) Tier strategy (keep 4 or collapse?) 4) Feature allocation per tier "
"5) Competitive moat 6) Risk assessment"}])
return resp.output[0].content[0].text
rec = generate_recommendation(results)
print("\n=== PRICING RECOMMENDATION ===\n")
print(rec)
with open("pricing_debate_report.md", "w") as f:
f.write(f"# Persona Debate — Pricing Analysis\n\n{rec}")
const OpenAI = require("openai").default;
const mavera = new OpenAI({ apiKey: API_KEY, baseURL: "https://app.mavera.io/api/v1" });
async function generateRecommendation(fgResults) {
const summary = (fgResults.results || []).map((qr) => ({
question: (qr.question || "").slice(0, 60), type: qr.type, summary: qr.summary || "",
...(qr.type === "NPS" && { nps: qr.nps_score }),
...(qr.type === "MULTIPLE_CHOICE" && { counts: qr.option_counts }),
}));
const prices = PRICES.map((p) => `${p.label}=${p.price}`).join(", ");
const resp = await mavera.responses.create({ model: "mavera-1", input: [{ role: "user",
content: `Two opposing buyers evaluated at: ${prices}\n\n${JSON.stringify(summary, null, 2)}\n\n` +
"Produce: 1) Van Westendorp 2) Recommended price 3) Tier strategy 4) Feature allocation 5) Moat 6) Risks" }] });
return resp.output[0].content[0].text;
}
const rec = await generateRecommendation(results);
console.log(rec);
const fs = require("fs");
fs.writeFileSync("pricing_debate_report.md", `# Persona Debate\n\n${rec}`);
Example Output
Tier Selection:
Starter ($199/month) ████ (4)
Growth ($499/month) ███ (3)
Scale ($999/month) ██ (2)
None — would not purchase █ (1)
Budget tendency: Starter ($199/month)
Premium tendency: Scale ($999/month)
"Too Expensive" Threshold:
Budget tendency: Scale ($999/month)
Premium tendency: All acceptable
"Too Cheap" Threshold:
Budget tendency: None too cheap
Premium tendency: Starter ($199/month)
Value NPS at Growth ($499/mo):
Overall NPS: 35
Budget avg: 4.2/10
Premium avg: 7.8/10
Variations
Add a 'Pragmatic Middle' persona
Add a 'Pragmatic Middle' persona
middle = {"name": "Sam — Pragmatic Evaluator", "role": "Director of Marketing, 150 people",
"description": "Compares 3 vendors, picks best value/risk ratio. Reads G2 reviews. Wants a pilot.",
"traits": ["comparison-shopper", "risk-balanced", "pilot-oriented"]}
middle_id = create_persona(middle)
# Add middle_id to persona_ids list
Test specific feature bundles per tier
Test specific feature bundles per tier
TIERS = {
"Starter": {"features": ["Chat API", "5 Focus Groups/mo"], "price": "$199/mo"},
"Growth": {"features": ["Chat", "Unlimited FGs", "Brand Voice"], "price": "$499/mo"},
"Scale": {"features": ["Everything + Video + Speak"], "price": "$999/mo"},
}
Iterate with refined price points
Iterate with refined price points
After the first debate narrows the range, test smaller increments:
REFINED = [{"label": f"Tier {c}", "price": f"${p}/mo"} for c, p in
zip("ABCD", [349, 449, 549, 649])]
Use Mave for competitive benchmarking first
Use Mave for competitive benchmarking first
mave = requests.post(f"{BASE}/mave/chat", headers=HEADERS, json={
"message": "Research pricing of UserTesting, Wynter, Poll the People, Remesh. "
"Compare models, tiers, and what's included."}).json()
print(mave.get("content", ""))
Industry-specific opposing personas
Industry-specific opposing personas
healthcare_budget = {"name": "Community Clinic Director",
"description": "Tiny budget, grant-funded, high compliance needs..."}
healthcare_premium = {"name": "Hospital System CMO",
"description": "$50M marketing budget, demands SLAs and HIPAA compliance..."}
Credits Estimate
| Operation | Typical Cost | Notes |
|---|---|---|
| Persona creation (×2) | 0–10 | One-time; reuse across runs |
| Focus Group (2 personas, 10 respondents, 8 questions) | 80–180 | Primary cost driver |
| Chat recommendation | 5–15 | Single synthesis call |
| Total | ~85–205 |
The debate format is highly efficient — opposing perspectives from a single Focus Group rather than two separate studies. Roughly half the cost of running each persona independently.
What’s Next
Industry Panel Simulation
Expand from 2 opposing personas to 10 buying-committee members
Message Testing Matrix
Test which messaging angle works best at each price point
Pricing Research
Full Van Westendorp analysis with persona segments
Generational Content Testing
How pricing perception varies across age demographics
Persona Selection Guide
Choose the right persona types for your research goal
Credits & Budget
Pre-flight checks and usage tracking