> ## 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.

# Abandoned Cart → Focus Group

> Analyze Shopify abandoned checkouts with Mavera Focus Groups to surface abandonment reasons by segment

### 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

```mermaid theme={"dark"}
flowchart LR
A["GET /checkouts.json abandoned"] --> B["Group by cart value + product type"] --> C["POST /api/v1/personas"] --> D["POST /api/v1/focus-groups"] --> E["Abandonment-reason insights"]
```

### Code

<CodeGroup>
  ```python Python theme={"dark"}
  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']}")
  ```

  ```javascript JavaScript theme={"dark"}
  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}`);
  })();
  ```
</CodeGroup>

### Example Output

```json theme={"dark"}
{
  "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

<AccordionGroup>
  <Accordion title="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.
  </Accordion>

  <Accordion title="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.
  </Accordion>
</AccordionGroup>
