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

# Customer Cohort → Persona Pipeline

> Segment Shopify customers by purchase history into cohorts and create Mavera personas for each tier

### Scenario

Your Shopify store has thousands of customers with varying purchase histories. You pull customer records with order counts and total spend via GraphQL, segment them into cohorts (new buyers with 1 order, returning buyers with 2–5 orders, VIP buyers with 6+ orders or \$500+ spend), and create a Mavera persona for each cohort.

### Architecture

```mermaid theme={"dark"}
flowchart LR
A["GraphQL: customers"] --> B["Segment: new / returning / VIP"] --> C["POST /api/v1/personas per cohort"] --> D["Cohort-calibrated persona library"]
```

### Code

<CodeGroup>
  ```python Python theme={"dark"}
  import os, requests, time

  STORE = os.environ["SHOPIFY_STORE"]
  TOKEN = os.environ["SHOPIFY_ACCESS_TOKEN"]
  MV = os.environ["MAVERA_API_KEY"]
  SH = f"https://{STORE}.myshopify.com/admin/api/2024-10/graphql.json"
  SH_H = {"X-Shopify-Access-Token": TOKEN, "Content-Type": "application/json"}
  MB = "https://app.mavera.io/api/v1"
  MH = {"Authorization": f"Bearer {MV}", "Content-Type": "application/json"}

  QUERY = """{
    customers(first: 100, sortKey: UPDATED_AT, reverse: true) {
      edges { node {
        id displayName email ordersCount
        amountSpent { amount currencyCode }
        defaultAddress { country provinceCode }
      }}
      pageInfo { hasNextPage endCursor }
    }
  }"""

  def fetch_customers():
      customers, cursor = [], None
      while True:
          q = QUERY if not cursor else QUERY.replace("first: 100", f'first: 100, after: "{cursor}"')
          resp = requests.post(SH, json={"query": q}, headers=SH_H)
          resp.raise_for_status()
          data = resp.json()["data"]["customers"]
          customers.extend(e["node"] for e in data["edges"])
          if not data["pageInfo"]["hasNextPage"]:
              break
          cursor = data["edges"][-1]["node"]["id"]
          time.sleep(0.5)
      return customers

  def segment(customers):
      cohorts = {"new": [], "returning": [], "vip": []}
      for c in customers:
          spend = float(c["amountSpent"]["amount"])
          if c["ordersCount"] >= 6 or spend >= 500:
              cohorts["vip"].append(c)
          elif c["ordersCount"] >= 2:
              cohorts["returning"].append(c)
          else:
              cohorts["new"].append(c)
      return cohorts

  customers = fetch_customers()
  labels = {"new": "New Buyer", "returning": "Returning Buyer", "vip": "VIP Customer"}
  for name, members in segment(customers).items():
      if not members:
          continue
      avg_orders = sum(m["ordersCount"] for m in members) / len(members)
      avg_spend = sum(float(m["amountSpent"]["amount"]) for m in members) / len(members)
      resp = requests.post(f"{MB}/personas", json={
          "name": f"Shopify {labels[name]}",
          "description": f"{labels[name]}: {len(members)} customers, avg {avg_orders:.1f} orders, avg ${avg_spend:.0f} spend.",
          "demographic": {"custom_attributes": {"cohort": name, "avg_order_count": round(avg_orders, 1)}},
      }, headers=MH)
      resp.raise_for_status()
      p = resp.json()
      print(f"Created '{p['name']}' (id={p['id']}) — {len(members)} customers")
  ```

  ```javascript JavaScript theme={"dark"}
  const STORE = process.env.SHOPIFY_STORE, TOKEN = process.env.SHOPIFY_ACCESS_TOKEN, MV = process.env.MAVERA_API_KEY;
  const SH = `https://${STORE}.myshopify.com/admin/api/2024-10/graphql.json`;
  const MB = "https://app.mavera.io/api/v1";
  const QUERY = `{ customers(first:100, sortKey:UPDATED_AT, reverse:true) { edges { node { id displayName ordersCount amountSpent { amount } defaultAddress { country } } } pageInfo { hasNextPage endCursor } } }`;

  async function fetchCustomers() {
    const customers = []; let cursor = null;
    while (true) {
      const q = cursor ? QUERY.replace("first:100", `first:100, after:"${cursor}"`) : QUERY;
      const resp = await fetch(SH, { method: "POST", headers: { "X-Shopify-Access-Token": TOKEN, "Content-Type": "application/json" }, body: JSON.stringify({ query: q }) });
      const data = (await resp.json()).data.customers;
      for (const e of data.edges) customers.push(e.node);
      if (!data.pageInfo.hasNextPage) break;
      cursor = data.edges.at(-1).node.id;
      await new Promise(r => setTimeout(r, 500));
    }
    return customers;
  }
  function segment(customers) {
    const c = { new: [], returning: [], vip: [] };
    for (const x of customers) {
      const spend = parseFloat(x.amountSpent.amount);
      if (x.ordersCount >= 6 || spend >= 500) c.vip.push(x);
      else if (x.ordersCount >= 2) c.returning.push(x);
      else c.new.push(x);
    }
    return c;
  }
  (async () => {
    const customers = await fetchCustomers();
    const labels = { new: "New Buyer", returning: "Returning Buyer", vip: "VIP Customer" };
    for (const [name, members] of Object.entries(segment(customers))) {
      if (!members.length) continue;
      const avgOrd = members.reduce((s, m) => s + m.ordersCount, 0) / members.length;
      const avgSpd = members.reduce((s, m) => s + parseFloat(m.amountSpent.amount), 0) / members.length;
      const resp = await fetch(`${MB}/personas`, { method: "POST",
        headers: { Authorization: `Bearer ${MV}`, "Content-Type": "application/json" },
        body: JSON.stringify({ name: `Shopify ${labels[name]}`, description: `${labels[name]}: ${members.length} customers, avg ${avgOrd.toFixed(1)} orders, avg $${avgSpd.toFixed(0)} spend.`, demographic: { custom_attributes: { cohort: name, avg_order_count: +avgOrd.toFixed(1) } } })
      });
      const p = await resp.json();
      console.log(`Created '${p.name}' (id=${p.id}) — ${members.length} customers`);
    }
  })();
  ```
</CodeGroup>

### Example Output

```json theme={"dark"}
{
  "id": "persona_8f3a1b2c",
  "name": "Shopify VIP Customer",
  "description": "VIP Customer: 47 customers, avg 9.3 orders, avg $812 spend.",
  "demographic": { "custom_attributes": { "cohort": "vip", "avg_order_count": 9.3 } },
  "created_at": "2025-03-17T14:22:08Z"
}
```

### Error Handling

<AccordionGroup>
  <Accordion title="Shopify GraphQL throttled (THROTTLED)">
    Shopify returns a `THROTTLED` error in `extensions.cost` when you exceed your point budget. Read `throttleStatus.currentlyAvailable` from each response. When available points fall below query cost, sleep for `(cost - available) / restore_rate` seconds. Standard stores restore at 50 pts/sec; Plus at 500 pts/sec.
  </Accordion>

  <Accordion title="Mavera 422 — duplicate persona name">
    If a persona with the same name exists, the API returns `422`. Fetch existing personas with `GET /api/v1/personas?name=Shopify+VIP+Customer` first, and use `PATCH /api/v1/personas/{id}` to update instead.
  </Accordion>
</AccordionGroup>
