---
title: "Org actor-grain people analytics (who edits/views/logs in)."
method: GET
path: "/v1/orgs/{orgId}/analytics/people"
tags: ["dashboards"]
---

# Org actor-grain people analytics (who edits/views/logs in).

`GET /v1/orgs/{orgId}/analytics/people`

## Request

- Base URL: `http://localhost:8080`
- URL: `http://localhost:8080/v1/orgs/{orgId}/analytics/people`
- Auth: one of:
  - API key in cookie `dvt_session`
  - HTTP bearer

Who edits, views, and logs in — an actor-grain sibling to
`GET /v1/orgs/{orgId}/analytics/content` (dashboard-grain) (DVT-1217).
Admin or owner only (audit:read). The `data` object shape is frozen —
field names are a client contract.

`viewsSource` follows the same events-vs-derived rule as the `content`
endpoint (see its description); `viewers`/`matrix` are computed from
`dashboard_views` when `"events"`, else approximated from
`query_executions` dashboard-render rows.

`editsSummary` is a fixed org-wide baseline (current window vs. the
immediately-prior equal-length window) — it is never narrowed by any
filter. `actorType` narrows `editors`/`viewers`/`matrix`; `dashboardId`
narrows `viewers`/`matrix` only (editors and logins are not
dashboard-scoped concepts). `connectionId` is accepted for parity with the
sibling endpoints' query-string shape but is always ignored here —
connections have no bearing on "who."

`loginsRecordedSince` is an ALL-TIME signal (not window-scoped): the
earliest recorded login (successful or not) across this org's current
members, or `null` if none is on record.

## Query parameters

- `days` integer
- `connectionId` string, uuid
- `dashboardId` string, uuid
- `actorType` 'user' | 'api_key' | 'agent'

## Response `200`

Org people-analytics result.

