Part 1: Cursor Data Ingestion
Set up a Cursor app so ConfigView can pull your team’s seats, AI usage, spend, and model policy into the dashboard.You do not need an Enterprise plan for most of this. Cursor gates the Admin API per endpoint, not as a whole. The collectors that answer the usual questions — members, daily usage, usage events, spend, audit logs, repository blocklists — carry no plan restriction. Two features are conditional:Those five collectors read a403as “not available to this team”: they record zero rows, leave whatever was already there untouched, and exit cleanly. The health check shows them as skipped, not failed, so the app does not sit permanently red over a feature you simply do not have. The other six are unaffected.
Step 1: Create an API Key
- Sign in at cursor.com/dashboard as a team owner or admin.
- Open API Keys.
- Create a new key and give it a name you’ll recognise, e.g.
ConfigView. - Copy the key now — Cursor shows the secret only once. If you lose it, delete the key and create a new one.
crsr_ followed by 64 characters.
There is no username to find — the key is the username. Cursor authenticates with HTTP Basic, and its examples look likecurl -u YOUR_API_KEY: https://api.cursor.com/teams/members. The-u KEY:means username = the key, password = empty. So there is no second value to go looking for in the Cursor dashboard, and ConfigView is right to prompt you for one field only. Paste thecrsr_...key intoCURSOR_ADMIN_API_KEYand you are done. To confirm a key works before pasting it, run that curl yourself — note the trailing colon, which is what makes the password empty. Without it curl prompts for a password and the request fails in a way that looks like a bad key.
Give the key admin scope. ConfigView reads team-wide data, so a key scoped to a single user cannot see it. The Model Access collectors additionally needmodels:read,models:*, oradmin:*— without one of those, the three model tables stay empty while everything else works, which reads as a partial outage rather than a missing scope.
Step 2: Add the Key to ConfigView
- Go to your ConfigView dashboard:
https://{companyname}.configview.com/admin/integrations/cursor - Under Credentials, enter the value and click the save icon on its row:
CURSOR_ADMIN_API_KEY: the API key you just created
Step 3: Enable the Cursor App in ConfigView
- Go to:
https://{companyname}.configview.com/admin/integrations/cursor - Click Connect. ConfigView creates its tables and schedules every collector at your default run time.
- Click Verify now to confirm the credentials work.
Do not schedule Daily Usage or Usage Events more often than hourly. Cursor aggregates both on an hourly cycle and documents that they should be polled at most once an hour. Running them every fifteen minutes returns the same numbers four times and spends four times the request budget to do it.
Step 4: Verify
- Go to:
https://{companyname}.configview.com/admin/integrations/cursor - Click Verify now in the page header.
- All checks should pass.
- 401 — Cursor does not recognise the key value. ConfigView sends it
correctly (HTTP Basic, key as the username), so this is about the key itself:
rotated, deleted, or belonging to a different account. Create a fresh one.
Note that an Organization API key has the same
crsr_shape but does not work on/teams/*routes. - 403 on a core endpoint — the key is valid but lacks the scope. Give it
admin:*. - 403 on Billing Groups or Model Access — expected on most plans. See the table at the top; there is nothing to fix.
Available Scripts
The two user id spaces
This is the single most common way to get a wrong answer out of these tables. Cursor issues two different user identifiers and they do not convert into each other:
The two are named differently on purpose. Join usage to identity on
email,
never on an id. A join between cursor_daily_usage and cursor_members on
their id columns returns zero rows — which looks exactly like a team where
nobody has used Cursor, rather than like a broken join.
cursor_usage_events and cursor_audit_logs carry only user_email, so they
join the same way.
Counting requests correctly
cursor_daily_usage carries subscription_included_reqs, usage_based_reqs,
and api_key_reqs. These count raw usage events, not billable request
units — Cursor documents this explicitly. Summing them produces a number that
looks like a request count and does not match the invoice.
For anything billing-related, use cursor_usage_events and sum
requests_costs. That column is the billable unit. charged_cents is the money.
Why some tables are empty
An empty table here is usually a configuration fact rather than a failure, and the health check reports which:cursor_model_providersandcursor_modelsare empty. Cursor returns409on the providers route whenever the team’s model policy isunrestrictedorlegacy— that is, whenever there is nothing to restrict, which is the normal state for most teams. Checkcursor_model_access.state. If it sayscustomand these are still empty, the API key is missing themodels:read/models:*/admin:*scope.cursor_repo_blocklistsis empty. Nothing has been added to the blocklist. That is a real finding about the team, not a collection problem.cursor_groupshas one row. Teams that have not created billing groups still get Cursor’s reserved catch-all group, which ConfigView collects asis_unassigned = 1so group spend reconciles against the invoice.
Snapshot tables and append-only tables
Most ConfigView tables are snapshots: each run replaces the last, and you query the newestrun_at. Four of these tables are not, because they are time
series over a rolling window rather than inventories of a current state:
On these four,
run_at records when a row was last refreshed, not which
snapshot it belongs to. Filtering them by run_at = MAX(run_at) returns only
the rows the most recent run happened to touch.
The other seven tables are ordinary snapshots and behave as usual.
cursor_spend is worth calling out: Cursor exposes only the current billing
cycle and has no history endpoint. Keeping each cycle’s snapshot is the only
reason “what did we spend last month” is ever answerable, so the cycle start is
part of the row’s identity.
Configuring the lookback windows
Three collectors read a rolling window rather than everything. Cursor rejects any single request wider than 30 days, so a longer lookback is walked in 30-day chunks — it costs more requests, never duplicate rows, because every row upserts on its natural key.
Windows are deliberately wider than the schedule interval: that is what backfills
whatever a failed or skipped run would otherwise have lost, and what lets
late-arriving records land.
Usage Events has the shortest default on purpose. It is the only collector whose
cost scales with how much the team uses Cursor rather than with how many
people are on it — one row per billable request. Raise it to backfill history,
but raise
CURSOR_MAX_REQUESTS_PER_RUN alongside it or the run will stop at its
cap partway through.
Other optional settings
A run that hits
CURSOR_MAX_REQUESTS_PER_RUN or CURSOR_MAX_PAGES says so
loudly in its summary and commits what it collected. Neither is a silent cap.
About the API limits
Cursor sets limits per endpoint, between 20 and 250 requests per minute. It documents 20/min for audit logs, daily usage and the model-access routes, and 60/min for usage events; ConfigView paces the endpoints Cursor gives no number for at the tightest of those rather than assuming they are looser. The important part is that your whole team shares one API key. All eleven collectors — and anything else you point at that key — draw on the same budget, so each script also refuses to spend more than its own allowance and stops cleanly rather than rate-limiting the rest of the app out of its run. If the health check reports429 on the first call, something else is already
using the key.
Data Tables
Once the scripts run, the corresponding Cursor tables will be created in your database. All tables include arun_at column and a raw JSON column with the
full Cursor object, and every column carries a MySQL COMMENT describing it —
visible in SHOW FULL COLUMNS or any database GUI.
Remember the four append-only tables above: query those by their own time
column, not by run_at.