
Get by-user metrics with the Analytics API
Pull the same Analytics metrics as the team endpoints, keyed by user email, with GET https://api.cursor.com/analytics/by-user/{metric}. Official reference: Analytics API → By-User Endpoints.
Authenticate with Basic auth (API key as username, empty password). Create keys at Cursor Dashboard → API Keys. Most Analytics endpoints need scope admin:*. Availability: Enterprise only. By-user rate limit: 50 requests per minute per team (team-level routes stay at 100/min).
Use by-user routes for per-user reports or when paging large teams. Response data is an object keyed by email; each value is that user's metric array. params.userMappings maps emails to public user_… ids for the page.
Available metrics
| Path | Metric |
|---|---|
/analytics/by-user/agent-edits |
Agent edits |
/analytics/by-user/tabs |
Tab usage |
/analytics/by-user/models |
Model usage |
/analytics/by-user/top-file-extensions |
Top file extensions |
/analytics/by-user/client-versions |
Client versions |
/analytics/by-user/mcp |
MCP adoption |
/analytics/by-user/commands |
Commands adoption |
/analytics/by-user/plans |
Plans adoption |
/analytics/by-user/skills |
Skills adoption |
/analytics/by-user/ask-mode |
Ask mode adoption |
Query parameters
| Parameter | Rules |
|---|---|
startDate |
Optional. Default: 7 days ago. Prefer YYYY-MM-DD, or shortcuts now / today / yesterday / Nd. Max range 30 days. Time is ignored (day-level UTC). |
endDate |
Optional. Default: today. Same formats as startDate. |
page |
Optional. 1-indexed. Default: 1. |
pageSize |
Optional. Users per page. Default: 100, max: 500. |
users |
Optional. Comma-separated emails and/or public user ids (user_…). When set, pagination counts only those users. |
Omit both dates for the last 7 days (simplest path for HTTP caching). Prefer day shortcuts or YYYY-MM-DD over ISO timestamps so ETag cache hits stay stable.
Request (agent-edits example)
curl -X GET "https://api.cursor.com/analytics/by-user/agent-edits?page=1&pageSize=50" \
-u YOUR_API_KEY:
Limit pagination to specific users:
curl -X GET "https://api.cursor.com/analytics/by-user/agent-edits?users=alice@example.com,bob@example.com,carol@example.com" \
-u YOUR_API_KEY:
Example response:
{
"data": {
"alice@example.com": [
{
"event_date": "2025-01-15",
"total_suggested_diffs": 145,
"total_accepted_diffs": 98,
"total_rejected_diffs": 47,
"total_green_lines_accepted": 820,
"total_red_lines_accepted": 160,
"total_green_lines_rejected": 210,
"total_red_lines_rejected": 60,
"total_green_lines_suggested": 1030,
"total_red_lines_suggested": 220,
"total_lines_suggested": 1250,
"total_lines_accepted": 980
}
],
"bob@example.com": [
{
"event_date": "2025-01-15",
"total_suggested_diffs": 95,
"total_accepted_diffs": 72,
"total_rejected_diffs": 23,
"total_green_lines_accepted": 450,
"total_red_lines_accepted": 90,
"total_green_lines_rejected": 120,
"total_red_lines_rejected": 35,
"total_green_lines_suggested": 570,
"total_red_lines_suggested": 125,
"total_lines_suggested": 695,
"total_lines_accepted": 540
}
]
},
"pagination": {
"page": 1,
"pageSize": 50,
"totalUsers": 120,
"totalPages": 3,
"hasNextPage": true,
"hasPreviousPage": false
},
"params": {
"metric": "agent-edits",
"teamId": 12345,
"startDate": "2025-01-01",
"endDate": "2025-01-31",
"page": 1,
"pageSize": 50,
"userMappings": [
{ "id": "user_abc123", "email": "alice@example.com" },
{ "id": "user_def456", "email": "bob@example.com" }
]
}
}
Per-day field shapes match the corresponding team endpoint (for example Tab rows use total_suggestions / total_accepts; model rows use date + model_breakdown). Swap {metric} in the path to reuse the same pagination and users filter.
Response fields
| Field | Type | Meaning |
|---|---|---|
data |
object | Keys = user emails; values = arrays of daily metric rows |
pagination.page / pageSize |
number | Current page |
pagination.totalUsers / totalPages |
number | Filtered or full team user count |
pagination.hasNextPage / hasPreviousPage |
boolean | Page links |
params.metric |
string | Echoes the path metric (e.g. agent-edits) |
params.userMappings |
array | { id, email } for users on this page |
Caching
Store the response ETag and send it back as If-None-Match on the next poll. Unchanged data returns 304 Not Modified with no body; 304s do not count against the rate limit. Analytics endpoints use Cache-Control: public, max-age=900 (15 minutes). See API Overview → Caching.
Pitfalls
- Hitting the 100/min team budget mindlessly — by-user routes are capped at 50/min.
- Paginating all users when you only need a few — pass
usersso pagination collapses to that set. - Requesting more than 30 days — the Analytics date range caps at 30 days.
- Sending timestamps with clock times — time is ignored, and varying times break ETag cache hits.
- Polling without
If-None-Match— burns the 50/min by-user budget on identical payloads.
Sibling how-tos in this batch: Get the team leaderboard, Get Bugbot PR analytics, Get Bugbot review analytics. Related team routes: Get agent edits, Get Tab usage, Get model usage.