Scenario
Your Klaviyo account tracks what customers buy. You want to test cross-sell hypotheses — if someone bought Product A, would they buy Product B? You pull purchase data and catalog information, then run a Mavera Focus Group with product-context questions using Likert scale and NPS format. The result is validated cross-sell opportunities before you build the flow.Architecture
Code
import os, requests, time
from collections import defaultdict
KL_KEY = os.environ["KLAVIYO_API_KEY"]
MV = os.environ["MAVERA_API_KEY"]
MB = "https://app.mavera.io/api/v1"
MH = {"Authorization": f"Bearer {MV}", "Content-Type": "application/json"}
KH = {
"Authorization": f"Klaviyo-API-Key {KL_KEY}",
"Content-Type": "application/json",
"revision": "2024-10-15",
}
r = requests.get("https://a.klaviyo.com/api/catalog-items", headers=KH, params={
"fields[catalog-item]": "title,description,url,price",
"page[size]": 50,
})
products = []
if r.status_code == 200:
products = r.json().get("data", [])
if not products:
products = [
{"id": "prod_001", "attributes": {"title": "Running Shoes Pro", "price": 129, "description": "High-performance running shoes for daily training"}},
{"id": "prod_002", "attributes": {"title": "Performance Socks 3-Pack", "price": 24, "description": "Moisture-wicking athletic socks"}},
{"id": "prod_003", "attributes": {"title": "GPS Running Watch", "price": 299, "description": "Multi-sport GPS watch with heart rate monitoring"}},
{"id": "prod_004", "attributes": {"title": "Hydration Belt", "price": 35, "description": "Hands-free running hydration system"}},
{"id": "prod_005", "attributes": {"title": "Recovery Foam Roller", "price": 45, "description": "Deep-tissue massage foam roller"}},
{"id": "prod_006", "attributes": {"title": "Training Plan (Digital)", "price": 19, "description": "12-week progressive running program"}},
]
PRODUCT_PAIRS = [
{"bought": "Running Shoes Pro", "suggest": "Performance Socks 3-Pack", "logic": "Complementary — socks protect the shoe investment"},
{"bought": "GPS Running Watch", "suggest": "Training Plan (Digital)", "logic": "Watch + plan = structured training"},
{"bought": "Running Shoes Pro", "suggest": "Recovery Foam Roller", "logic": "Runners who invest in shoes care about recovery"},
{"bought": "Hydration Belt", "suggest": "GPS Running Watch", "logic": "Hydration belt = long runs = watch needed"},
]
personas = []
for name, desc in [
("Casual Runner", "Runs 2-3x per week for fitness. Buys basics, price-conscious. Doesn't consider themselves 'a runner' — it's just exercise."),
("Dedicated Runner", "Runs 4-5x per week, tracks every metric. Invests in gear. Training for a half marathon. Reads running blogs."),
("New Runner", "Started running 2 months ago. Bought first pair of real running shoes. Overwhelmed by options. Wants guidance."),
]:
p = requests.post(f"{MB}/personas", headers=MH, json={"name": name, "description": desc}).json()
personas.append(p)
time.sleep(0.3)
product_catalog = "\n".join(
f"- {p.get('attributes', p).get('title', 'Unknown')}: ${p.get('attributes', p).get('price', 0)} — {p.get('attributes', p).get('description', '')}"
for p in products
)
pair_questions = []
for pair in PRODUCT_PAIRS:
pair_questions.append(
f"You recently bought '{pair['bought']}'. On a scale of 1-5 (1=No interest, 5=Would buy immediately), "
f"how likely are you to buy '{pair['suggest']}'? "
f"Explain your rating — what would make you more or less likely?"
)
pair_questions.append(
"On a scale of 0-10, how likely are you to recommend our store to a friend? "
"What's the #1 thing we'd need to do to move your score up by 2 points?"
)
fg = requests.post(f"{MB}/focus-groups", headers=MH, json={
"name": "Klaviyo: Product Affinity Cross-Sell Study",
"persona_ids": [p["id"] for p in personas],
"questions": pair_questions,
"context": f"""Product affinity study for an e-commerce running gear store.
PRODUCT CATALOG:
{product_catalog}
CROSS-SELL HYPOTHESES:
{chr(10).join(f"- {p['bought']} → {p['suggest']}: {p['logic']}" for p in PRODUCT_PAIRS)}
Each persona represents a different buyer segment. We want to validate which cross-sell pairs resonate with each segment before building automated flows.""",
"responses_per_persona": 2,
}).json()
for _ in range(30):
time.sleep(5)
data = requests.get(f"{MB}/focus-groups/{fg['id']}", headers=MH).json()
if data.get("status") == "completed":
break
print(f"Focus Group: {data.get('id')} — {data.get('status')}\n")
for resp in data.get("responses", []):
print(f"[{resp.get('persona_id','?')}] {resp.get('question','')[:80]}")
print(f" → {resp.get('answer','')[:300]}\n")
const KL_KEY = process.env.KLAVIYO_API_KEY;
const MV = process.env.MAVERA_API_KEY;
const MB = "https://app.mavera.io/api/v1";
const MH = { Authorization: `Bearer ${MV}`, "Content-Type": "application/json" };
const KH = { Authorization: `Klaviyo-API-Key ${KL_KEY}`, "Content-Type": "application/json", revision: "2024-10-15" };
let products = [];
const catRes = await fetch("https://a.klaviyo.com/api/catalog-items?fields[catalog-item]=title,description,price&page[size]=50", { headers: KH });
if (catRes.ok) products = (await catRes.json()).data || [];
if (!products.length) {
products = [
{ attributes: { title: "Running Shoes Pro", price: 129, description: "High-performance running shoes" } },
{ attributes: { title: "Performance Socks 3-Pack", price: 24, description: "Moisture-wicking socks" } },
{ attributes: { title: "GPS Running Watch", price: 299, description: "Multi-sport GPS watch" } },
{ attributes: { title: "Hydration Belt", price: 35, description: "Hands-free hydration" } },
{ attributes: { title: "Recovery Foam Roller", price: 45, description: "Deep-tissue roller" } },
{ attributes: { title: "Training Plan (Digital)", price: 19, description: "12-week running program" } },
];
}
const PAIRS = [
{ bought: "Running Shoes Pro", suggest: "Performance Socks 3-Pack" },
{ bought: "GPS Running Watch", suggest: "Training Plan (Digital)" },
{ bought: "Running Shoes Pro", suggest: "Recovery Foam Roller" },
{ bought: "Hydration Belt", suggest: "GPS Running Watch" },
];
const personas = [];
for (const [name, desc] of [
["Casual Runner", "Runs 2-3x/week for fitness. Price-conscious. Just exercise."],
["Dedicated Runner", "4-5x/week, tracks metrics, training for half marathon."],
["New Runner", "Started 2 months ago. First real shoes. Overwhelmed."],
]) {
const p = await fetch(`${MB}/personas`, { method: "POST", headers: MH,
body: JSON.stringify({ name, description: desc }) }).then((r) => r.json());
personas.push(p);
await new Promise((r) => setTimeout(r, 300));
}
const catalog = products.map((p) => `- ${p.attributes.title}: $${p.attributes.price} — ${p.attributes.description}`).join("\n");
const questions = PAIRS.map((p) =>
`You bought '${p.bought}'. Rate 1-5 (1=No interest, 5=Buy immediately) likelihood to buy '${p.suggest}'. Explain your rating.`
);
questions.push("NPS 0-10: recommend our store to a friend? What moves your score up 2 points?");
const fg = await fetch(`${MB}/focus-groups`, { method: "POST", headers: MH,
body: JSON.stringify({
name: "Klaviyo: Product Affinity Study",
persona_ids: personas.map((p) => p.id),
questions,
context: `Running gear store cross-sell study.\n\nCatalog:\n${catalog}\n\nValidating cross-sell pairs before building flows.`,
responses_per_persona: 2,
}),
}).then((r) => r.json());
let data;
for (let i = 0; i < 30; i++) {
await new Promise((r) => setTimeout(r, 5000));
data = await fetch(`${MB}/focus-groups/${fg.id}`, { headers: MH }).then((r) => r.json());
if (data.status === "completed") break;
}
console.log(`Focus Group: ${data.id} — ${data.status}\n`);
for (const resp of data.responses || []) {
console.log(`[${resp.persona_id}] ${(resp.question || "").slice(0, 80)}`);
console.log(` → ${(resp.answer || "").slice(0, 300)}\n`);
}
Example Output
Focus Group: fg_kl_affinity_8m3n — completed
[Casual Runner] Shoes → Socks (1-5 scale)
→ 4/5. Actually yeah — I just spent $129 on shoes, and my old
cotton socks are going to ruin them. At $24 for 3 pairs, it's
a no-brainer add-on. BUT only if you show me this within 2
days of my shoe purchase. After a week, I've already grabbed
socks at Target.
[Dedicated Runner] Watch → Training Plan
→ 5/5. I bought the watch specifically to follow structured
training. A $19 plan that tells my watch what workout to do
today? That's the missing piece. This should be offered at
checkout, not after.
[New Runner] Shoes → Foam Roller
→ 2/5. I don't know what a foam roller is for. I'm too new to
think about recovery — I'm still trying to run a mile without
stopping. Sell me the roller in month 3, not month 1.
[Casual Runner] NPS
→ 7/10. Good products, fair prices. To get me to 9: show me a
"starter bundle" with shoes + socks + a beginner guide at 15%
off. I hate shopping piece by piece.
Error Handling
Catalog API availability
Catalog API availability
The Catalog Items API (
/api/catalog-items) requires catalog sync to be configured in Klaviyo. If not set up, the endpoint returns empty. The code includes fallback sample data.Purchase event data
Purchase event data
For production cross-sell analysis, query Klaviyo’s Events API filtered by
Placed Order events to find actual co-purchase patterns before hypothesizing pairs.Likert scale interpretation
Likert scale interpretation
Focus Group responses include both the numeric rating and explanation. Parse the numeric value (e.g. “4/5”) and the qualitative reasoning separately for analysis.
What’s Next
Klaviyo Integration
Back to Klaviyo integration overview
Segment Overlap Analysis
Simplified segmentation recommendations
SMS vs. Email Creative Testing
Channel preference insights
Focus Groups API
Full reference for POST /api/v1/focus-groups