Scenario
Your store loses revenue to abandoned checkouts. You pull recent abandoned carts via the REST API, group by product type and cart value range (under 50,50–150,150+), create Mavera personas from checkout demographics, and run a focus group to surface reactions to abandonment triggers — shipping costs, payment options, trust signals, checkout complexity.Architecture
Code
import os, requests
from collections import defaultdict
from datetime import datetime, timedelta
STORE = os.environ["SHOPIFY_STORE"]
TOKEN = os.environ["SHOPIFY_ACCESS_TOKEN"]
MV = os.environ["MAVERA_API_KEY"]
SH_B = f"https://{STORE}.myshopify.com/admin/api/2024-10"
SH_H = {"X-Shopify-Access-Token": TOKEN}
MB, MH = "https://app.mavera.io/api/v1", {"Authorization": f"Bearer {MV}", "Content-Type": "application/json"}
since = (datetime.utcnow() - timedelta(days=30)).isoformat()
url = f"{SH_B}/checkouts.json?status=open&created_at_min={since}&limit=250"
checkouts = []
while url:
resp = requests.get(url, headers=SH_H); resp.raise_for_status()
checkouts.extend(resp.json().get("checkouts", []))
link = resp.headers.get("Link", "")
url = None
if 'rel="next"' in link:
for part in link.split(","):
if 'rel="next"' in part: url = part.split("<")[1].split(">")[0]
print(f"{len(checkouts)} abandoned checkouts")
groups = defaultdict(list)
for co in checkouts:
a = float(co.get("total_price", "0"))
tier = "under_50" if a < 50 else ("50_to_150" if a <= 150 else "over_150")
types = sorted(set(i.get("product_type", "General") for i in co.get("line_items", [])))[:2]
groups[f"{tier}|{','.join(types)}"].append(co)
pids, sums = [], []
for key, members in list(groups.items())[:5]:
tier, types = key.split("|", 1)
r = requests.post(f"{MB}/personas", json={"name": f"Cart Abandoner — {tier} — {types}", "description": f"Abandoned {tier.replace('_',' ')} cart with {types}. {len(members)} checkouts."}, headers=MH)
r.raise_for_status(); pids.append(r.json()["id"])
avg = sum(float(c["total_price"]) for c in members) / len(members)
sums.append(f"{key}: {len(members)} carts, avg ${avg:.0f}")
fg = requests.post(f"{MB}/focus-groups", json={
"name": "Shopify Cart Abandonment Analysis", "persona_ids": pids,
"questions": ["What would cause you to leave items in your cart?", "What would make you complete checkout?", "How important is free shipping?", "What trust signals do you need before entering payment info?"],
"context": "Investigating cart abandonment.\n\n" + "\n".join(f"- {s}" for s in sums),
}, headers=MH)
fg.raise_for_status(); result = fg.json()
print(f"Focus group: {result['id']}")
for r in result.get("responses", []):
print(f"\n[{r['persona_name']}] {r['question']}\n → {r['answer']}")
const STORE = process.env.SHOPIFY_STORE, TOKEN = process.env.SHOPIFY_ACCESS_TOKEN, MV = process.env.MAVERA_API_KEY;
const SH_B = `https://${STORE}.myshopify.com/admin/api/2024-10`;
const MB = "https://app.mavera.io/api/v1";
const MH = { Authorization: `Bearer ${MV}`, "Content-Type": "application/json" };
(async () => {
const since = new Date(Date.now() - 30 * 86400000).toISOString();
let url = `${SH_B}/checkouts.json?status=open&created_at_min=${since}&limit=250`;
const checkouts = [];
while (url) {
const resp = await fetch(url, { headers: { "X-Shopify-Access-Token": TOKEN } });
checkouts.push(...((await resp.json()).checkouts || []));
const link = resp.headers.get("Link") || "";
url = link.includes('rel="next"') ? link.match(/<([^>]+)>;\s*rel="next"/)?.[1] : null;
}
console.log(`${checkouts.length} abandoned checkouts`);
const groups = {};
for (const co of checkouts) {
const a = parseFloat(co.total_price || "0");
const tier = a < 50 ? "under_50" : a <= 150 ? "50_to_150" : "over_150";
const types = [...new Set((co.line_items || []).map(i => i.product_type || "General"))].sort().slice(0, 2);
(groups[`${tier}|${types.join(",")}`] ??= []).push(co);
}
const pids = [], sums = [];
for (const [key, members] of Object.entries(groups).slice(0, 5)) {
const [tier, types] = key.split("|");
const r = await fetch(`${MB}/personas`, { method: "POST", headers: MH, body: JSON.stringify({ name: `Cart Abandoner — ${tier} — ${types}`, description: `Abandoned ${tier.replace(/_/g, " ")} cart. ${members.length} checkouts.` }) });
pids.push((await r.json()).id);
const avg = members.reduce((s, c) => s + parseFloat(c.total_price), 0) / members.length;
sums.push(`${key}: ${members.length} carts, avg $${avg.toFixed(0)}`);
}
const fg = await fetch(`${MB}/focus-groups`, { method: "POST", headers: MH, body: JSON.stringify({
name: "Shopify Cart Abandonment Analysis", persona_ids: pids,
questions: ["What would cause you to leave items in your cart?", "What would make you complete checkout?", "How important is free shipping?", "What trust signals do you need?"],
context: "Investigating cart abandonment.\n\n" + sums.map(s => `- ${s}`).join("\n")
}) });
const result = await fg.json();
console.log(`Focus group: ${result.id}`);
for (const r of result.responses || []) console.log(`\n[${r.persona_name}] ${r.question}\n → ${r.answer}`);
})();
Example Output
{
"id": "fg_2b8e4c1a",
"responses": [
{
"persona_name": "Cart Abandoner — over_150 — Outerwear",
"question": "What would cause you to leave items in your cart?",
"answer": "At that price point, I need a clear return policy and real customer photos. If shipping pushes the total over my budget, I close the tab."
},
{
"persona_name": "Cart Abandoner — under_50 — Accessories",
"question": "How important is free shipping?",
"answer": "For a $30 item, paying $8 shipping feels wrong. I'd add another item to hit a free shipping threshold."
}
]
}
Error Handling
Checkout API requires Shopify Plus or approval
Checkout API requires Shopify Plus or approval
GET /checkouts.json for abandoned checkouts may require Shopify Plus or specific permissions. If you get 403, verify your app has read_checkouts scope. On non-Plus stores, use GET /orders.json?status=any and filter for financial_status: pending as a proxy.Focus group — too many personas
Focus group — too many personas
Mavera focus groups work best with 3–7 personas. If grouping produces more segments, merge similar tiers or run multiple focus groups per value tier.