> ## 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 Custom Attributes → Persona Source

> Create Mavera personas from BigCommerce customer attribute segments for data-grounded content generation

### Scenario

BigCommerce stores custom customer attributes — company type, purchase frequency, preferred categories — via customer attributes. You pull these, group customers into segments by shared attribute values, and create Mavera personas from each real-world segment so generated content reflects actual buyer profiles.

**Flow:** BigCommerce `GET /v3/customers?include=attributes` → Group by attribute values → Mavera `POST /api/v1/personas` per segment

### Architecture

```mermaid theme={"dark"}
flowchart LR
A["GET /v3/customers with attributes"] --> B["Group by custom attribute values"] --> C["POST /api/v1/personas per segment"]
```

### Code

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

  STORE = os.environ["BIGCOMMERCE_STORE_HASH"]
  BC_TOKEN = os.environ["BIGCOMMERCE_ACCESS_TOKEN"]
  MV = os.environ["MAVERA_API_KEY"]
  BC = f"https://api.bigcommerce.com/stores/{STORE}/v3"
  BC_HEADERS = {"X-Auth-Token": BC_TOKEN, "Content-Type": "application/json", "Accept": "application/json"}
  MH = {"Authorization": f"Bearer {MV}", "Content-Type": "application/json"}

  customers, page = [], 1
  while True:
      r = requests.get(f"{BC}/customers",
          headers=BC_HEADERS,
          params={"include": "attributes", "page": page, "limit": 50})
      if r.status_code == 429:
          time.sleep(2); continue
      r.raise_for_status()
      batch = r.json().get("data", [])
      customers.extend(batch)
      if len(batch) < 50: break
      page += 1
      time.sleep(0.2)

  SEGMENT_ATTR = "customer_type"
  segments = defaultdict(list)
  for cust in customers:
      for attr in cust.get("attributes", []):
          if attr.get("attribute_name") == SEGMENT_ATTR and attr.get("attribute_value"):
              segments[attr["attribute_value"]].append(cust)
              break

  personas = []
  for seg_name, members in segments.items():
      if len(members) < 3:
          continue
      companies = list({c.get("company", "") for c in members if c.get("company")})[:5]
      emails = [c.get("email", "") for c in members]
      domains = list({e.split("@")[-1] for e in emails if "@" in e and not any(
          d in e for d in ["gmail", "yahoo", "hotmail", "outlook"])})[:5]

      spend_attr = next((a["attribute_value"] for m in members
          for a in m.get("attributes", [])
          if a.get("attribute_name") == "avg_order_value"), "unknown")

      r = requests.post("https://app.mavera.io/api/v1/personas", headers=MH, json={
          "name": f"BigCommerce: {seg_name}",
          "description": (f"{seg_name} segment from BigCommerce. "
              f"N={len(members)}. Companies: {', '.join(companies[:3])}. "
              f"Domains: {', '.join(domains[:3])}. Avg order value: {spend_attr}."),
          "demographic": {"companies": companies, "segment_size": len(members)},
          "psychographic": {"buyer_type": seg_name.lower()},
      })
      r.raise_for_status()
      persona = r.json()
      personas.append({"segment": seg_name, "id": persona["id"], "n": len(members)})
      print(f"{seg_name}: {persona['id']} ({len(members)} customers)")
      time.sleep(0.3)

  print(f"\nCreated {len(personas)} personas from {len(customers)} customers")
  ```

  ```javascript JavaScript theme={"dark"}
  const STORE = process.env.BIGCOMMERCE_STORE_HASH;
  const BC_TOKEN = process.env.BIGCOMMERCE_ACCESS_TOKEN;
  const MV = process.env.MAVERA_API_KEY;
  const BC = `https://api.bigcommerce.com/stores/${STORE}/v3`;
  const bcHeaders = { "X-Auth-Token": BC_TOKEN, "Content-Type": "application/json", Accept: "application/json" };
  const MH = { Authorization: `Bearer ${MV}`, "Content-Type": "application/json" };

  const customers = [];
  let page = 1;
  while (true) {
    const res = await fetch(`${BC}/customers?include=attributes&page=${page}&limit=50`,
      { headers: bcHeaders });
    if (res.status === 429) { await new Promise(r => setTimeout(r, 2000)); continue; }
    const batch = (await res.json()).data || [];
    customers.push(...batch);
    if (batch.length < 50) break;
    page++;
    await new Promise(r => setTimeout(r, 200));
  }

  const SEGMENT_ATTR = "customer_type";
  const segments = {};
  for (const cust of customers) {
    const attr = (cust.attributes || []).find(a => a.attribute_name === SEGMENT_ATTR);
    if (attr?.attribute_value) {
      (segments[attr.attribute_value] ||= []).push(cust);
    }
  }

  const personas = [];
  for (const [segName, members] of Object.entries(segments)) {
    if (members.length < 3) continue;
    const companies = [...new Set(members.map(c => c.company).filter(Boolean))].slice(0, 5);
    const domains = [...new Set(members.map(c => c.email?.split("@")[1]).filter(d =>
      d && !["gmail.com","yahoo.com","hotmail.com","outlook.com"].includes(d)))].slice(0, 5);

    const res = await fetch("https://app.mavera.io/api/v1/personas", {
      method: "POST", headers: MH,
      body: JSON.stringify({
        name: `BigCommerce: ${segName}`,
        description: `${segName} segment. N=${members.length}. Companies: ${companies.slice(0,3).join(", ")}. Domains: ${domains.slice(0,3).join(", ")}.`,
        demographic: { companies, segment_size: members.length },
        psychographic: { buyer_type: segName.toLowerCase() },
      }),
    });
    if (!res.ok) throw new Error(`Mavera ${res.status}`);
    const persona = await res.json();
    personas.push({ segment: segName, id: persona.id, n: members.length });
    console.log(`${segName}: ${persona.id} (${members.length} customers)`);
    await new Promise(r => setTimeout(r, 300));
  }

  console.log(`\nCreated ${personas.length} personas from ${customers.length} customers`);
  ```
</CodeGroup>

### Example Output

```json theme={"dark"}
[
  { "segment": "Enterprise", "id": "per_ent_8k2m", "n": 45 },
  { "segment": "SMB", "id": "per_smb_3f9a", "n": 120 },
  { "segment": "Wholesale", "id": "per_ws_7b4c", "n": 28 },
  { "segment": "Marketplace Seller", "id": "per_mps_1d6e", "n": 67 }
]
```

### Error Handling

<AccordionGroup>
  <Accordion title="Custom attributes not returned">The `include=attributes` query param is required — without it the `attributes` array is omitted. Verify your API account has `store_v2_customers_read_only` scope.</Accordion>
  <Accordion title="Segment too small for useful personas">Segments with fewer than 3 members produce generic personas. The code skips these. Combine low-count segments into an "Other" bucket or use a broader grouping attribute.</Accordion>
</AccordionGroup>
