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

> Cluster Customer.io customer attributes by plan, industry, and usage tier to create Mavera Custom Personas for targeted content and focus groups

### Scenario

Customer.io stores rich customer attributes — plan type, industry, usage frequency, signup source, feature flags. These attributes define natural audience clusters, but they live as flat key-value pairs with no synthesis. This job pulls customer attributes via the App API, clusters them by plan tier and industry, then creates Mavera Custom Personas for each segment. Every focus group and content generation can now target the exact mix of attributes that defines each customer type.

**Flow:** Customer.io App API → Customer attributes → Cluster by plan/industry/usage → Mavera `POST /api/v1/personas` → Attribute-driven persona library

### Architecture

```mermaid theme={"dark"}
flowchart LR
A["GET /v1/customers by attribute"] --> B["Group by plan × industry × usage"] --> C["Extract traits per cluster"] --> D["POST /api/v1/personas"] --> E["Segment-mapped persona library"]
```

### Code

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

  CIO_APP = os.environ["CIO_APP_KEY"]
  MV = os.environ["MAVERA_API_KEY"]
  APP_BASE = "https://api.customer.io/v1"
  MB = "https://app.mavera.io/api/v1"
  APP_H = {"Authorization": f"Bearer {CIO_APP}"}
  MV_H = {"Authorization": f"Bearer {MV}", "Content-Type": "application/json"}

  SEGMENT_IDS = [1, 2, 3, 4, 5]

  # 1. Pull customers from key segments
  all_customers = []
  for seg_id in SEGMENT_IDS:
      r = requests.get(f"{APP_BASE}/segments/{seg_id}/membership",
          headers=APP_H, params={"limit": 200})
      if r.status_code == 429:
          time.sleep(1)
          r = requests.get(f"{APP_BASE}/segments/{seg_id}/membership",
              headers=APP_H, params={"limit": 200})
      r.raise_for_status()
      ids = r.json().get("ids", [])

      for cid in ids[:50]:
          cr = requests.get(f"{APP_BASE}/customers/{cid}/attributes",
              headers=APP_H)
          if cr.status_code == 429:
              time.sleep(1)
              cr = requests.get(f"{APP_BASE}/customers/{cid}/attributes",
                  headers=APP_H)
          if cr.ok:
              attrs = cr.json().get("customer", {})
              all_customers.append(attrs)
          time.sleep(0.12)

  # 2. Cluster by plan × industry × usage
  clusters = defaultdict(list)
  for cust in all_customers:
      plan = cust.get("plan_type", "free").lower()
      industry = cust.get("industry", "unknown").lower()
      usage = cust.get("usage_tier", "low").lower()
      key = f"{plan}|{industry}|{usage}"
      clusters[key].append(cust)

  # 3. Create personas for significant clusters (5+ members)
  personas = []
  for key, members in sorted(clusters.items(), key=lambda x: -len(x[1])):
      if len(members) < 5:
          continue

      plan, industry, usage = key.split("|")
      roles = list({m.get("role", "") for m in members if m.get("role")})[:5]
      sources = list({m.get("signup_source", "") for m in members if m.get("signup_source")})[:3]
      features = list({f for m in members for f in (m.get("active_features") or "").split(",") if f.strip()})[:5]

      r = requests.post(f"{MB}/personas", headers=MV_H, json={
          "name": f"CIO: {plan.title()} / {industry.title()} / {usage.title()} Usage",
          "description": (
              f"Customer.io segment. Plan: {plan}. Industry: {industry}. "
              f"Usage tier: {usage}. N={len(members)}. "
              f"Common roles: {', '.join(roles[:3])}. "
              f"Signup sources: {', '.join(sources)}. "
              f"Active features: {', '.join(features[:3])}."
          ),
          "demographic": {
              "job_titles": roles,
              "industries": [industry],
          },
          "psychographic": {
              "plan_tier": plan,
              "usage_level": usage,
              "feature_adoption": features[:3],
          },
      })
      r.raise_for_status()
      personas.append({
          "cluster": key,
          "id": r.json()["id"],
          "n": len(members),
      })
      print(f"{plan.title()} / {industry.title()} / {usage.title()}: "
            f"{r.json()['id']} ({len(members)} customers)")
      time.sleep(0.3)

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

  ```javascript JavaScript theme={"dark"}
  const CIO_APP = process.env.CIO_APP_KEY;
  const MV = process.env.MAVERA_API_KEY;
  const APP_BASE = "https://api.customer.io/v1";
  const MB = "https://app.mavera.io/api/v1";
  const appH = { Authorization: `Bearer ${CIO_APP}` };
  const mvH = { Authorization: `Bearer ${MV}`, "Content-Type": "application/json" };

  const SEGMENT_IDS = [1, 2, 3, 4, 5];

  // 1. Pull customers from segments
  const allCustomers = [];
  for (const segId of SEGMENT_IDS) {
    let res = await fetch(`${APP_BASE}/segments/${segId}/membership?limit=200`,
      { headers: appH });
    if (res.status === 429) {
      await new Promise(r => setTimeout(r, 1000));
      res = await fetch(`${APP_BASE}/segments/${segId}/membership?limit=200`,
        { headers: appH });
    }
    const ids = (await res.json()).ids || [];

    for (const cid of ids.slice(0, 50)) {
      let cr = await fetch(`${APP_BASE}/customers/${cid}/attributes`,
        { headers: appH });
      if (cr.status === 429) {
        await new Promise(r => setTimeout(r, 1000));
        cr = await fetch(`${APP_BASE}/customers/${cid}/attributes`,
          { headers: appH });
      }
      if (cr.ok) allCustomers.push((await cr.json()).customer || {});
      await new Promise(r => setTimeout(r, 120));
    }
  }

  // 2. Cluster
  const clusters = {};
  for (const cust of allCustomers) {
    const plan = (cust.plan_type || "free").toLowerCase();
    const industry = (cust.industry || "unknown").toLowerCase();
    const usage = (cust.usage_tier || "low").toLowerCase();
    const key = `${plan}|${industry}|${usage}`;
    (clusters[key] ??= []).push(cust);
  }

  // 3. Create personas for clusters with 5+ members
  const personas = [];
  const sorted = Object.entries(clusters).sort(([, a], [, b]) => b.length - a.length);

  for (const [key, members] of sorted) {
    if (members.length < 5) continue;
    const [plan, industry, usage] = key.split("|");
    const roles = [...new Set(members.map(m => m.role).filter(Boolean))].slice(0, 5);
    const sources = [...new Set(members.map(m => m.signup_source).filter(Boolean))].slice(0, 3);
    const features = [...new Set(
      members.flatMap(m => (m.active_features || "").split(",").map(f => f.trim()).filter(Boolean))
    )].slice(0, 5);

    const cap = (s) => s.charAt(0).toUpperCase() + s.slice(1);
    const res = await fetch(`${MB}/personas`, {
      method: "POST", headers: mvH,
      body: JSON.stringify({
        name: `CIO: ${cap(plan)} / ${cap(industry)} / ${cap(usage)} Usage`,
        description: `Customer.io segment. Plan: ${plan}. Industry: ${industry}. Usage: ${usage}. N=${members.length}. Roles: ${roles.slice(0, 3).join(", ")}. Sources: ${sources.join(", ")}. Features: ${features.slice(0, 3).join(", ")}.`,
        demographic: { job_titles: roles, industries: [industry] },
        psychographic: { plan_tier: plan, usage_level: usage, feature_adoption: features.slice(0, 3) },
      }),
    });
    const persona = await res.json();
    personas.push({ cluster: key, id: persona.id, n: members.length });
    console.log(`${cap(plan)} / ${cap(industry)} / ${cap(usage)}: ${persona.id} (${members.length} customers)`);
    await new Promise(r => setTimeout(r, 300));
  }

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

### Example Output

```json theme={"dark"}
[
  { "cluster": "pro|saas|high",    "id": "per_cio_a1b2", "n": 87 },
  { "cluster": "pro|ecommerce|medium", "id": "per_cio_c3d4", "n": 42 },
  { "cluster": "enterprise|fintech|high", "id": "per_cio_e5f6", "n": 31 },
  { "cluster": "free|saas|low",    "id": "per_cio_g7h8", "n": 28 },
  { "cluster": "starter|healthcare|medium", "id": "per_cio_i9j0", "n": 15 }
]
```

```text theme={"dark"}
Pro / Saas / High: per_cio_a1b2 (87 customers)
Pro / Ecommerce / Medium: per_cio_c3d4 (42 customers)
Enterprise / Fintech / High: per_cio_e5f6 (31 customers)
Free / Saas / Low: per_cio_g7h8 (28 customers)
Starter / Healthcare / Medium: per_cio_i9j0 (15 customers)

