[Admin API] Usage and Member endpoints do not return complete data for removed users

Where does the bug appear (feature/product)?

Somewhere else…

Describe the Bug

The Cursor Admin API includes fields in response schemas that document how removed users should be represented. However, when users are removed from an organization, the actual API responses exclude critical identifying information for those users’ historical data:

  1. /teams/members endpoint: The documentation specifies an isRemoved boolean field that should indicate whether a team member has been removed. However, the endpoint only returns currently active members and completely omits removed users from the response, making it impossible to distinguish between users who were never members and users who have been removed.

  2. /teams/filtered-usage-events endpoint: The documentation lists userEmail as a required response field for all usage events. However, when querying historical usage events for users who have subsequently been removed, the userEmail field is missing from the response, even though the events were generated while the user was active.

This creates data integrity issues when attempting to build a monthly report of our teams usage as historical usage data becomes incomplete and difficult to attribute after user removal

Steps to Reproduce

Prerequisites

  • Team with Admin API access and an API key (API overview)
  • A test user who can be removed without violating team minimums (at least one paid member and one admin remain)
  • jq, curl, and permission to remove a member

Replace placeholders: YOUR_API_KEY, REMOVED_USER_EMAIL, ACTIVE_USER_EMAIL, date range epoch ms.

1. Establish baseline while user is active

export API_KEY="YOUR_API_KEY"
export USER_EMAIL="REMOVED_USER_EMAIL"

# Member directory
curl -sS -u "$API_KEY:" https://api.cursor.com/teams/members \
  | jq --arg e "$USER_EMAIL" '.teamMembers[] | select(.email | ascii_downcase == ($e | ascii_downcase))'

# Usage in a recent window (adjust dates to your billing period)
curl -sS -u "$API_KEY:" https://api.cursor.com/teams/filtered-usage-events \
  -H "Content-Type: application/json" \
  -d "{
    \"startDate\": 1746057600000,
    \"endDate\": 1748735999999,
    \"email\": \"$USER_EMAIL\",
    \"page\": 1,
    \"pageSize\": 10
  }" | jq '{total: .totalUsageEventsCount, sample: .usageEvents[0]}'

Note: Record whether the member’s id is a number or a user_… string, and whether sample events include any user id field.

2. Remove the user

curl -sS -u "$API_KEY:" https://api.cursor.com/teams/remove-member \
  -H "Content-Type: application/json" \
  -d "{\"email\": \"$USER_EMAIL\"}" | jq .

Record userId from the response.

3. Re-check member directory

curl -sS -u "$API_KEY:" https://api.cursor.com/teams/members \
  | jq --arg e "$USER_EMAIL" '
    .teamMembers[]
    | select(.email | ascii_downcase == ($e | ascii_downcase))
    | {id, email, name, role, isRemoved}
  '

Expected (per docs): One object with isRemoved: true and stable id.
Failure modes:

  • No row returned for that email (user omitted from teamMembers).
  • isRemoved missing or false after removal.
  • id type/shape unlike userId from step 2.

4. Fetch historical usage for the same period

curl -sS -u "$API_KEY:" https://api.cursor.com/teams/filtered-usage-events \
  -H "Content-Type: application/json" \
  -d "{
    \"startDate\": 1746057600000,
    \"endDate\": 1748735999999,
    \"email\": \"$USER_EMAIL\",
    \"page\": 1,
    \"pageSize\": 25
  }" | jq '{
    total: .totalUsageEventsCount,
    hasUserIdOnEvents: ([.usageEvents[] | has("userId")] | any),
    emails: ([.usageEvents[].userEmail] | unique)
  }'

Expected: Events still returned for pre-removal activity; each event carries userId or step 3 provides a joinable member row.
Failure mode: Events exist (non-zero totalUsageEventsCount) but step 3 did not return a member—client cannot map email → stable id.

5. Attempt userId filter using member id

Using the id from step 3 (or userId from step 2):

export MEMBER_ID="user_PDSPmvukpYgZEDXsoNirw3CFhy"   # example encoded id

curl -sS -u "$API_KEY:" https://api.cursor.com/teams/filtered-usage-events \
  -H "Content-Type: application/json" \
  -d "{
    \"startDate\": 1746057600000,
    \"endDate\": 1748735999999,
    \"userId\": \"$MEMBER_ID\",
    \"page\": 1,
    \"pageSize\": 10
  }" | jq '{total: .totalUsageEventsCount, error: .error}'

Expected: Filter accepts the same identifier shape as /teams/members and returns the same event set as the email filter (for that user’s historical window).
Failure modes: 400/empty results when passing encoded string ids; only numeric ids accepted despite live members returning strings.

6. Control: active member still enriches

Repeat steps 1 and 4 for ACTIVE_USER_EMAIL who remains on the team. Compare whether user_id resolution succeeds for active but not removed users—indicates a removed-member-specific directory gap rather than a generic integration bug.

Expected Behavior

Directory (GET /teams/members)

GIVEN a user was previously on the team and accrued usage in the current or a past billing period
AND the user was later removed (via admin UI or POST /teams/remove-member)
WHEN a client calls GET /teams/members with a valid team Admin API key
THEN the user appears in teamMembers
AND isRemoved is true
AND id is the same stable identifier used elsewhere in the Admin API (encoded user_… form, as used in audit-log user filters and billing groups)
AND email matches the userEmail on that user’s historical usage events (after normalising case and surrounding whitespace).

Usage events (POST /teams/filtered-usage-events)

GIVEN a date range that includes activity from a since-removed team member
WHEN a client calls POST /teams/filtered-usage-events for that range (with or without email / userId filters)
THEN events for that member are returned with the same userEmail as when they were active
AND each event includes a stable userId or the documentation guarantees that /teams/members always returns a joinable row for that email (including isRemoved: true).

Cross-endpoint consistency

GIVEN the Admin API documents userId as a filter on /teams/filtered-usage-events
WHEN a client obtains id from GET /teams/members
THEN that value is accepted by the userId filter and matches the identifier returned from POST /teams/remove-member (userId in the remove-member response).

Operating System

MacOS

Version Information

Cursor Admin API: 2026-05-26

Does this stop you from using Cursor

No - Cursor works, but with this issue

To answer each of them

  1. /teams/members omitting removed users — under certain conditions, removed members are dropped from the response entirely rather than appearing with isRemoved: true. Our team is aware of this gap.

  2. /teams/filtered-usage-events losing userEmail for removed users - this is a known issue affecting multiple teams. The email resolution path depends on active team membership, so once a user is removed, their historical events lose email attribution. The email filter also rejects removed user emails for the same reason.

  3. userId type mismatch - the string-encoded IDs (user_XXX) returned by /teams/members are not accepted by the numeric userId filter on /teams/filtered-usage-events. This cross-endpoint inconsistency is tracked separately.

Workaround (partial): If you maintain a daily snapshot of the /teams/members response, you can preserve email-to-ID mappings before members are removed. This won’t recover data for already-removed users, but it prevents the attribution gap going forward.

All three issues are being tracked internally. Your report is thorough and well-structured — it’s been added to the existing investigation.

Thanks for quick response!

Will keep and eye on this thread, is there any rough ETA or priority on when these issues will be addressed?

We don’t have a specific ETA to share at this time. These issues are tracked internally and will be addressed, but I can’t commit to a timeline.

We’ll follow up on this thread when there’s an update.