Skip to main content

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.

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")

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

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.
For production cross-sell analysis, query Klaviyo’s Events API filtered by Placed Order events to find actual co-purchase patterns before hypothesizing pairs.
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