Persons & cohortsGET /api/v1/analytics/personsGET /api/v1/analytics/persons/attribute-stats

List behavioral person profiles, filterable by customer-defined attributes, and get cohort distributions - for every declared attribute, the top value buckets, the long-tail count, and how many persons have no value set.

List persons

curl "https://www.vevee.org/api/v1/analytics/persons?attr.plan=pro&cols=plan,goal&limit=50" \
  -H "Authorization: Bearer sk_live_YOUR_KEY"

Parameters

NameTypeDescription
attr.<key>optionalstringEquality filter on a declared attribute, e.g. attr.plan=pro. Repeat for multiple attributes (AND'd together). Max 5 filters; extras are dropped.
colsoptionalstringComma-separated attribute keys to include in each person's attributes object. Max 20; invalid keys are silently skipped. Omit to leave attributes off the response entirely.
cursoroptionalstringOpaque pagination token from a previous response's nextCursor. Treat it as an opaque string - do not parse or construct it.
limitoptionalnumberPage size, 1 to 100. Defaults to 50.
{
  "ok": true,
  "data": {
    "persons": [
      {
        "personId": "psn_7f3a2c",
        "distinctId": "user_abc123",
        "isIdentified": true,
        "firstSeenAt": "2026-05-14T08:02:11.000Z",
        "lastSeenAt": "2026-08-01T09:41:03.000Z",
        "properties": { "email": "sam@example.com", "plan": "pro" },
        "attributes": { "plan": "pro", "goal": "lesson_planning" }
      },
      {
        "personId": "psn_1b9d40",
        "distinctId": "user_def456",
        "isIdentified": true,
        "firstSeenAt": "2026-06-02T14:20:44.000Z",
        "lastSeenAt": "2026-07-30T17:12:56.000Z",
        "properties": { "email": "jo@example.com" },
        "attributes": { "plan": "pro", "goal": null }
      }
    ],
    "nextCursor": "2026-07-30T17:12:56.000Z|psn_1b9d40"
  }
}

attributes is only present on each person when cols is passed. nextCursor is null on the last page. It is a composite lastSeenAt|personId string - always treat it as opaque and pass it back verbatim as cursor.

!
Excluded persons never appear here. Persons who opted out of AI personalization (or opted out entirely) or are pending deletion are silently absent from the list - not marked, not counted, just missing.

Attribute stats

Cohort distributions (“personas”): for every non-archived attribute declared on the app, the top value buckets by count, a long-tail otherCount, and how many persons in the cohort have no value set for it.

curl "https://www.vevee.org/api/v1/analytics/persons/attribute-stats?attr.plan=pro" \
  -H "Authorization: Bearer sk_live_YOUR_KEY"

Accepts the same attr.<key> filters as the list endpoint (max 5), used to narrow the cohort before computing distributions - omit them to see the whole app.

{
  "ok": true,
  "data": {
    "totalPersons": 219,
    "stats": [
      {
        "key": "goal",
        "displayName": "Goal",
        "type": "single_choice",
        "setCount": 188,
        "nullCount": 31,
        "buckets": [
          { "value": "lesson_planning", "count": 96 },
          { "value": "grading", "count": 54 },
          { "value": "content_creation", "count": 38 }
        ],
        "otherCount": 0
      },
      {
        "key": "persona",
        "displayName": "Persona",
        "type": "single_choice",
        "setCount": 219,
        "nullCount": 0,
        "buckets": [
          { "value": "teacher", "count": 171 },
          { "value": "student", "count": 48 }
        ],
        "otherCount": 0
      }
    ]
  }
}
i
Attribute stats count everyone - unlike the person list, opted-out and pending-deletion persons are still included in totalPersons and the buckets. This is an aggregate, not a per-person view, so the same rule that applies to overview and subscriber stats applies here too.

Errors

  • invalid_key (401) - bad or revoked API key.
  • requires_secret_key (403) - a pk_* key was used.
i
Related: the MCP server exposes the same two operations as list_persons and get_attribute_stats - see MCP server.