---
title: "Org automation analytics (edit-method attribution, channel flows, key/agent activity, export delivery health)."
method: GET
path: "/v1/orgs/{orgId}/analytics/automation"
tags: ["dashboards"]
---

# Org automation analytics (edit-method attribution, channel flows, key/agent activity, export delivery health).

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

## Request

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

How content gets edited (DVT-1501): the human/agent method split
over time, channel -> action -> folder edit flows, per-API-key
activity, an agent roll-call, and export-schedule delivery health.
Admin or owner only (audit:read). The `data` object shape is frozen
— field names are a client contract.

Edit-method attribution (`methodWeekly`/`methodWindow`/
`methodPrevWindow`) uses the exact same audit_logs CASE semantics as
`GET .../analytics/people`'s `methodMix` (see that schema) — `human`
= actor_type='user'; `agent` = any non-user actor_type (DVT-1522).
This collapsed a prior three-way ui/mcp/api split that classified
by audit metadata (`oauth_client_id` vs `api_key_name`); that split
systematically mislabeled the dvt MCP server's actual traffic (which
authenticates via a customer `dvt_live_` API key, stamping
`api_key_name`) as "API" rather than "MCP", since the `oauth_client_id`
signal is only ever stamped by an unused OAuth-JWT code path. Channel
display attribution (`flows[].channel`, `keys[].name`) still surfaces
the underlying auth-path detail (UI / api-key-name / oauth-client-id)
— only the classification axis collapsed to two classes.

Two attribution gaps, both intentional: `flows[].channel` and
`keys[].name`-keyed attribution are NAME-grain (api_key_name is a
string, not a stable id, so two same-org keys sharing a name are
indistinguishable here — exact key-grain stamping is DVT-1435); and
`keys[].queries` is always `0` because `query_executions` carries no
api-key metadata at all, so no query can be attributed to a specific
key even at name-grain. `rollCall[].queries` is NOT affected by
either gap — it is grouped by the true underlying actor id, which
`query_executions.actor_id` does carry.

`methodWeekly` is a FIXED trailing 12 ISO (Monday-start) weeks,
independent of `days`. `kpis.schedules` and the `keys`/`schedules`
arrays are org-wide / point-in-time (unwindowed). `keys[].events30d`
is a FIXED trailing 30-calendar-day daily series, independent of
`days`. `kpis.deliverySuccessPct` is a FIXED trailing 14-calendar-day
org-wide figure, independent of `days`. Everything else honors
`days`. `connectionId`/`dashboardId`/`actorType` are accepted for
parity with the sibling analytics endpoints' query-string shape but
always ignored here — connections/dashboards/actor-type have no
bearing on "how content gets edited."

## Query parameters

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

## Response `200`

Org automation-analytics result.

