Skip to main content

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

ParameterTypeDescription
client_idstring (uuid)Unique identifier for the client organization

Query Parameters

ParameterTypeRequiredDefaultDescription
granularitystringYes-Aggregation granularity: daily, weekly, monthly, quarterly. Determines how avg_time_ms_per_day is computed.
start_datestringYes-Start of period (YYYY-MM-DD)
end_datestringYes-End of period, inclusive (YYYY-MM-DD)
compare_start_datestringNo-Comparison period start (YYYY-MM-DD). Must be provided with compare_end_date.
compare_end_datestringNo-Comparison period end, inclusive (YYYY-MM-DD). Must be provided with compare_start_date.
filterstringNo-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.
qstringNo-Search against canonical_name (case-insensitive, partial match)
category_idsstringNo-Comma-separated catalog category UUIDs to filter by
vendor_idsstringNo-Comma-separated catalog vendor UUIDs to filter by
is_favoritebooleanNo-Filter by favorite status: true (favorites only), false (non-favorites only)
is_approvedbooleanNo-Filter by approval status: true (approved only), false (unapproved only)
department_idsstringNo-Comma-separated department UUIDs to scope usage data to
sort_bystringNocanonical_nameSort field: canonical_name, unique_users, active_time, avg_time, session_count
sort_orderstringNodescSort direction: asc, desc
page_sizeintegerNo50Items per page. Max: 200
cursorstringNo-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

ValueDescription
(omitted)All apps with at least one row in app_usage_reports during the period
discoveredApps whose first-ever activity_date for this client falls within the period
dropped_offApps with rows before start_date but zero rows within the period

Response

FieldTypeDescription
periodPeriodResolved date range
dataAppUtilization[]Array of per-app utilization objects
total_countintegerTotal number of apps matching the current filters
next_cursorstring | nullCursor 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

FieldTypeNullableDescription
start_datestringNoStart of the primary period (YYYY-MM-DD)
end_datestringNoEnd of the primary period (YYYY-MM-DD)
compare_start_datestringYesStart of comparison period. null if no comparison requested.
compare_end_datestringYesEnd of comparison period. null if no comparison requested.

AppUtilization

Per-app utilization metrics for the requested time period.

FieldTypeNullableDescription
appAppNoFull app record including catalog_app (name, logo, vendor, categories) and client-specific fields
unique_usersMetricWithDeltaDtoNoDistinct users who used this app at least once during the period
active_time_msMetricWithDeltaDtoNoTotal active time in milliseconds across all users during the period
avg_time_ms_per_dayMetricWithDeltaDtoNoAverage active time in milliseconds per day within the period, computed using the requested granularity. value is null if no time data is available.
session_countMetricWithDeltaDtoNoTotal sessions across all users during the period
usage_levelstringYesUsage intensity: heavy, medium, or light. Derived from the Usage Score formula (35% adoption rate + 65% activity rate). null when insufficient data.
risk_levelstringYesRisk classification: critical, medium, or low. Always null for approved apps.

DB source:

DTO FieldDB Aggregation
appJoined via app_usage_reports.application_idappscatalog_applications
unique_users.valueCOUNT(DISTINCT app_usage_reports.user_id) WHERE activity_date BETWEEN start_date AND end_date
active_time_ms.valueSUM(app_usage_reports.total_active_ms) WHERE activity_date BETWEEN start_date AND end_date
avg_time_ms_per_day.valueSUM(total_active_ms) / COUNT(DISTINCT activity_date) within the period
session_count.valueSUM(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_valueThe same aggregation as *.value applied to the comparison date range. null when no comparison period is requested.
usage_levelDerived 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_levelSourced from apps.risk_level. null when apps.is_approved = true.

Pagination

This endpoint uses cursor-based pagination.

  1. Omit cursor on the first request
  2. If next_cursor is not null, pass its value as cursor on the next request
  3. Repeat until next_cursor is null

Constraints

  • Max date range: 366 days for both primary and comparison periods. Requests exceeding this return 400.
  • Comparison symmetry: Both compare_start_date and compare_end_date must be provided together, or both omitted.
  • Timezone: All dates are in the client's configured timezone.
  • granularity is required. There is no default.

Error Responses

StatusDescription
400Invalid parameters (bad date format, range exceeds max, mismatched comparison params, missing granularity, unknown filter or sort_by value)
401Authentication required
403Insufficient permissions for this client
404Client not found
500Server 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.value is the total active time across all users for the period, in milliseconds. Divide by 3,600,000 for hours.
  • avg_time_ms_per_day.value is the average over days that had activity, not over the full date range. Its precision depends on granularity.
  • session_count.value is the total number of sessions, not an average. This aligns with the DB column total_session_count and the existing session-count metric on App Activity.
  • unique_users.value counts distinct users for the entire period, not per day.
  • usage_level is null when there are fewer than 5 data points for the app in the period.
  • risk_level is always null for 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_ids is 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 with unique_users: 0.
  • Data source is app_usage_reports, which is partitioned by month. Queries spanning month boundaries read from multiple partitions transparently.