> ## Documentation Index
> Fetch the complete documentation index at: https://support.configview.com/llms.txt
> Use this file to discover all available pages before exploring further.

# Cursor setup

## 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.

> **The Admin API is an Enterprise feature.** Cursor exposes it to Enterprise
> teams only. On any other plan the API key authenticates but the routes return
> `403 Forbidden`, and no configuration will fix that — check your plan first.

***

### Step 1: Create an API Key

1. Sign in at **[cursor.com/dashboard](https://cursor.com/dashboard)** as a team
   owner or admin.
2. Open **API Keys**.
3. Create a new key and give it a name you'll recognise, e.g. `ConfigView`.
4. **Copy the key now** — Cursor shows the secret only once. If you lose it,
   delete the key and create a new one.

A Cursor API key looks like `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 like
> `curl -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 the `crsr_...` key into `CURSOR_ADMIN_API_KEY` and
> 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 need
> `models:read`, `models:*`, or `admin:*` — 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

1. Go to your ConfigView dashboard: `https://{companyname}.configview.com/admin/integrations/cursor`
2. 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

1. Go to: `https://{companyname}.configview.com/admin/integrations/cursor`
2. Click **Connect**. ConfigView creates its tables and schedules every collector
   at your default run time.
3. 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

1. Go to: `https://{companyname}.configview.com/admin/integrations/cursor`
2. Click **Verify now** in the page header.
3. All checks should pass.

If a check fails, confirm the secret saved correctly, that the key came from the
**API Keys** page of the team dashboard, and that the team is on an Enterprise
plan.

***

### Available Scripts

| Script                | Table                    | Description                                                                                                               |
| --------------------- | ------------------------ | ------------------------------------------------------------------------------------------------------------------------- |
| Members               | `cursor_members`         | The team roster — email, name, role, and whether the member has been removed                                              |
| Daily Usage           | `cursor_daily_usage`     | One row per member per day: lines suggested and accepted, tab / chat / agent / composer activity, and the model most used |
| Usage Events          | `cursor_usage_events`    | **One row per billable request** — model, token counts, request cost, and cents charged                                   |
| Spend                 | `cursor_spend`           | Per-member spend for the billing cycle, with each member's configured limit                                               |
| Audit Logs            | `cursor_audit_logs`      | Admin activity — sign-ins, membership changes, role changes, settings changes                                             |
| Billing Groups        | `cursor_groups`          | Billing groups with cycle spend, member counts, and directory linkage                                                     |
| Billing Group Members | `cursor_group_members`   | The member-to-group edge list, including members who have left a group                                                    |
| Repository Blocklists | `cursor_repo_blocklists` | Repositories and glob patterns withheld from Cursor's AI features                                                         |
| Model Access Policy   | `cursor_model_access`    | Whether the team restricts models, and what a newly released model defaults to                                            |
| Model Providers       | `cursor_model_providers` | Each provider and whether the team has it enabled                                                                         |
| Models                | `cursor_models`          | Every model the policy names, with its enabled state and configured parameters                                            |

***

### 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**:

| Column                                | Shape                              | Where it comes from       |
| ------------------------------------- | ---------------------------------- | ------------------------- |
| `cursor_members.cursor_user_id`       | Encoded string, e.g. `user_a1b2c3` | `/teams/members`          |
| `cursor_spend.cursor_user_id`         | Encoded string                     | `/teams/spend`            |
| `cursor_group_members.cursor_user_id` | Encoded string                     | `/teams/groups`           |
| `cursor_daily_usage.numeric_user_id`  | Integer, e.g. `4815`               | `/teams/daily-usage-data` |

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_providers` and `cursor_models` are empty.** Cursor returns
  `409` on the providers route whenever the team's model policy is
  `unrestricted` or `legacy` — that is, whenever there is nothing to restrict,
  which is the normal state for most teams. Check `cursor_model_access.state`.
  If it says `custom` and these are still empty, the API key is missing the
  `models:read` / `models:*` / `admin:*` scope.
* **`cursor_repo_blocklists` is empty.** Nothing has been added to the
  blocklist. That is a real finding about the team, not a collection problem.
* **`cursor_groups` has one row.** Teams that have not created billing groups
  still get Cursor's reserved catch-all group, which ConfigView collects as
  `is_unassigned = 1` so 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 newest `run_at`. **Four of these tables are not**, because they are time
series over a rolling window rather than inventories of a current state:

| Table                 | Query it by   | Why                                                                   |
| --------------------- | ------------- | --------------------------------------------------------------------- |
| `cursor_daily_usage`  | `usage_date`  | Rows accumulate per user per day                                      |
| `cursor_usage_events` | `event_ts`    | Rows accumulate per billable request                                  |
| `cursor_audit_logs`   | `event_ts`    | Rows accumulate per audit event                                       |
| `cursor_spend`        | `cycle_start` | One snapshot per billing cycle, kept so past cycles remain answerable |

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.

| Variable                      | Default | Effect                             |
| ----------------------------- | ------- | ---------------------------------- |
| `CURSOR_USAGE_LOOKBACK_DAYS`  | `30`    | How far back Daily Usage re-reads  |
| `CURSOR_EVENTS_LOOKBACK_DAYS` | `7`     | How far back Usage Events re-reads |
| `CURSOR_AUDIT_LOOKBACK_DAYS`  | `30`    | How far back Audit Logs re-reads   |

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

| Variable                      | Default                  | Effect                                                                              |
| ----------------------------- | ------------------------ | ----------------------------------------------------------------------------------- |
| `CURSOR_MAX_REQUESTS_PER_RUN` | per script               | Hard cap on requests one run may spend                                              |
| `CURSOR_PAGE_SIZE`            | per script               | Page size; the per-endpoint maximum is 500 for audit logs and 1000 for usage events |
| `CURSOR_MAX_PAGES`            | `2000`                   | Backstop against a pagination change looping forever                                |
| `CURSOR_API_BASE`             | `https://api.cursor.com` | Only change this if Cursor moves the endpoint                                       |

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 reports `429` 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 a `run_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`.
