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

# Profile Traits → Custom Persona Source

> Pull Segment computed traits, cluster users by LTV and churn risk, and create Mavera Custom Personas enriched with CDP behavioral data

### Scenario

Segment's Profile API computes traits from raw events — lifetime value, purchase frequency, feature adoption score, engagement tier. These computed traits are richer than raw event properties because they aggregate behavior over time. You pull profile traits for a sample of users, identify trait clusters, and create Mavera Custom Personas enriched with CDP-computed behavioral data. This is the Advanced persona creation path — using real computed metrics instead of guesswork.

### Architecture

```mermaid theme={"dark"}
flowchart LR
A["Segment GET profiles with computed traits"] --> B["Cluster by trait combinations"] --> C["POST /api/v1/personas per cluster"] --> D["Trait-enriched personas"]
```

### Code

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

  SEG_TOKEN = os.environ["SEGMENT_TOKEN"]
  MV = os.environ["MAVERA_API_KEY"]
  MB = "https://app.mavera.io/api/v1"
  MH = {"Authorization": f"Bearer {MV}", "Content-Type": "application/json"}
  SH = {"Authorization": f"Bearer {SEG_TOKEN}", "Content-Type": "application/json"}

  SPACE_ID = os.environ.get("SEGMENT_SPACE_ID", "spa_xxxxx")

  r = requests.get(
      f"https://api.segmentapis.com/spaces/{SPACE_ID}/collections/users/profiles",
      headers=SH,
      params={"limit": 200, "include": "traits"},
  )
  r.raise_for_status()
  profiles_data = r.json()
  profiles = profiles_data.get("data", {}).get("profiles", [])

  if not profiles:
      profiles = [
          {"traits": {"ltv": 1200, "purchase_count": 8, "engagement_tier": "high", "churn_risk": 0.05, "plan": "pro", "industry": "SaaS"}},
          {"traits": {"ltv": 450, "purchase_count": 3, "engagement_tier": "medium", "churn_risk": 0.22, "plan": "starter", "industry": "E-commerce"}},
          {"traits": {"ltv": 85, "purchase_count": 1, "engagement_tier": "low", "churn_risk": 0.65, "plan": "free", "industry": "Agency"}},
      ] * 20

  print(f"Fetched {len(profiles)} profiles with traits\n")

  clusters = {"high_value": [], "growth": [], "at_risk": [], "new": []}
  for p in profiles:
      traits = p.get("traits", {})
      ltv = float(traits.get("ltv", traits.get("lifetime_value", 0)) or 0)
      purchases = int(traits.get("purchase_count", traits.get("order_count", 0)) or 0)
      churn_risk = float(traits.get("churn_risk", traits.get("churn_probability", 0.5)) or 0.5)
      tier = traits.get("engagement_tier", "unknown")

      if ltv > 500 and churn_risk < 0.15:
          clusters["high_value"].append(traits)
      elif ltv > 100 and purchases >= 2:
          clusters["growth"].append(traits)
      elif churn_risk > 0.4:
          clusters["at_risk"].append(traits)
      else:
          clusters["new"].append(traits)

  def summarize_cluster(users, label):
      if not users:
          return None
      avg_ltv = sum(float(u.get("ltv", 0) or 0) for u in users) / len(users)
      avg_purchases = sum(int(u.get("purchase_count", 0) or 0) for u in users) / len(users)
      avg_churn = sum(float(u.get("churn_risk", 0) or 0) for u in users) / len(users)

      industries = defaultdict(int)
      plans = defaultdict(int)
      for u in users:
          if u.get("industry"): industries[u["industry"]] += 1
          if u.get("plan"): plans[u["plan"]] += 1

      top_ind = sorted(industries, key=industries.get, reverse=True)[:3]
      top_plans = sorted(plans, key=plans.get, reverse=True)[:3]

      return {
          "label": label, "n": len(users),
          "avg_ltv": avg_ltv, "avg_purchases": avg_purchases, "avg_churn": avg_churn,
          "industries": top_ind, "plans": top_plans,
      }

  created = []
  for cluster_name, users in clusters.items():
      summary = summarize_cluster(users, cluster_name)
      if not summary or summary["n"] < 3:
          continue

      label = cluster_name.replace("_", " ").title()
      persona = requests.post(f"{MB}/personas", headers=MH, json={
          "name": f"Segment CDP: {label}",
          "description": (
              f"{label} segment from Segment computed traits. N={summary['n']}. "
              f"Avg LTV: ${summary['avg_ltv']:.0f}. Avg purchases: {summary['avg_purchases']:.1f}. "
              f"Avg churn risk: {summary['avg_churn']:.0%}. "
              f"Industries: {', '.join(summary['industries'])}. "
              f"Plans: {', '.join(summary['plans'])}."
          ),
          "demographic": {
              "industries": summary["industries"],
              "plans": summary["plans"],
          },
          "psychographic": {
              "cluster": cluster_name,
              "avg_ltv": summary["avg_ltv"],
              "avg_churn_risk": summary["avg_churn"],
              "engagement_tier": "high" if summary["avg_ltv"] > 500 else "medium" if summary["avg_ltv"] > 100 else "low",
          },
      }).json()

      created.append({"cluster": label, "id": persona["id"], "n": summary["n"]})
      print(f"  {label}: {persona['id']} (N={summary['n']}, LTV=${summary['avg_ltv']:.0f}, churn={summary['avg_churn']:.0%})")
      time.sleep(0.3)

  print(f"\nCreated {len(created)} CDP-enriched personas")
  ```

  ```javascript JavaScript theme={"dark"}
  const SEG_TOKEN = process.env.SEGMENT_TOKEN;
  const MV = process.env.MAVERA_API_KEY;
  const MB = "https://app.mavera.io/api/v1";
  const MH = { Authorization: `Bearer ${MV}`, "Content-Type": "application/json" };
  const SH = { Authorization: `Bearer ${SEG_TOKEN}`, "Content-Type": "application/json" };
  const SPACE_ID = process.env.SEGMENT_SPACE_ID || "spa_xxxxx";

  const profRes = await fetch(
    `https://api.segmentapis.com/spaces/${SPACE_ID}/collections/users/profiles?limit=200&include=traits`,
    { headers: SH }
  ).then((r) => r.json());

  let profiles = profRes.data?.profiles || [];
  if (!profiles.length) {
    const sample = [
      { traits: { ltv: 1200, purchase_count: 8, engagement_tier: "high", churn_risk: 0.05, plan: "pro", industry: "SaaS" } },
      { traits: { ltv: 450, purchase_count: 3, engagement_tier: "medium", churn_risk: 0.22, plan: "starter", industry: "E-commerce" } },
      { traits: { ltv: 85, purchase_count: 1, engagement_tier: "low", churn_risk: 0.65, plan: "free", industry: "Agency" } },
    ];
    profiles = Array(20).fill(null).flatMap(() => sample);
  }

  console.log(`Fetched ${profiles.length} profiles\n`);

  const clusters = { high_value: [], growth: [], at_risk: [], new: [] };
  for (const p of profiles) {
    const t = p.traits || {};
    const ltv = parseFloat(t.ltv || t.lifetime_value || 0);
    const purchases = parseInt(t.purchase_count || t.order_count || 0);
    const churn = parseFloat(t.churn_risk || t.churn_probability || 0.5);

    if (ltv > 500 && churn < 0.15) clusters.high_value.push(t);
    else if (ltv > 100 && purchases >= 2) clusters.growth.push(t);
    else if (churn > 0.4) clusters.at_risk.push(t);
    else clusters.new.push(t);
  }

  function summarize(users) {
    const avg = (key) => users.reduce((s, u) => s + parseFloat(u[key] || 0), 0) / (users.length || 1);
    const topN = (key) => {
      const c = {};
      users.forEach((u) => { if (u[key]) c[u[key]] = (c[u[key]] || 0) + 1; });
      return Object.keys(c).sort((a, b) => c[b] - c[a]).slice(0, 3);
    };
    return {
      n: users.length, avgLtv: avg("ltv"), avgPurchases: avg("purchase_count"),
      avgChurn: avg("churn_risk"), industries: topN("industry"), plans: topN("plan"),
    };
  }

  const created = [];
  for (const [clusterName, users] of Object.entries(clusters)) {
    if (users.length < 3) continue;
    const s = summarize(users);
    const label = clusterName.replace(/_/g, " ").replace(/\b\w/g, (c) => c.toUpperCase());

    const p = await fetch(`${MB}/personas`, { method: "POST", headers: MH,
      body: JSON.stringify({
        name: `Segment CDP: ${label}`,
        description: `${label} from Segment traits. N=${s.n}. LTV: $${s.avgLtv.toFixed(0)}. Purchases: ${s.avgPurchases.toFixed(1)}. Churn: ${(s.avgChurn * 100).toFixed(0)}%. Industries: ${s.industries.join(", ")}. Plans: ${s.plans.join(", ")}.`,
        demographic: { industries: s.industries, plans: s.plans },
        psychographic: { cluster: clusterName, avg_ltv: s.avgLtv, avg_churn_risk: s.avgChurn },
      }),
    }).then((r) => r.json());

    created.push({ cluster: label, id: p.id, n: s.n });
    console.log(`  ${label}: ${p.id} (N=${s.n}, LTV=$${s.avgLtv.toFixed(0)}, churn=${(s.avgChurn * 100).toFixed(0)}%)`);
    await new Promise((r) => setTimeout(r, 300));
  }

  console.log(`\nCreated ${created.length} CDP-enriched personas`);
  ```
</CodeGroup>

### Example Output

```json theme={"dark"}
{
  "profiles_fetched": 60,
  "personas": [
    { "cluster": "High Value", "id": "per_cdp_hv_1", "n": 20, "avg_ltv": 1200, "avg_churn": "5%" },
    { "cluster": "Growth", "id": "per_cdp_gr_2", "n": 20, "avg_ltv": 450, "avg_churn": "22%" },
    { "cluster": "At Risk", "id": "per_cdp_ar_3", "n": 20, "avg_ltv": 85, "avg_churn": "65%" }
  ]
}
```

### Error Handling

<AccordionGroup>
  <Accordion title="Profile API requires Engage">The Profiles API (`/spaces/{id}/collections/users/profiles`) requires Segment Engage. On free plans, use the Tracking API to send events and create personas from event patterns instead.</Accordion>
  <Accordion title="Computed traits vs. custom traits">Computed traits (like LTV, purchase count) are defined in Segment Engage → Computed Traits. If they don't exist, the profile traits will only include properties set via `identify()` calls.</Accordion>
  <Accordion title="Profile pagination">The Profiles API returns up to 200 profiles per page. Use the `cursor` parameter from the response for pagination. Each page counts as one API call.</Accordion>
</AccordionGroup>

***

## What's Next

<CardGroup cols={2}>
  <Card title="Segment Integration" icon="diagram-project" href="/integrations/segment">
    Back to Segment integration overview
  </Card>

  <Card title="Audience Segments → Focus Groups" icon="users-viewfinder" href="/integrations/segment/audience-focus-groups">
    Map audiences to focus group research
  </Card>

  <Card title="Real-Time Persona Triggers" icon="bolt" href="/integrations/segment/realtime-persona-triggers">
    Auto-trigger research from live events
  </Card>

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