These are undocumented endpoints used by the Cursor dashboard at
https://cursor.com/dashboard/usage. They are not part of any public API and
may change or break without notice.
All endpoints use cookie-based session authentication. Requests without
valid session cookies receive a 401 {"error": "not_authenticated"} response.
The session relies on two httpOnly cookies (not visible to JavaScript on the page):
| Cookie | HttpOnly | Purpose |
|---|---|---|
WorkosCursorSessionToken |
Yes | The session/auth token. This is the one that matters for API access. |
cursor-web-target-synced-user |
Yes | Routing/targeting cookie. |
workos_id |
No | Non-httpOnly identifier. Visible to page JS but not the auth credential. |
- Open https://cursor.com/dashboard/usage in Chrome
- Open DevTools (F12) > Application > Cookies >
https://cursor.com - Copy the value of
WorkosCursorSessionToken
curl -s 'https://cursor.com/api/usage-summary' \
-H 'Cookie: WorkosCursorSessionToken=YOUR_COOKIE_VALUE'WorkosCursorSessionToken alone is sufficient for authentication. The other
cookies (cursor-web-target-synced-user, workos_id) are not required.
CSRF protection: POST endpoints require an Origin: https://cursor.com
header. Without it you get {"error": "Invalid origin for state-changing request"}.
GET endpoints do not require this header.
Returns billing cycle info, plan limits, and current usage totals.
Request:
GET https://cursor.com/api/usage-summary
No parameters. Returns data for the authenticated user.
Response:
{
"billingCycleStart": "2026-04-02T14:11:55.000Z",
"billingCycleEnd": "2026-05-02T14:11:55.000Z",
"membershipType": "enterprise",
"limitType": "team",
"isUnlimited": false,
"autoModelSelectedDisplayMessage": "You've used 100% of your included total usage",
"namedModelSelectedDisplayMessage": "You've used 100% of your included API usage",
"individualUsage": {
"plan": {
"enabled": true,
"used": 2000,
"limit": 2000,
"remaining": 0,
"breakdown": {
"included": 2000,
"bonus": 6121,
"total": 8121
},
"autoPercentUsed": 0,
"apiPercentUsed": 100,
"totalPercentUsed": 100
},
"onDemand": {
"enabled": true,
"used": 2309,
"limit": null,
"remaining": null
}
},
"teamUsage": {
"onDemand": {
"enabled": true,
"used": "<cents>",
"limit": "<cents>",
"remaining": "<cents>"
}
}
}Key fields:
billingCycleStart/billingCycleEnd- ISO 8601 timestamps for the current billing periodmembershipType-"pro","enterprise", etc.individualUsage.plan.used/.limit- request-based usage against plan allowanceindividualUsage.onDemand.used- usage-based (pay-per-use) consumptionteamUsage.onDemand- team-wide spend limits and usage (cents)
Returns legacy/simple usage counters. Appears to only track GPT-4 class requests.
Request:
GET https://cursor.com/api/usage?user=user_01JE43Z1MGD5DSQ5VHW4SNV7DE
The user parameter is your WorkOS user ID (the user_ prefixed string, not
the numeric userId).
Response:
{
"gpt-4": {
"numRequests": 0,
"numRequestsTotal": 0,
"numTokens": 0,
"maxTokenUsage": null,
"maxRequestUsage": null
},
"startOfMonth": "2026-04-02T14:11:55.000Z"
}This endpoint seems mostly vestigial. The usage-summary and filtered-usage-events endpoints provide much richer data.
The main usage data endpoint. Returns paginated, filterable usage events with per-request cost and token breakdowns.
Request:
POST https://cursor.com/api/dashboard/get-filtered-usage-events
Content-Type: application/json
Request body:
{
"teamId": 2168997,
"userId": 152683922,
"startDate": "1774846800000",
"endDate": "1775451599999",
"page": 1,
"pageSize": 100
}| Field | Type | Required | Description |
|---|---|---|---|
teamId |
number | No | Numeric team ID. Omit to get all events for the authenticated user. |
userId |
number | No | Numeric user ID. Filters to a specific user (useful for team admins). |
startDate |
string | No | Unix timestamp in milliseconds (as a string). Start of date range. |
endDate |
string | No | Unix timestamp in milliseconds (as a string). End of date range. |
page |
number | No | 1-based page number. Defaults to 1. |
pageSize |
number | No | Events per page. Defaults to 100. |
All fields are optional. An empty {} body returns the first 100 events across
all time for the authenticated user.
Response:
{
"totalUsageEventsCount": 30653,
"usageEventsDisplay": [
{
"timestamp": "1775418973898",
"model": "claude-4.6-opus-high-thinking",
"kind": "USAGE_EVENT_KIND_USAGE_BASED",
"requestsCosts": 30.4,
"usageBasedCosts": "$1.21",
"isTokenBasedCall": true,
"tokenUsage": {
"inputTokens": 3,
"outputTokens": 20525,
"cacheWriteTokens": 112151,
"totalCents": 121.41
},
"owningUser": "152683922",
"owningTeam": "2168997",
"cursorTokenFee": 3.32,
"isChargeable": true,
"isHeadless": false,
"chargedCents": 124.73
}
]
}Event fields:
| Field | Type | Description |
|---|---|---|
timestamp |
string | Unix timestamp in milliseconds (as string) |
model |
string | Model used (e.g., claude-4.6-opus-high-thinking, claude-4.6-sonnet-medium-thinking, composer-2) |
kind |
string | USAGE_EVENT_KIND_USAGE_BASED (billed per-token) or USAGE_EVENT_KIND_INCLUDED_IN_BUSINESS (included in plan) |
requestsCosts |
number | Cost in "requests" units against plan allowance |
usageBasedCosts |
string | Formatted dollar amount for usage-based billing |
isTokenBasedCall |
boolean | Whether this was a token-metered call |
tokenUsage |
object | Token breakdown: inputTokens, outputTokens, cacheWriteTokens, totalCents |
owningUser |
string | Numeric user ID (as string) |
owningTeam |
string | Numeric team ID (as string) |
cursorTokenFee |
number | Cursor's markup fee in cents |
isChargeable |
boolean | Whether this event incurred a charge |
isHeadless |
boolean | Whether the request was from a headless/background agent |
chargedCents |
number | Total charged amount in cents (tokenUsage.totalCents + cursorTokenFee) |
Pagination: Use page (1-based) and pageSize. totalUsageEventsCount
tells you the total number of events matching your filters.
import requests
session = requests.Session()
session.cookies.set("WorkosCursorSessionToken", "YOUR_COOKIE", domain="cursor.com")
# Required for POST endpoints (CSRF protection)
session.headers["Origin"] = "https://cursor.com"
summary = session.get("https://cursor.com/api/usage-summary").json()
print(f"Billing period: {summary['billingCycleStart']} to {summary['billingCycleEnd']}")
print(f"Plan used: {summary['individualUsage']['plan']['used']}/{summary['individualUsage']['plan']['limit']}")
on_demand = summary["individualUsage"]["onDemand"]
if on_demand["enabled"]:
print(f"On-demand usage: {on_demand['used']} cents")
# Fetch detailed events
events = session.post(
"https://cursor.com/api/dashboard/get-filtered-usage-events",
json={"page": 1, "pageSize": 10},
).json()
print(f"Total events: {events['totalUsageEventsCount']}")
for e in events["usageEventsDisplay"]:
print(f" {e['model']}: {e['usageBasedCosts']} ({e['tokenUsage']['outputTokens']} output tokens)")- Unofficial: These endpoints are reverse-engineered from the dashboard. They can change without notice.
- Cookie auth:
WorkosCursorSessionTokenis the only cookie needed. It is httpOnly, so you must extract it from DevTools (Application > Cookies). The token is a JWT; check theexpclaim for expiration. - CSRF: POST endpoints require
Origin: https://cursor.com. GET endpoints do not. - Rate limits: Unknown. Be reasonable with request frequency.
- CSV export: The "Export CSV" button on the dashboard is client-side only; it converts already-loaded event data to CSV in the browser. There is no server-side CSV endpoint.
- Team admins: If you have team admin access, you can also use the official
Admin API which provides
a
POST /teams/spendendpoint with API key authentication.