- object
  - `data` OrgAnalyticsPeople, required: Actor-grain people analytics for a single organization over a rolling window (DVT-1217) — who edits, views, and logs in. All field names are a frozen client contract.
    - `rangeDays` integer, required: The requested window in calendar days (echo of the clamped `days` param).
    - `generatedAt` string, date-time, required: RFC3339 UTC timestamp when this result was assembled.
    - `viewsSource` 'events' | 'derived', required: Same events-vs-derived rule as `OrgAnalyticsContent.viewsSource`.
    - `editsSummary` object, required: Fixed org-wide baseline (never narrowed by any filter): the current window vs. the immediately-prior equal-length window.
      - `edits` integer, required: audit_logs create/update events in the current window.
      - `agentEdits` integer, required: Of edits, those with actor_type='agent'.
      - `prevEdits` integer, required: Same count for the immediately-prior equal-length window.
      - `prevAgentEdits` integer, required: Of prevEdits, those with actor_type='agent'.
    - `activeMembers` integer: DVT-1597: fixed org-wide baseline (never narrowed by any filter), same current-vs-prior posture as `editsSummary` — distinct HUMAN (actor_type='user') actors with any activity (audit_logs create/update, dashboard_views, or query_executions) in the current window. Optional; older clients ignore it.
    - `prevActiveMembers` integer: Same count for the immediately-prior equal-length window. Optional; older clients ignore it.
    - `activeDaily14` integer[]: DVT-1612: FIXED trailing 14-calendar-day daily distinct active-member counts (same source/filter definition as `activeMembers`), oldest -> newest, independent of `days`. Optional; older clients ignore it.
    - `editors` object[], required: Up to 20 editors, descending by edits. actorType narrows.
      - `actorId` string, required: Actor UUID.
      - `name` string, required: display_name, else email, else 'unknown'.
      - `actorType` string, required: 'user', 'api_key', or 'agent'.
      - `edits` integer, required: audit_logs create/update events in the window.
      - `lastEditAt` string, date-time, required: Most recent edit's timestamp in the window.
    - `viewers` object[], required: Up to 20 viewers, descending by views. actorType + dashboardId narrow. From dashboard_views when viewsSource='events', else distinct request_ids from query_executions.
      - `actorId` string, required: Actor UUID.
      - `name` string, required: display_name, else email, else 'unknown'.
      - `actorType` string, required: 'user', 'api_key', or 'agent'.
      - `views` integer, required: View (or render-derived) count in the window.
      - `lastViewedAt` string, date-time, required: Most recent view's timestamp in the window.
    - `matrix` object, required: Top ≤10 viewers × top ≤10 dashboards (by the same views source), only non-zero cells. actorType + dashboardId narrow (same as viewers).
      - `actors` object[], required: The row axis — up to 10 of the top viewers above.
        - `actorId` string, required
        - `name` string, required
      - `dashboards` object[], required: The column axis — up to 10 dashboards, by the same views source.
        - `dashboardId` string, required
        - `title` string, required: Dashboard title, or empty string when untitled.
      - `cells` object[], required: Non-zero (actor, dashboard) view counts only.
        - `actorId` string, required
        - `dashboardId` string, required
        - `views` integer, required
    - `logins` object[], required: Up to 20 members, descending by most recent activity (latest of lastLoginAt/lastViewAt), then logins, then views — recency-first so the LIMIT never truncates a currently-active member. Row set is members with successful logins OR org-scoped dashboard views in the window (FULL OUTER union, DVT-1411) — a long-lived-session member who never re-authenticates but keeps viewing still shows up here. Never narrowed by actorType/dashboardId/connectionId. login_history has no org dimension — logins are recorded at session mint, before any org context exists — so each user's `logins` count is their GLOBAL login count across every org they belong to, not an org-scoped count; only the user roster itself is restricted to this org's current members. `views`/`lastViewAt` come from `dashboard_views` filtered to actor_type='user' and are org-scoped to this workspace (unlike logins); they are never narrowed by any filter either.
      - `userId` string, required: User UUID.
      - `name` string, required: display_name, else email, else 'unknown'.
      - `logins` integer, required: Successful logins in the window.
      - `lastLoginAt` string, date-time, nullable, required: Most recent successful login's timestamp in the window; null when the member had views but no successful login in the window.
      - `views` integer, required: Org-scoped dashboard views in the window (dashboard_views, actor_type='user') — this workspace only, unlike logins.
      - `lastViewAt` string, date-time, nullable, required: Most recent org-scoped view in the window; null when none.
    - `loginsRecordedSince` string, date-time, nullable, required: ALL-TIME earliest login_history row (successful or not) across this org's current members; null if none is on record.
    - `viewsRecordedSince` string, date-time, nullable, required: ALL-TIME earliest dashboard_views row for this org; null if view telemetry has never recorded here (recording started ~2026-07-10, migration 0058).
    - `roster` object[]: One row per live org member (DVT-1498) — every member, not just a top-N leaderboard. Status tiers mirror the mockup's memberTiers(): inactive (last activity >60d ago, or never active), dormant (31-60d), power (<=30d AND events >= the top-decile cutoff, min 1), active (<=7d), else casual. `events` is views+edits+queries in the window, also the tiering total.
      - `userId` string, required: User UUID.
      - `name` string, required: display_name, else email, else 'unknown'.
      - `email` string, required
      - `role` string, required: Member's role name.
      - `joinedAt` string, date-time, required: organization_members.created_at.
      - `lastLoginAt` string, date-time, nullable: ALL-TIME most recent successful login_history row, or null if none on record.
      - `lastActiveAt` string, date-time, nullable: ALL-TIME most recent activity by this member across the same three org-scoped sources that feed `events` — audit_logs, dashboard_views, query_executions (the GREATEST of each source's max timestamp) — so a view/query-only member's status tier is never contradicted by a stale audit-only signal. Null if none on record. NOT the same source as `Member.lastActiveAt` on the admin Members page (audit_logs only, a different surface).
      - `status` 'inactive' | 'dormant' | 'power' | 'active' | 'casual', required
      - `views` integer, required: dashboard_views count in the window.
      - `edits` integer, required: audit_logs create/update count in the window.
      - `queries` integer, required: query_executions count in the window.
      - `events` integer, required: views+edits+queries in the window (the tiering total).
      - `methodMix` MethodMix, required: An edit-method / actor-class breakdown of audit_logs create/update events (DVT-1498, collapsed to two classes DVT-1522): `human` = actor_type='user'; `agent` = any non-user actor_type. Replaces a prior three-way ui/mcp/api split keyed off audit metadata (oauth_client_id vs api_key_name), which systematically mislabeled the dvt MCP server's real traffic — it authenticates via a customer API key, stamping api_key_name — as "API" rather than "MCP", since oauth_client_id is only ever stamped by an unused code path.
        - `human` integer, required
        - `agent` integer, required
      - `daily14` integer[], required: FIXED trailing 14-calendar-day sparkline (independent of `days`), oldest -> newest, combined views+edits+queries per day.
    - `cohorts` object[]: Monthly join-cohort retention (DVT-1498), capped to the 12 most- recently-joined cohorts.
      - `joinMonth` string, required: "YYYY-MM" the cohort joined.
      - `size` integer, required: Members who joined in joinMonth.
      - `activePct` number[], required: Fraction (0..1) of the cohort with any audit_logs activity in each calendar month since joining, index 0 = join month itself. Length is min(12, monthsElapsed+1) — truncated to elapsed months, not padded to a fixed size.
    - `weeklyRanks` object[]: The trailing 12 ISO (Monday-start) weeks' top-8-by-events bump-chart rows (DVT-1498), oldest -> newest, always exactly 12 entries regardless of `days` (FIXED window).
      - `weekStart` string, required: "YYYY-MM-DD", the week's Monday.
      - `top` object[], required: Up to 8 members, descending by events; empty when the org had no activity that week.
        - `userId` string, required: User UUID.
        - `name` string, required: display_name, else email, else 'unknown'.
        - `rank` integer, required: 1-8, 1 = most events that week.
        - `events` integer, required
  - `meta` Meta, required: Response metadata.
    - `requestId` string, required: Correlates with the X-Request-Id header.
    - `total` integer: Item count, present on collection responses.
    - `nextCursor` string, nullable: Opaque keyset-pagination cursor, present on paginated responses (e.g. GET /v1/dashboards) when more rows exist beyond this page. Pass it back as the `cursor` query param to fetch the next page; absent or null on the last page.
    - `roleResolveDegraded` boolean: Snowflake mode only (DVT-2260), on GET /v1/dashboards: present and true when the caller's live Snowflake role resolution failed or was unavailable — role-shared dashboards may be missing from this page. Omitted (not merely false) whenever resolution succeeded or the role-share arm does not apply (off snowflake mode, an org admin/owner, or off the ingress listener).

## Other responses

- `401`: Missing, invalid, or expired session.
- `403`: The caller's role does not permit this action.
- `404`: Resource does not exist.

---

[API](https://skmtc.dev/dvt/apis/dvt-rest-api.md) · [All operations](https://skmtc.dev/dvt/apis/dvt-rest-api/llms.txt) · [OpenAPI document](https://skmtc.dev/dvt/apis/dvt-rest-api/revisions/00016f504165?raw)
