App Utilization
GET /clients/{client_id}/analytics/apps/utilization
Returns aggregate utilization metrics for all of a client's applications over a specified time period. Each row contains the full App DTO (including catalog data and categories) alongside period-level metrics (unique users, active time, average daily time, session count), usage and risk classifications, and optional comparison-period change percentages.
This endpoint powers the App Metrics table widget and the SaaS utilization table view. It is a collection-level analytics endpoint — for per-app time-series data, use App Activity.
Data source is app_usage_reports (same as App Activity), aggregated across all dates in the requested period rather than grouped by day.
Use Case
Use this endpoint to:
- Render a sortable utilization table across all client apps
- Compare app usage between two time periods (e.g. this month vs last month)
- Filter utilization by category, vendor, department, or favorite status
- Sort by usage metrics to find most/least used apps
- Filter to only newly discovered or dropped-off apps
Path Parameters
| Parameter | Type | Description |
|---|---|---|
client_id | string (uuid) | Unique identifier for the client organization |
Query Parameters
| Parameter | Type | Required | Default | Description |
|---|---|---|---|---|
granularity | string | Yes | - | Aggregation granularity: daily, weekly, monthly, quarterly. Determines how avg_time_ms_per_day is computed. |
start_date | string | Yes | - | Start of period (YYYY-MM-DD) |
end_date | string | Yes | - | End of period, inclusive (YYYY-MM-DD) |
compare_start_date | string | No | - | Comparison period start (YYYY-MM-DD). Must be provided with compare_end_date. |
compare_end_date | string | No | - | Comparison period end, inclusive (YYYY-MM-DD). Must be provided with compare_start_date. |
filter | string | No | - | Filter by discovery status: discovered (apps first seen this period), dropped_off (apps with prior activity but none this period). When omitted, returns all apps with activity. |
q | string | No | - | Search against canonical_name (case-insensitive, partial match) |
category_ids | string | No | - | Comma-separated catalog category UUIDs to filter by |
vendor_ids | string | No | - | Comma-separated catalog vendor UUIDs to filter by |
is_favorite | boolean | No | - | Filter by favorite status: true (favorites only), false (non-favorites only) |
is_approved | boolean | No | - | Filter by approval status: true (approved only), false (unapproved only) |
department_ids | string | No | - | Comma-separated department UUIDs to scope usage data to |
sort_by | string | No | canonical_name | Sort field: canonical_name, unique_users, active_time, avg_time, session_count |
sort_order | string | No | desc | Sort direction: asc, desc |
page_size | integer | No | 50 | Items per page. Max: 200 |
cursor | string | No | - | Pagination cursor from a previous response |
The singular forms category_id, vendor_id, and department_id are accepted as deprecated aliases. Use the plural forms for new integrations. See Filter Parameters: Plural IDs.
Granularity
granularity is required. It controls how avg_time_ms_per_day is computed — whether the average is across days, weeks, months, or quarters within the period. It does not affect unique_users, active_time_ms, or session_count, which are always period totals. See Analytics Pattern: Granularity.
Comparison period
When both compare_start_date and compare_end_date are provided, the change_pct field inside each metric object contains the percentage change from the comparison period to the primary period. When omitted, all change_pct fields are null.
Providing only one of the two comparison parameters returns 400.
filter parameter
| Value | Description |
|---|---|
| (omitted) | All apps with at least one row in app_usage_reports during the period |
discovered | Apps whose first-ever activity_date for this client falls within the period |
dropped_off | Apps with rows before start_date but zero rows within the period |
Response
| Field | Type | Description |
|---|---|---|
period | Period | Resolved date range |
data | AppUtilization[] | Array of per-app utilization objects |
total_count | integer | Total number of apps matching the current filters |
next_cursor | string | null | Cursor for the next page. null when on the last page. |
Example Requests
Basic utilization for February 2026
curl -X GET "https://api.example.com/v2/clients/aa7cf840-9ca9-46a3-9778-9015d6580d50/analytics/apps/utilization?granularity=monthly&start_date=2026-02-01&end_date=2026-02-28" \
-H "Authorization: Bearer YOUR_API_TOKEN"
With month-over-month comparison
curl -X GET "https://api.example.com/v2/clients/aa7cf840-9ca9-46a3-9778-9015d6580d50/analytics/apps/utilization?granularity=monthly&start_date=2026-02-01&end_date=2026-02-28&compare_start_date=2026-01-01&compare_end_date=2026-01-31" \
-H "Authorization: Bearer YOUR_API_TOKEN"
Filtered by category, sorted by active time
curl -X GET "https://api.example.com/v2/clients/aa7cf840-9ca9-46a3-9778-9015d6580d50/analytics/apps/utilization?granularity=monthly&start_date=2026-02-01&end_date=2026-02-28&category_ids=a7b8c9d0-e1f2-3456-abcd-789012345678&sort_by=active_time&sort_order=desc" \
-H "Authorization: Bearer YOUR_API_TOKEN"
Filtered by multiple categories
curl -X GET "https://api.example.com/v2/clients/aa7cf840-9ca9-46a3-9778-9015d6580d50/analytics/apps/utilization?granularity=monthly&start_date=2026-02-01&end_date=2026-02-28&category_ids=a7b8c9d0-e1f2-3456-abcd-789012345678,b8c9d0e1-f2a3-4567-bcde-901234567890" \
-H "Authorization: Bearer YOUR_API_TOKEN"
Discovered apps only
curl -X GET "https://api.example.com/v2/clients/aa7cf840-9ca9-46a3-9778-9015d6580d50/analytics/apps/utilization?granularity=monthly&start_date=2026-02-01&end_date=2026-02-28&filter=discovered" \
-H "Authorization: Bearer YOUR_API_TOKEN"
Scoped to a department
curl -X GET "https://api.example.com/v2/clients/aa7cf840-9ca9-46a3-9778-9015d6580d50/analytics/apps/utilization?granularity=monthly&start_date=2026-02-01&end_date=2026-02-28&department_ids=d1a2b3c4-e5f6-7890-abcd-ef1234567890" \
-H "Authorization: Bearer YOUR_API_TOKEN"
Example Responses
With comparison period
{
"period": {
"start_date": "2026-02-01",
"end_date": "2026-02-28",
"compare_start_date": "2026-01-01",
"compare_end_date": "2026-01-31"
},
"data": [
{
"app": {
"id": "f1a2b3c4-d5e6-7890-abcd-ef1234567890",
"catalog_app": {
"id": "a1b2c3d4-e5f6-7890-abcd-ef1234567890",
"canonical_name": "Slack",
"logo": "https://cdn.example.com/logos/slack.png",
"type": "desktop",
"vendor": { "id": "v1a2b3c4-d5e6-7890-abcd-ef1234567890", "name": "Salesforce, Inc.", "url": "https://www.salesforce.com" },
"categories": [{ "id": "a7b8c9d0-e1f2-3456-abcd-789012345678", "name": "Communication" }]
},
"is_favorite": true,
"is_approved": true,
"license_count": 50
},
"unique_users": { "value": 42, "change_pct": 5.2, "compare_value": 40 },
"active_time_ms": { "value": 432000000, "change_pct": -3.1, "compare_value": 446000000 },
"avg_time_ms_per_day": { "value": 15428571, "change_pct": -3.1, "compare_value": 15928571 },
"session_count": { "value": 1284, "change_pct": 7.8, "compare_value": 1191 },
"usage_level": "heavy",
"risk_level": null
},
{
"app": {
"id": "g2b3c4d5-e6f7-8901-bcde-f12345678901",
"catalog_app": {
"id": "b2c3d4e5-f6a7-8901-bcde-f12345678901",
"canonical_name": "GitHub",
"logo": "https://cdn.example.com/logos/github.png",
"type": "browser",
"vendor": { "id": "v2b3c4d5-e6f7-8901-bcde-f12345678901", "name": "GitHub, Inc.", "url": "https://www.github.com" },
"categories": [{ "id": "b8c9d0e1-f2a3-4567-bcde-901234567890", "name": "Developer Tools" }]
},
"is_favorite": false,
"is_approved": true,
"license_count": null
},
"unique_users": { "value": 28, "change_pct": 12.0, "compare_value": 25 },
"active_time_ms": { "value": 216000000, "change_pct": 8.4, "compare_value": 199440000 },
"avg_time_ms_per_day": { "value": 7714285, "change_pct": 8.4, "compare_value": 7116285 },
"session_count": { "value": 892, "change_pct": 15.3, "compare_value": 774 },
"usage_level": "medium",
"risk_level": null
}
],
"total_count": 78,
"next_cursor": "eyJsYXN0X2lkIjoiZzJiM2M0ZDUifQ"
}
Without comparison period
When no comparison dates are provided, change_pct is null inside each metric object:
{
"period": {
"start_date": "2026-02-01",
"end_date": "2026-02-28",
"compare_start_date": null,
"compare_end_date": null
},
"data": [
{
"app": {
"id": "f1a2b3c4-d5e6-7890-abcd-ef1234567890",
"catalog_app": {
"id": "a1b2c3d4-e5f6-7890-abcd-ef1234567890",
"canonical_name": "Slack",
"logo": "https://cdn.example.com/logos/slack.png",
"type": "desktop",
"vendor": { "id": "v1a2b3c4-d5e6-7890-abcd-ef1234567890", "name": "Salesforce, Inc.", "url": "https://www.salesforce.com" },
"categories": [{ "id": "a7b8c9d0-e1f2-3456-abcd-789012345678", "name": "Communication" }]
},
"is_favorite": true,
"is_approved": true,
"license_count": 50
},
"unique_users": { "value": 42, "change_pct": null, "compare_value": null },
"active_time_ms": { "value": 432000000, "change_pct": null, "compare_value": null },
"avg_time_ms_per_day": { "value": 15428571, "change_pct": null, "compare_value": null },
"session_count": { "value": 1284, "change_pct": null, "compare_value": null },
"usage_level": "heavy",
"risk_level": null
}
],
"total_count": 78,
"next_cursor": "eyJsYXN0X2lkIjoiZzJiM2M0ZDUifQ"
}
Empty Result
{
"period": {
"start_date": "2026-02-01",
"end_date": "2026-02-28",
"compare_start_date": null,
"compare_end_date": null
},
"data": [],
"total_count": 0,
"next_cursor": null
}
Period
| Field | Type | Nullable | Description |
|---|---|---|---|
start_date | string | No | Start of the primary period (YYYY-MM-DD) |
end_date | string | No | End of the primary period (YYYY-MM-DD) |
compare_start_date | string | Yes | Start of comparison period. null if no comparison requested. |
compare_end_date | string | Yes | End of comparison period. null if no comparison requested. |
AppUtilization
Per-app utilization metrics for the requested time period.
| Field | Type | Nullable | Description |
|---|---|---|---|
app | App | No | Full app record including catalog_app (name, logo, vendor, categories) and client-specific fields |
unique_users | MetricWithDeltaDto | No | Distinct users who used this app at least once during the period |
active_time_ms | MetricWithDeltaDto | No | Total active time in milliseconds across all users during the period |
avg_time_ms_per_day | MetricWithDeltaDto | No | Average active time in milliseconds per day within the period, computed using the requested granularity. value is null if no time data is available. |
session_count | MetricWithDeltaDto | No | Total sessions across all users during the period |
usage_level | string | Yes | Usage intensity: heavy, medium, or light. Derived from the Usage Score formula (35% adoption rate + 65% activity rate). null when insufficient data. |
risk_level | string | Yes | Risk classification: critical, medium, or low. Always null for approved apps. |
DB source:
| DTO Field | DB Aggregation |
|---|---|
app | Joined via app_usage_reports.application_id → apps → catalog_applications |
unique_users.value | COUNT(DISTINCT app_usage_reports.user_id) WHERE activity_date BETWEEN start_date AND end_date |
active_time_ms.value | SUM(app_usage_reports.total_active_ms) WHERE activity_date BETWEEN start_date AND end_date |
avg_time_ms_per_day.value | SUM(total_active_ms) / COUNT(DISTINCT activity_date) within the period |
session_count.value | SUM(app_usage_reports.total_session_count) WHERE activity_date BETWEEN start_date AND end_date |
*.change_pct | ((current - comparison) / comparison) * 100 using the same aggregations over the comparison date range |
*.compare_value | The same aggregation as *.value applied to the comparison date range. null when no comparison period is requested. |
usage_level | Derived from usage score: 35% adoption rate + 65% activity rate. heavy ≥ 0.66, medium ≥ 0.33, light < 0.33. null when fewer than 5 data points. |
risk_level | Sourced from apps.risk_level. null when apps.is_approved = true. |
Pagination
This endpoint uses cursor-based pagination.
- Omit
cursoron the first request - If
next_cursoris notnull, pass its value ascursoron the next request - Repeat until
next_cursorisnull
Constraints
- Max date range: 366 days for both primary and comparison periods. Requests exceeding this return
400. - Comparison symmetry: Both
compare_start_dateandcompare_end_datemust be provided together, or both omitted. - Timezone: All dates are in the client's configured timezone.
granularityis required. There is no default.
Error Responses
| Status | Description |
|---|---|
| 400 | Invalid parameters (bad date format, range exceeds max, mismatched comparison params, missing granularity, unknown filter or sort_by value) |
| 401 | Authentication required |
| 403 | Insufficient permissions for this client |
| 404 | Client not found |
| 500 | Server error |
Example Error Response
{
"error": {
"code": "invalid_parameter",
"message": "compare_start_date requires compare_end_date to be provided",
"details": {
"compare_start_date": "2026-01-01",
"compare_end_date": null
}
}
}
Notes
active_time_ms.valueis the total active time across all users for the period, in milliseconds. Divide by 3,600,000 for hours.avg_time_ms_per_day.valueis the average over days that had activity, not over the full date range. Its precision depends ongranularity.session_count.valueis the total number of sessions, not an average. This aligns with the DB columntotal_session_countand the existingsession-countmetric on App Activity.unique_users.valuecounts distinct users for the entire period, not per day.usage_levelisnullwhen there are fewer than 5 data points for the app in the period.risk_levelis alwaysnullfor approved apps (is_approved: true). It is only populated for unapproved apps.- Category badges should be read from
app.catalog_app.categories[]. An app may belong to zero or more categories. - When
department_idsis provided, all metrics are scoped to usage from users who belong to any of the specified departments. The app list itself is not filtered — apps with zero usage from those departments still appear withunique_users: 0. - Data source is
app_usage_reports, which is partitioned by month. Queries spanning month boundaries read from multiple partitions transparently.
Related Endpoints
- App Utilization Summary -- Card counts: apps used, discovered, dropped off
- App Utilization Rankings -- Top app by interactions, time, and active users
- App Activity -- Daily time-series for a single app
- List Apps -- App identity and metadata (CRUD domain)
- Get App -- Full app profile with catalog data