Mavera Surfaces Used
| Surface | Role |
|---|---|
Personas (POST /personas, GET /personas) | Create budget-segment personas (price-sensitive, mid-market, enterprise) |
Focus Groups (POST /focus-groups) | Run pricing sensitivity study with Slider, Likert, and Open-Ended questions |
Chat + response_format | Synthesize results into a price sensitivity report with optimal price range |
The Van Westendorp Price Sensitivity Meter uses four questions to find optimal pricing. This playbook adapts those questions to Mavera’s Slider question type, running them across budget-segment personas to produce a synthetic price sensitivity analysis.
What Value Does Mavera Add?
| Value | How |
|---|---|
| Insurance | Test pricing with 4 distinct budget segments before announcing. Catch price-perception mismatches early. |
| Opening new doors | Run pricing studies for new features, bundles, or market segments in an afternoon — no recruiting, no incentives. |
| Saving time | Traditional Van Westendorp studies take 2-4 weeks and $10K+. This runs in 30 minutes for ~300 credits. |
When to Use This
- You’re setting launch pricing for a new product or feature and need segment-specific sensitivity data.
- You’re considering a price increase and want to estimate the elasticity before announcing.
- You’re designing tiered pricing and need to validate where tier boundaries should fall.
- You’re entering a new market and need a pricing anchor based on local buyer expectations.
What You Need
| Requirement | Details |
|---|---|
| Mavera API key | Starts with mvra_live_. Get one at Developer Settings. |
| Workspace ID | From your dashboard URL (ws_...). |
| Product description | Clear description of what’s being priced (features, value prop, competitive context). |
| Price range | Reasonable min/max for the Slider questions (e.g. 0−500/month). |
| Credits | ~200–500 total. See Credits Estimate. |
| Python 3.8+ or Node.js 18+ | requests / openai for Python; native fetch for Node. |
MAVERA_API_KEY=mvra_live_your_key_here
MAVERA_WORKSPACE_ID=ws_your_workspace_id
The Van Westendorp Framework
The original Van Westendorp asks four questions. Mavera adapts each to a Slider question type:| Van Westendorp Question | Mavera Slider | What It Finds |
|---|---|---|
| ”At what price would this be too expensive — you wouldn’t consider buying?” | Slider: 0–max | Upper bound (ceiling) |
| “At what price would this be expensive but worth considering?” | Slider: 0–max | High-end acceptable range |
| ”At what price would this be a great deal — a bargain?” | Slider: 0–max | Low-end sweet spot |
| ”At what price would this be so cheap you’d question the quality?” | Slider: 0–max | Lower bound (floor) |
- Point of Marginal Cheapness (PMC): Where “too cheap” meets “bargain”
- Point of Marginal Expensiveness (PME): Where “too expensive” meets “expensive but worth it”
- Optimal Price Point (OPP): Where “too cheap” meets “too expensive”
- Acceptable Price Range: Between PMC and PME
Architecture
The Flow
1
Define your product and price range
Write a clear product description and set a realistic price range for the Slider min/max. Too narrow misses the range; too wide reduces precision.
2
Create 4 budget-segment personas
Build personas representing distinct budget tiers: bootstrapped startup, growth-stage, mid-market, and enterprise. Each brings a different price sensitivity.
3
Run the pricing Focus Group
40 respondents across 4 personas (10 per persona). 4 Van Westendorp Slider questions + 1 Likert (value-for-money) + 1 Open-Ended (pricing model preference).
4
Poll for completion
Pricing focus groups with 40 respondents and 6 questions typically complete in 5-10 minutes.
5
Calculate price sensitivity curves
Extract Slider medians and distributions. Compute the four intersection points.
6
Generate the pricing report
Use Chat with structured output to synthesize findings into a pricing recommendation with segment breakdowns.
Code: Full Pricing Research Pipeline
Setup
import os
import json
import time
import statistics
import requests
from openai import OpenAI
MAVERA_API_KEY = os.environ["MAVERA_API_KEY"]
WORKSPACE_ID = os.environ["MAVERA_WORKSPACE_ID"]
BASE = "https://app.mavera.io/api/v1"
HEADERS = {
"Authorization": f"Bearer {MAVERA_API_KEY}",
"Content-Type": "application/json",
}
mavera = OpenAI(api_key=MAVERA_API_KEY, base_url=BASE)
PRODUCT_DESCRIPTION = (
"An AI-powered market research platform that lets you create synthetic audience "
"personas and run focus groups, pricing studies, and message testing in minutes. "
"Replaces traditional research agencies for positioning, pricing, and campaign validation. "
"Includes: unlimited personas, focus groups (up to N=100), AI research agent, "
"video analysis, and content generation."
)
PRICE_RANGE = {"min": 0, "max": 500, "unit": "$/month"}
BUDGET_PERSONAS = [
{
"name": "Bootstrapped Startup Founder",
"description": (
"Solo founder or 2-person team at a pre-revenue startup. "
"Total marketing budget: $500-$2K/month. Every dollar is scrutinized. "
"Uses free tools wherever possible. Will pay for tools that directly "
"generate revenue or save significant time. Compares against doing it manually."
),
},
{
"name": "Growth-Stage Marketing Lead",
"description": (
"Head of Marketing at a Series A/B startup (20-80 employees). "
"Marketing budget: $10K-$50K/month. Has budget flexibility but needs to justify "
"ROI for each tool. Currently spending $5-15K/quarter on ad hoc research. "
"Evaluates based on cost-per-insight vs traditional agencies."
),
},
{
"name": "Mid-Market VP Marketing",
"description": (
"VP of Marketing at a $10M-$50M ARR company (100-500 employees). "
"Marketing budget: $50K-$200K/month. Has a research budget line item. "
"Currently uses a mix of agencies ($20-50K/project) and DIY surveys. "
"Values speed and always-on availability over cost savings."
),
},
{
"name": "Enterprise CMO",
"description": (
"CMO at a $100M+ company with a dedicated insights team. "
"Research budget: $500K-$2M/year. Uses top-tier agencies for major projects. "
"Evaluates platforms on capability depth, security, and integration with "
"existing insights stack. Price is secondary to reliability and scalability."
),
},
]
import OpenAI from "openai";
const MAVERA_API_KEY = process.env.MAVERA_API_KEY;
const WORKSPACE_ID = process.env.MAVERA_WORKSPACE_ID;
const BASE = "https://app.mavera.io/api/v1";
const HEADERS = {
Authorization: `Bearer ${MAVERA_API_KEY}`,
"Content-Type": "application/json",
};
const mavera = new OpenAI({ apiKey: MAVERA_API_KEY, baseURL: BASE });
const PRODUCT_DESCRIPTION =
"An AI-powered market research platform that lets you create synthetic audience " +
"personas and run focus groups, pricing studies, and message testing in minutes. " +
"Replaces traditional research agencies for positioning, pricing, and campaign validation. " +
"Includes: unlimited personas, focus groups (up to N=100), AI research agent, " +
"video analysis, and content generation.";
const PRICE_RANGE = { min: 0, max: 500, unit: "$/month" };
const BUDGET_PERSONAS = [
{
name: "Bootstrapped Startup Founder",
description:
"Solo founder or 2-person team at a pre-revenue startup. " +
"Total marketing budget: $500-$2K/month. Every dollar is scrutinized. " +
"Uses free tools wherever possible. Compares against doing it manually.",
},
{
name: "Growth-Stage Marketing Lead",
description:
"Head of Marketing at a Series A/B startup (20-80 employees). " +
"Marketing budget: $10K-$50K/month. Has budget flexibility but needs to justify " +
"ROI for each tool. Currently spending $5-15K/quarter on ad hoc research.",
},
{
name: "Mid-Market VP Marketing",
description:
"VP of Marketing at a $10M-$50M ARR company (100-500 employees). " +
"Marketing budget: $50K-$200K/month. Has a research budget line item. " +
"Values speed and always-on availability over cost savings.",
},
{
name: "Enterprise CMO",
description:
"CMO at a $100M+ company with a dedicated insights team. " +
"Research budget: $500K-$2M/year. Uses top-tier agencies for major projects. " +
"Price is secondary to reliability and scalability.",
},
];
Stage 1 — Create Budget-Segment Personas
def create_budget_personas() -> list[str]:
"""Create 4 budget-segment personas and return their IDs."""
persona_ids = []
for persona in BUDGET_PERSONAS:
resp = requests.post(
f"{BASE}/personas",
headers=HEADERS,
json={
"name": persona["name"],
"description": persona["description"],
"workspace_id": WORKSPACE_ID,
},
).json()
if "error" in resp:
raise Exception(f"Failed to create persona '{persona['name']}': {resp['error']['message']}")
persona_ids.append(resp["id"])
print(f"✓ Created persona: {persona['name']} ({resp['id']})")
return persona_ids
async function createBudgetPersonas() {
const personaIds = [];
for (const persona of BUDGET_PERSONAS) {
const resp = await fetch(`${BASE}/personas`, {
method: "POST",
headers: HEADERS,
body: JSON.stringify({
name: persona.name,
description: persona.description,
workspace_id: WORKSPACE_ID,
}),
}).then((r) => r.json());
if (resp.error)
throw new Error(`Failed to create persona '${persona.name}': ${resp.error.message}`);
personaIds.push(resp.id);
console.log(`✓ Created persona: ${persona.name} (${resp.id})`);
}
return personaIds;
}
Stage 2 — Run Pricing Focus Group
Six questions: 4 Van Westendorp Sliders, 1 Likert (value-for-money), 1 Open-Ended (pricing model).def run_pricing_focus_group(persona_ids: list[str]) -> dict:
"""Run the Van Westendorp pricing focus group."""
product_context = (
f"Product: {PRODUCT_DESCRIPTION}\n\n"
f"Pricing is per month ({PRICE_RANGE['unit']}). "
f"Consider the full feature set when answering."
)
payload = {
"name": "Pricing Research — Van Westendorp × 4 Segments",
"sample_size": 40,
"persona_ids": persona_ids,
"workspace_id": WORKSPACE_ID,
"context": product_context,
"questions": [
{
"question": (
f"At what price per month would this product be SO EXPENSIVE "
f"that you would not consider buying it, regardless of quality?\n\n"
f"Product: {PRODUCT_DESCRIPTION}"
),
"type": "SLIDER",
"min_value": PRICE_RANGE["min"],
"max_value": PRICE_RANGE["max"],
"min_label": f"${PRICE_RANGE['min']}/mo",
"max_label": f"${PRICE_RANGE['max']}/mo",
"order": 1,
},
{
"question": (
f"At what price per month would this product be EXPENSIVE "
f"but you would still consider buying it because of its value?"
),
"type": "SLIDER",
"min_value": PRICE_RANGE["min"],
"max_value": PRICE_RANGE["max"],
"min_label": f"${PRICE_RANGE['min']}/mo",
"max_label": f"${PRICE_RANGE['max']}/mo",
"order": 2,
},
{
"question": (
f"At what price per month would this product be a BARGAIN — "
f"a great deal for what you get?"
),
"type": "SLIDER",
"min_value": PRICE_RANGE["min"],
"max_value": PRICE_RANGE["max"],
"min_label": f"${PRICE_RANGE['min']}/mo",
"max_label": f"${PRICE_RANGE['max']}/mo",
"order": 3,
},
{
"question": (
f"At what price per month would this product be SO CHEAP that "
f"you would question its quality and reliability?"
),
"type": "SLIDER",
"min_value": PRICE_RANGE["min"],
"max_value": PRICE_RANGE["max"],
"min_label": f"${PRICE_RANGE['min']}/mo",
"max_label": f"${PRICE_RANGE['max']}/mo",
"order": 4,
},
{
"question": (
"If this product were priced at $149/month, would it represent "
"good value for money? Rate 1-10."
),
"type": "LIKERT",
"scale": 10,
"order": 5,
},
{
"question": (
"Which pricing model would you prefer for this type of product?\n"
"- Flat monthly fee (unlimited usage)\n"
"- Usage-based (pay per focus group / research query)\n"
"- Tiered plans (starter / pro / enterprise)\n"
"- Annual contract with monthly billing\n\n"
"Explain your reasoning."
),
"type": "OPEN_ENDED",
"order": 6,
},
],
}
resp = requests.post(
f"{BASE}/focus-groups",
headers=HEADERS,
json=payload,
).json()
if "error" in resp:
raise Exception(resp["error"]["message"])
print(f"✓ Pricing focus group created: {resp['id']}")
print(f" Sample size: 40 (10 per segment)")
print(f" Questions: 6 (4 sliders + 1 likert + 1 open-ended)")
return resp
def poll_focus_group(fg_id: str, timeout_min: int = 15) -> dict:
"""Poll until the focus group completes."""
for attempt 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.get("status") == "COMPLETED":
print(f"✓ Pricing focus group completed")
return resp
if resp.get("status") == "FAILED":
raise Exception(f"Focus group failed: {resp.get('error', 'Unknown')}")
time.sleep(10)
raise TimeoutError(f"Focus group {fg_id} timed out")
async function runPricingFocusGroup(personaIds) {
const productContext =
`Product: ${PRODUCT_DESCRIPTION}\n\n` +
`Pricing is per month (${PRICE_RANGE.unit}). Consider the full feature set.`;
const payload = {
name: "Pricing Research — Van Westendorp × 4 Segments",
sample_size: 40,
persona_ids: personaIds,
workspace_id: WORKSPACE_ID,
context: productContext,
questions: [
{
question:
`At what price per month would this product be SO EXPENSIVE ` +
`that you would not consider buying it, regardless of quality?\n\n` +
`Product: ${PRODUCT_DESCRIPTION}`,
type: "SLIDER",
min_value: PRICE_RANGE.min, max_value: PRICE_RANGE.max,
min_label: `$${PRICE_RANGE.min}/mo`, max_label: `$${PRICE_RANGE.max}/mo`,
order: 1,
},
{
question:
`At what price per month would this product be EXPENSIVE ` +
`but you would still consider buying it because of its value?`,
type: "SLIDER",
min_value: PRICE_RANGE.min, max_value: PRICE_RANGE.max,
min_label: `$${PRICE_RANGE.min}/mo`, max_label: `$${PRICE_RANGE.max}/mo`,
order: 2,
},
{
question:
`At what price per month would this product be a BARGAIN — ` +
`a great deal for what you get?`,
type: "SLIDER",
min_value: PRICE_RANGE.min, max_value: PRICE_RANGE.max,
min_label: `$${PRICE_RANGE.min}/mo`, max_label: `$${PRICE_RANGE.max}/mo`,
order: 3,
},
{
question:
`At what price per month would this product be SO CHEAP that ` +
`you would question its quality and reliability?`,
type: "SLIDER",
min_value: PRICE_RANGE.min, max_value: PRICE_RANGE.max,
min_label: `$${PRICE_RANGE.min}/mo`, max_label: `$${PRICE_RANGE.max}/mo`,
order: 4,
},
{
question:
"If this product were priced at $149/month, would it represent " +
"good value for money? Rate 1-10.",
type: "LIKERT", scale: 10, order: 5,
},
{
question:
"Which pricing model would you prefer for this type of product?\n" +
"- Flat monthly fee (unlimited usage)\n" +
"- Usage-based (pay per focus group / research query)\n" +
"- Tiered plans (starter / pro / enterprise)\n" +
"- Annual contract with monthly billing\n\n" +
"Explain your reasoning.",
type: "OPEN_ENDED", order: 6,
},
],
};
const resp = await fetch(`${BASE}/focus-groups`, {
method: "POST", headers: HEADERS,
body: JSON.stringify(payload),
}).then((r) => r.json());
if (resp.error) throw new Error(resp.error.message);
console.log(`✓ Pricing focus group created: ${resp.id}`);
return resp;
}
async function pollFocusGroup(fgId, timeoutMin = 15) {
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") {
console.log("✓ Pricing focus group completed");
return resp;
}
if (resp.status === "FAILED")
throw new Error(`Focus group failed: ${resp.error || "Unknown"}`);
await new Promise((r) => setTimeout(r, 10000));
}
throw new Error(`Focus group ${fgId} timed out`);
}
Set Slider
max_value high enough to capture the true ceiling. If most “too expensive” responses cluster at the max, your range is too narrow and you’ll miss price points.Stage 3 — Calculate Price Sensitivity
Extract Slider values and compute the Van Westendorp intersection points.VW_QUESTION_MAP = {
"too_expensive": "SO EXPENSIVE",
"expensive_worth_it": "EXPENSIVE but you would still consider",
"bargain": "BARGAIN",
"too_cheap": "SO CHEAP",
}
def extract_slider_data(fg_results: dict) -> dict:
"""Extract Van Westendorp slider responses from focus group results."""
slider_data = {}
for result in fg_results.get("results", []):
if result.get("type") != "SLIDER":
continue
question = result.get("question", "")
for key, marker in VW_QUESTION_MAP.items():
if marker in question:
slider_data[key] = {
"values": result.get("values", []),
"mean": result.get("mean_value", 0),
"median": result.get("median_value", 0),
"std_dev": result.get("std_dev", 0),
"distribution": result.get("distribution", {}),
"per_persona": result.get("per_persona", []),
}
break
return slider_data
def calculate_vw_points(slider_data: dict) -> dict:
"""Calculate Van Westendorp intersection points from slider medians."""
too_expensive = slider_data.get("too_expensive", {}).get("median", 0)
expensive_ok = slider_data.get("expensive_worth_it", {}).get("median", 0)
bargain = slider_data.get("bargain", {}).get("median", 0)
too_cheap = slider_data.get("too_cheap", {}).get("median", 0)
# Approximate intersection points
pmc = (too_cheap + bargain) / 2 # Point of Marginal Cheapness
pme = (too_expensive + expensive_ok) / 2 # Point of Marginal Expensiveness
opp = (too_cheap + too_expensive) / 2 # Optimal Price Point
idp = (bargain + expensive_ok) / 2 # Indifference Price Point
return {
"point_of_marginal_cheapness": round(pmc, 2),
"point_of_marginal_expensiveness": round(pme, 2),
"optimal_price_point": round(opp, 2),
"indifference_price_point": round(idp, 2),
"acceptable_range": {
"low": round(pmc, 2),
"high": round(pme, 2),
},
"raw_medians": {
"too_expensive": too_expensive,
"expensive_worth_it": expensive_ok,
"bargain": bargain,
"too_cheap": too_cheap,
},
}
def segment_analysis(slider_data: dict) -> dict:
"""Break down pricing by persona segment."""
segments = {}
for key, data in slider_data.items():
for persona_result in data.get("per_persona", []):
pid = persona_result.get("persona_id", "unknown")
if pid not in segments:
segments[pid] = {}
segments[pid][key] = {
"mean": persona_result.get("mean_value", 0),
"median": persona_result.get("median_value", 0),
}
# Calculate per-segment VW points
for pid, data in segments.items():
te = data.get("too_expensive", {}).get("median", 0)
eo = data.get("expensive_worth_it", {}).get("median", 0)
bg = data.get("bargain", {}).get("median", 0)
tc = data.get("too_cheap", {}).get("median", 0)
segments[pid]["optimal_price"] = round((tc + te) / 2, 2)
segments[pid]["acceptable_range"] = {
"low": round((tc + bg) / 2, 2),
"high": round((te + eo) / 2, 2),
}
return segments
const VW_QUESTION_MAP = {
too_expensive: "SO EXPENSIVE",
expensive_worth_it: "EXPENSIVE but you would still consider",
bargain: "BARGAIN",
too_cheap: "SO CHEAP",
};
function extractSliderData(fgResults) {
const sliderData = {};
for (const result of fgResults.results || []) {
if (result.type !== "SLIDER") continue;
for (const [key, marker] of Object.entries(VW_QUESTION_MAP)) {
if (result.question?.includes(marker)) {
sliderData[key] = {
values: result.values || [],
mean: result.mean_value || 0,
median: result.median_value || 0,
std_dev: result.std_dev || 0,
distribution: result.distribution || {},
per_persona: result.per_persona || [],
};
break;
}
}
}
return sliderData;
}
function calculateVWPoints(sliderData) {
const te = sliderData.too_expensive?.median || 0;
const eo = sliderData.expensive_worth_it?.median || 0;
const bg = sliderData.bargain?.median || 0;
const tc = sliderData.too_cheap?.median || 0;
const pmc = (tc + bg) / 2;
const pme = (te + eo) / 2;
const opp = (tc + te) / 2;
const idp = (bg + eo) / 2;
return {
point_of_marginal_cheapness: Math.round(pmc * 100) / 100,
point_of_marginal_expensiveness: Math.round(pme * 100) / 100,
optimal_price_point: Math.round(opp * 100) / 100,
indifference_price_point: Math.round(idp * 100) / 100,
acceptable_range: {
low: Math.round(pmc * 100) / 100,
high: Math.round(pme * 100) / 100,
},
raw_medians: { too_expensive: te, expensive_worth_it: eo, bargain: bg, too_cheap: tc },
};
}
function segmentAnalysis(sliderData) {
const segments = {};
for (const [key, data] of Object.entries(sliderData)) {
for (const pr of data.per_persona || []) {
const pid = pr.persona_id || "unknown";
if (!segments[pid]) segments[pid] = {};
segments[pid][key] = { mean: pr.mean_value || 0, median: pr.median_value || 0 };
}
}
for (const [pid, data] of Object.entries(segments)) {
const te = data.too_expensive?.median || 0;
const eo = data.expensive_worth_it?.median || 0;
const bg = data.bargain?.median || 0;
const tc = data.too_cheap?.median || 0;
segments[pid].optimal_price = Math.round(((tc + te) / 2) * 100) / 100;
segments[pid].acceptable_range = {
low: Math.round(((tc + bg) / 2) * 100) / 100,
high: Math.round(((te + eo) / 2) * 100) / 100,
};
}
return segments;
}
Stage 4 — Generate Pricing Report
PRICING_REPORT_SCHEMA = {"type": "json_schema", "json_schema": {
"name": "pricing_report", "strict": True,
"schema": {
"type": "object",
"properties": {
"executive_summary": {"type": "string"},
"optimal_price_point": {"type": "number"},
"acceptable_range_low": {"type": "number"},
"acceptable_range_high": {"type": "number"},
"recommended_price": {"type": "number"},
"recommended_pricing_model": {"type": "string"},
"segment_recommendations": {
"type": "array",
"items": {
"type": "object",
"properties": {
"segment": {"type": "string"},
"optimal_price": {"type": "number"},
"willingness_to_pay": {"type": "string"},
"pricing_model_preference": {"type": "string"},
},
"required": ["segment", "optimal_price", "willingness_to_pay", "pricing_model_preference"],
},
},
"tier_suggestions": {
"type": "array",
"items": {
"type": "object",
"properties": {
"tier_name": {"type": "string"},
"price_point": {"type": "number"},
"target_segment": {"type": "string"},
"key_features": {"type": "array", "items": {"type": "string"}},
},
"required": ["tier_name", "price_point", "target_segment", "key_features"],
},
},
"risks": {
"type": "array",
"items": {"type": "string"},
},
"next_steps": {
"type": "array",
"items": {"type": "string"},
},
},
"required": [
"executive_summary", "optimal_price_point", "acceptable_range_low",
"acceptable_range_high", "recommended_price", "recommended_pricing_model",
"segment_recommendations", "tier_suggestions", "risks", "next_steps",
],
},
}}
def generate_pricing_report(
vw_points: dict, segments: dict, fg_results: dict
) -> dict:
"""Synthesize pricing data into a recommendation report."""
# Extract Likert and open-ended results
likert_result = None
open_ended_result = None
for result in fg_results.get("results", []):
if result.get("type") == "LIKERT":
likert_result = result
elif result.get("type") == "OPEN_ENDED":
open_ended_result = result
prompt = (
"You are a pricing strategist. Analyze this Van Westendorp pricing data "
"and produce a pricing recommendation.\n\n"
f"## Product\n{PRODUCT_DESCRIPTION}\n\n"
f"## Van Westendorp Results (All Segments)\n{json.dumps(vw_points, indent=2)}\n\n"
f"## Segment-Level Analysis\n{json.dumps(segments, indent=2)}\n\n"
)
if likert_result:
prompt += f"## Value-for-Money at $149/mo\nMean score: {likert_result.get('mean_score', 'N/A')}/10\n\n"
if open_ended_result:
prompt += f"## Pricing Model Preferences\n{open_ended_result.get('summary', 'N/A')}\n\n"
prompt += (
"Produce a recommendation with:\n"
"1. Optimal price point and acceptable range\n"
"2. Per-segment pricing sensitivity\n"
"3. Suggested pricing tiers\n"
"4. Risks of pricing too high or too low\n"
"5. Next steps for validation"
)
resp = mavera.responses.create(
model="mavera-1",
input=[{"role": "user", "content": prompt}],
extra_body={"response_format": PRICING_REPORT_SCHEMA},
)
return json.loads(resp.output[0].content[0].text)
const PRICING_REPORT_SCHEMA = { type: "json_schema", json_schema: {
name: "pricing_report", strict: true,
schema: {
type: "object",
properties: {
executive_summary: { type: "string" },
optimal_price_point: { type: "number" },
acceptable_range_low: { type: "number" },
acceptable_range_high: { type: "number" },
recommended_price: { type: "number" },
recommended_pricing_model: { type: "string" },
segment_recommendations: {
type: "array",
items: {
type: "object",
properties: {
segment: { type: "string" },
optimal_price: { type: "number" },
willingness_to_pay: { type: "string" },
pricing_model_preference: { type: "string" },
},
required: ["segment", "optimal_price", "willingness_to_pay", "pricing_model_preference"],
},
},
tier_suggestions: {
type: "array",
items: {
type: "object",
properties: {
tier_name: { type: "string" },
price_point: { type: "number" },
target_segment: { type: "string" },
key_features: { type: "array", items: { type: "string" } },
},
required: ["tier_name", "price_point", "target_segment", "key_features"],
},
},
risks: { type: "array", items: { type: "string" } },
next_steps: { type: "array", items: { type: "string" } },
},
required: [
"executive_summary", "optimal_price_point", "acceptable_range_low",
"acceptable_range_high", "recommended_price", "recommended_pricing_model",
"segment_recommendations", "tier_suggestions", "risks", "next_steps",
],
},
}};
async function generatePricingReport(vwPoints, segments, fgResults) {
let likertResult = null, openEndedResult = null;
for (const r of fgResults.results || []) {
if (r.type === "LIKERT") likertResult = r;
if (r.type === "OPEN_ENDED") openEndedResult = r;
}
let prompt =
"You are a pricing strategist. Analyze this Van Westendorp pricing data " +
"and produce a pricing recommendation.\n\n" +
`## Product\n${PRODUCT_DESCRIPTION}\n\n` +
`## Van Westendorp Results\n${JSON.stringify(vwPoints, null, 2)}\n\n` +
`## Segment-Level Analysis\n${JSON.stringify(segments, null, 2)}\n\n`;
if (likertResult) prompt += `## Value-for-Money at $149/mo\nMean: ${likertResult.mean_score}/10\n\n`;
if (openEndedResult) prompt += `## Pricing Model Preferences\n${openEndedResult.summary}\n\n`;
prompt +=
"Produce a recommendation with optimal price, per-segment sensitivity, " +
"suggested tiers, risks, and next steps.";
const resp = await mavera.responses.create({
model: "mavera-1",
input: [{ role: "user", content: prompt }],
response_format: PRICING_REPORT_SCHEMA,
});
return JSON.parse(resp.output[0].content[0].text);
}
Running the Full Pipeline
def run_pricing_research():
print("=" * 60)
print("PRICING RESEARCH — VAN WESTENDORP")
print("=" * 60)
# Stage 1: Create personas
print("\n--- Stage 1: Creating Budget-Segment Personas ---")
persona_ids = create_budget_personas()
# Stage 2: Run focus group
print("\n--- Stage 2: Running Pricing Focus Group ---")
fg = run_pricing_focus_group(persona_ids)
fg_results = poll_focus_group(fg["id"])
# Stage 3: Calculate price sensitivity
print("\n--- Stage 3: Calculating Price Sensitivity ---")
slider_data = extract_slider_data(fg_results)
vw_points = calculate_vw_points(slider_data)
segments = segment_analysis(slider_data)
print(f"\nVan Westendorp Results:")
print(f" Too cheap (floor): ${vw_points['raw_medians']['too_cheap']}/mo")
print(f" Bargain: ${vw_points['raw_medians']['bargain']}/mo")
print(f" Expensive but OK: ${vw_points['raw_medians']['expensive_worth_it']}/mo")
print(f" Too expensive (ceil): ${vw_points['raw_medians']['too_expensive']}/mo")
print(f"\n Optimal Price Point: ${vw_points['optimal_price_point']}/mo")
print(f" Acceptable Range: ${vw_points['acceptable_range']['low']} – ${vw_points['acceptable_range']['high']}/mo")
# Stage 4: Generate report
print("\n--- Stage 4: Generating Pricing Report ---")
report = generate_pricing_report(vw_points, segments, fg_results)
print(f"\nRecommended price: ${report['recommended_price']}/mo")
print(f"Pricing model: {report['recommended_pricing_model']}")
print(f"\nTier suggestions:")
for tier in report.get("tier_suggestions", []):
print(f" {tier['tier_name']}: ${tier['price_point']}/mo → {tier['target_segment']}")
# Save outputs
output = {
"van_westendorp": vw_points,
"segments": segments,
"report": report,
}
with open("pricing_research.json", "w") as f:
json.dump(output, f, indent=2)
print("\n✓ Saved pricing_research.json")
return report
if __name__ == "__main__":
run_pricing_research()
import fs from "fs";
async function runPricingResearch() {
console.log("PRICING RESEARCH — VAN WESTENDORP");
// Stage 1
console.log("\n--- Stage 1: Creating Budget-Segment Personas ---");
const personaIds = await createBudgetPersonas();
// Stage 2
console.log("\n--- Stage 2: Running Pricing Focus Group ---");
const fg = await runPricingFocusGroup(personaIds);
const fgResults = await pollFocusGroup(fg.id);
// Stage 3
console.log("\n--- Stage 3: Calculating Price Sensitivity ---");
const sliderData = extractSliderData(fgResults);
const vwPoints = calculateVWPoints(sliderData);
const segments = segmentAnalysis(sliderData);
console.log(`\nOptimal Price Point: $${vwPoints.optimal_price_point}/mo`);
console.log(`Acceptable Range: $${vwPoints.acceptable_range.low} – $${vwPoints.acceptable_range.high}/mo`);
// Stage 4
console.log("\n--- Stage 4: Generating Pricing Report ---");
const report = await generatePricingReport(vwPoints, segments, fgResults);
console.log(`\nRecommended price: $${report.recommended_price}/mo`);
for (const tier of report.tier_suggestions || []) {
console.log(` ${tier.tier_name}: $${tier.price_point}/mo → ${tier.target_segment}`);
}
fs.writeFileSync("pricing_research.json", JSON.stringify({ van_westendorp: vwPoints, segments, report }, null, 2));
console.log("\n✓ Saved pricing_research.json");
return report;
}
runPricingResearch();
Example Output
{
"executive_summary": "Van Westendorp analysis across 4 budget segments reveals an optimal price point of $129/mo with an acceptable range of $79-$229/mo. Strong segment divergence: bootstrapped founders cap at $49/mo while enterprise CMOs find anything under $99/mo suspiciously cheap. Tiered pricing is the clear winner in model preference.",
"optimal_price_point": 129,
"acceptable_range_low": 79,
"acceptable_range_high": 229,
"recommended_price": 149,
"recommended_pricing_model": "Tiered plans with usage-based overage",
"segment_recommendations": [
{
"segment": "Bootstrapped Startup Founder",
"optimal_price": 39,
"willingness_to_pay": "Low — caps at $49/mo, highly price sensitive",
"pricing_model_preference": "Usage-based or freemium"
},
{
"segment": "Growth-Stage Marketing Lead",
"optimal_price": 129,
"willingness_to_pay": "Moderate — willing to pay $99-$199/mo with clear ROI",
"pricing_model_preference": "Tiered plans"
},
{
"segment": "Enterprise CMO",
"optimal_price": 349,
"willingness_to_pay": "High — price is secondary to capability and reliability",
"pricing_model_preference": "Annual contract with dedicated support"
}
],
"tier_suggestions": [
{
"tier_name": "Starter",
"price_point": 49,
"target_segment": "Bootstrapped startups",
"key_features": ["5 personas", "3 focus groups/mo", "Basic chat"]
},
{
"tier_name": "Growth",
"price_point": 149,
"target_segment": "Growth-stage companies",
"key_features": ["25 personas", "15 focus groups/mo", "Mave agent", "Video analysis"]
},
{
"tier_name": "Enterprise",
"price_point": 399,
"target_segment": "Mid-market and enterprise",
"key_features": ["Unlimited personas", "Unlimited focus groups", "Priority support", "SSO", "API access"]
}
],
"risks": [
"Pricing below $79/mo signals low quality to enterprise buyers",
"Pricing above $229/mo loses growth-stage segment entirely",
"Usage-based pricing creates unpredictable bills — monitor NPS"
],
"next_steps": [
"A/B test $129 vs $149 on pricing page with real traffic",
"Run a follow-up focus group testing tier feature bundles",
"Validate enterprise pricing with 5 real enterprise prospects"
]
}
Variations
Test price increases on existing customers
Test price increases on existing customers
Create personas representing your current customer base. Test the proposed increase against their current price anchor:
{
"question": (
"You currently pay $99/mo for this product. "
"At what price would you cancel your subscription?"
),
"type": "SLIDER",
"min_value": 99,
"max_value": 500,
"order": 1,
}
Feature-value analysis
Feature-value analysis
Add questions that price individual features to find which drive willingness-to-pay:
features = ["AI research agent", "Video analysis", "Focus groups (N=100)"]
for i, feature in enumerate(features):
questions.append({
"question": f"How much would you pay monthly for {feature} alone?",
"type": "SLIDER",
"min_value": 0, "max_value": 200,
"order": 7 + i,
})
Geographic pricing
Geographic pricing
Run the same study with region-specific personas (US, EU, APAC, LATAM) to set geographic pricing:
regions = {
"US": ["us_startup_id", "us_enterprise_id"],
"EU": ["eu_startup_id", "eu_enterprise_id"],
"APAC": ["apac_startup_id", "apac_enterprise_id"],
}
for region, ids in regions.items():
fg = run_pricing_focus_group(ids)
# Compare VW points across regions
Competitive price anchoring
Competitive price anchoring
Include competitor pricing in the context so personas anchor relative to known alternatives:
PRODUCT_DESCRIPTION += (
"\n\nFor reference, similar products are priced at:\n"
"- Competitor A: $99/mo (basic plan)\n"
"- Competitor B: $249/mo (all features)\n"
"- Competitor C: Usage-based, typically $150-$300/mo"
)
Credits Estimate
| Stage | Typical Cost | Notes |
|---|---|---|
| Create 4 personas | 0 | Persona creation is free |
| Focus Group (N=40, 6 questions) | 200–400 credits | 4 sliders + 1 likert + 1 open-ended |
| Pricing report (1 chat call) | 5–15 credits | Single structured output |
| Total | ~205–415 credits |
For a quick directional read, start with N=20 (5 per segment) at roughly half the cost. Scale to N=40+ for tier-worthy data you’d put in a pricing deck.
See Also
Focus Groups
Slider, Likert, and all 12 question types
Positioning Workshop
Test positioning alongside pricing
Market Entry Research
Research the market before pricing
Brand Perception Audit
Understand brand perception context for pricing
Persona Debate
Opposing personas evaluate pricing at different points
Credits & Budget
Track and manage credit usage