Created 5 personas from 203 customers
```

### Error Handling

<AccordionGroup>
  <Accordion title="App API 10 req/sec limit">Individual customer attribute lookups burn rate fast. The code throttles to 120ms between requests. For 1,000+ customers, use Customer.io's data export or segment-based approaches instead of per-customer calls.</Accordion>
  <Accordion title="Missing attributes">Not all customers have `plan_type`, `industry`, or `usage_tier`. Defaults to `free`/`unknown`/`low`. Ensure these attributes are set via Track API `identify` calls before running this job.</Accordion>
  <Accordion title="Segment membership pagination">The `/segments/{id}/membership` endpoint returns up to 200 IDs per page. For larger segments, paginate with the `next` cursor.</Accordion>
  <Accordion title="Persona duplication">Re-running creates duplicates. Check with `GET /api/v1/personas?search=CIO:` before creating, or `DELETE` old versions first.</Accordion>
</AccordionGroup>

***

## What's Next

<CardGroup cols={2}>
  <Card title="Customer.io Integration" icon="message" href="/integrations/customer-io">
    Back to Customer.io integration overview
  </Card>

  <Card title="Campaign Messaging Strategy" icon="chart-line" href="/integrations/customer-io/campaign-messaging-strategy">
    Analyze campaign metrics for winning patterns
  </Card>

  <Card title="Webhook → Mave Trigger" icon="bolt" href="/integrations/customer-io/webhook-mave-trigger">
    Automated retention research on churn signals
  </Card>

  <Card title="Personas API" icon="user" href="/api-reference/personas">
    Full reference for POST /api/v1/personas
  </Card>
</CardGroup>