- object
  - `data` OrgAnalyticsAutomation, required: Automation-analytics payload (DVT-1501) — how content gets edited. 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.
    - `kpis` AutomationKPIs, required: The automation-tab KPI strip (DVT-1501).
      - `editsWindow` integer, required: human+agent edits in the `days` window.
      - `agentSharePct` integer, required: DVT-1522: round(100 * agent / (human+agent)) over the window; 0 when the window has zero edits.
      - `activeKeys` integer, required: Org API keys, unrevoked, used within the last 30 calendar days.
      - `agentActors` integer, required: Distinct non-user audit_logs actor ids active in the `days` window.
      - `agentQueries` integer: DVT-1599: query_executions in the `days` window whose actor_type is 'api_key' or 'agent' — "queries run by keys & agents", the honest metric behind the ApiKeysPage relabel (there is no true per-request MCP-call log). Optional; older clients ignore it.
      - `schedules` integer, required: Org-wide export_schedules count (unwindowed).
      - `deliverySuccessPct` integer, required: FIXED trailing 14-calendar-day org-wide figure, independent of `days`: 100 * done / (done+failed) among export_runs, rounded; `-1` when there are zero terminal (done or failed) runs in the window (never a misleading 0%).
    - `methodWeekly` AutomationMethodPoint[], required: FIXED trailing 12 ISO (Monday-start) weeks, independent of `days`, oldest -> newest — always exactly 12 points, a zero-activity week still gets a point with human=agent=0.
      - `weekStart` string, required: "YYYY-MM-DD", the UTC Monday of that ISO week.
      - `human` integer, required
      - `agent` integer, required
    - `methodWindow` 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
    - `methodPrevWindow` 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
    - `flows` PublishFlow[], required: Edit-flow Sankey rows (channel -> action -> folder edit counts) over the `days` window, capped to the top 40 by count descending.
      - `channel` string, required: 'UI' for a human-authored edit, else the API key name (metadata.api_key_name) or MCP OAuth client id (metadata.oauth_client_id) when attributable, else the raw actor_type as a last resort. This is an opaque display label — do not string-match it to classify the row; use methodClass.
      - `methodClass` 'human' | 'agent', required: DVT-1522: 'human' (actor_type='user') or 'agent' (any other actor_type), emitted alongside channel so callers never have to string-sniff channel to classify a row (channel is opaque — an oauth_client_id or an operator-chosen key name give no reliable lexical signal, e.g. a key literally named "claude-sync" is still the agent class). actor_type alone is exhaustive, so unlike channel this field is never empty — no legacy unclassified row exists once classification no longer depends on audit metadata.
      - `action` 'create' | 'update', required
      - `folder` string, required: The resolved dashboard's folder name, "Unfiled" when its folder_id is null, or "No folder" when this row's parent dashboard can't be resolved at all (a non-content resource type or a genuine orphan).
      - `count` integer, required
    - `keys` AutomationKey[], required: Every org API key (unwindowed roster), each with windowed/fixed-window activity.
      - `keyId` string, required
      - `name` string, required
      - `createdBy` string, required: Creator's display name, else email, else "unknown".
      - `createdAt` string, date-time, required
      - `lastUsedAt` string, date-time, nullable
      - `scopes` string[]
      - `events30d` integer[], required: FIXED trailing 30-calendar-day daily event count (name-grain), independent of `days`, oldest -> newest.
      - `edits` integer, required: The `days`-windowed count of the same name-grain attribution as events30d.
      - `queries` integer, required: Always 0 — see the schema description.
    - `rollCall` AutomationRollCallEntry[], required: Non-human actors active in the `days` window, grouped by the underlying actor id, top 20 by combined edits+queries descending.
      - `name` string, required: Best-effort label — the most recent non-null api_key_name/oauth_client_id this actor's edits carried in the window, else the literal "agent".
      - `edits` integer, required
      - `queries` integer, required
      - `topDashboards` AutomationRollCallDashboard[], required: Top 3 dashboards by combined edits+queries, descending.
        - `dashboardId` string, required
        - `title` string, required: Dashboard title, or empty string when untitled.
        - `events` integer, required: Combined edits+queries, this actor, this dashboard, windowed.
    - `schedules` AutomationSchedule[], required: Every org export schedule (unwindowed roster), each with cadence + delivery health.
      - `scheduleId` string, required
      - `dashboardId` string, required
      - `dashboardTitle` string, required: Dashboard title, or empty string when untitled.
      - `cadence` string, required: Raw cron expression.
      - `target` string, required: Best-effort human summary of delivery destinations (active email-recipient count and/or Slack-webhook destination labels); "no destinations configured" when it has neither.
      - `lastRuns` string[], required: Up to the 14 most recent TERMINAL runs ('done' -> "ok", 'failed' -> "fail"; in-flight/'skipped' runs excluded), oldest -> newest.
      - `failingRecent` boolean, required: True when any of this schedule's 3 most-recent runs (by fired_at, any status) has status 'failed'.
  - `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)
