# CauseWise MCP and management API

Use a workspace API key so Cursor, Claude Code, Codex, or another MCP client can register a site, install tracking, configure goals and funnels, and read analytics. The assistant edits the website repository. CauseWise does not.

The management key is not the public tracking key. Never put it in website code. The tracking snippet contains only the public site key.

Entry points:

- [Tracking API](./causewise-tracking.md) — `causewise.js`, automatic pageviews, `window.causewise.track`
- [Event conventions](./causewise-events.md) — recommended names; custom names still work
- [AI coding assistants](./causewise-agents.md) — install order, framework notes, examples, verification
- This page — REST, MCP, API keys

`get_tracking_instructions` returns the tracking rules and, when you pass `siteId`, that site’s official snippet.

## 1. Create an API key

1. Open **Settings** in CauseWise.
2. Under **API keys**, name the key, leave the scopes you need selected, and choose an optional expiration.
3. Copy the key when it is shown. CauseWise stores only a hash and will not show the secret again.
4. Revoke a key from the same list when you no longer use it.

Scopes:

- `sites:read` — list sites, read a site, and fetch the tracking snippet
- `sites:write` — create and update sites
- `goals:read` — list goals and read goal analytics
- `goals:write` — create, update, and delete goals
- `funnels:read` — list funnels and read funnel analytics
- `funnels:write` — create, update, and delete funnels
- `analytics:read` — tracking status, recent events, and the site analytics summary

Write does not include read. Select both when a tool should create a goal and then read it back.

Keys created before goal and funnel scopes existed keep only the scopes they were given. Create a new key, or a second key, when you need `goals:*` or `funnels:*`. CauseWise does not add scopes to an existing key.

A key cannot see another workspace, billing, or account settings.

## 2. Connect Cursor

CauseWise speaks MCP over HTTP at:

`https://causewise.io/api/mcp`

Local development:

`http://localhost:3000/api/mcp`

In Cursor, add the server to your MCP configuration. Do not commit the key.

```json
{
  "mcpServers": {
    "causewise": {
      "url": "https://causewise.io/api/mcp",
      "headers": {
        "Authorization": "Bearer cw_api_YOUR_KEY"
      }
    }
  }
}
```

The client must send `Authorization: Bearer` with a `cw_api_…` key. A public tracking key (`cw_` followed by hex, without `api`) is rejected. CauseWise does not advertise OAuth for this endpoint.

### Claude Code

Put this in `.mcp.json`. Claude Code requires `type`. `${CAUSEWISE_API_KEY}` is the raw `cw_api_…` key from the environment. Do not commit the key.

```json
{
  "mcpServers": {
    "causewise": {
      "type": "http",
      "url": "https://causewise.io/api/mcp",
      "headers": {
        "Authorization": "Bearer ${CAUSEWISE_API_KEY}"
      }
    }
  }
}
```

### Codex

Add this to `~/.codex/config.toml` or a trusted project `.codex/config.toml`. Set `CAUSEWISE_AUTHORIZATION` to `Bearer cw_api_…`. Codex copies that environment value into the `Authorization` header. Do not commit the key.

```toml
[mcp_servers.causewise]
url = "https://causewise.io/api/mcp"

[mcp_servers.causewise.env_http_headers]
Authorization = "CAUSEWISE_AUTHORIZATION"
```

### Tools

The server exposes these 21 tools. Each call still needs the scope listed in the REST table below.

`list_sites`, `create_site`, `get_site`, `update_site`, `get_tracking_snippet`, `get_tracking_instructions`, `get_tracking_status`, `list_recent_events`, `list_goals`, `create_goal`, `get_goal`, `update_goal`, `delete_goal`, `get_goal_analytics`, `list_funnels`, `create_funnel`, `get_funnel`, `update_funnel`, `delete_funnel`, `get_funnel_analytics`, `get_site_analytics_summary`.

## 3. Ask Cursor to integrate a site

Example prompt:

```text
Integrate CauseWise into this website. Register the site if necessary, retrieve its tracking snippet, install tracking using this project's framework conventions, and verify the integration.
```

Cursor should:

1. Call `list_sites` and reuse a site whose domain matches this project.
2. Call `create_site` only when the domain is new. CauseWise normalizes the domain and returns the existing site instead of creating a duplicate.
3. Call `get_tracking_snippet` and install that HTML once.
4. Detect the framework and place the snippet in the shared layout or document head.
5. Skip installation when the same `data-site` value is already present.
6. Leave other analytics scripts in place.
7. Run the project's usual checks.
8. Call `get_tracking_status` after a real page load. Do not invent a test event.

The MCP server does not edit the repository. The coding assistant does. Before it edits, it should call `get_tracking_instructions`. Pageviews are automatic. Custom outcomes use `window.causewise.track`. A passing test suite does not prove an event was stored.

A copyable prompt for that work is in [causewise-agents.md](./causewise-agents.md).

## 4. REST API

The MCP tools call the same operations as these endpoints. Send `Authorization: Bearer cw_api_…`.

| Method | Path | Scope |
| --- | --- | --- |
| GET | `/api/v1/sites` | `sites:read` |
| POST | `/api/v1/sites` | `sites:write` |
| GET | `/api/v1/sites/:siteId` | `sites:read` |
| PATCH | `/api/v1/sites/:siteId` | `sites:write` |
| GET | `/api/v1/sites/:siteId/tracking-snippet` | `sites:read` |
| GET | `/api/v1/sites/:siteId/tracking-status` | `analytics:read` |
| GET | `/api/v1/sites/:siteId/events` | `analytics:read` |
| GET | `/api/v1/sites/:siteId/overview` | `analytics:read` |
| GET | `/api/v1/sites/:siteId/analytics/summary` | `analytics:read` |
| GET | `/api/v1/sites/:siteId/goals` | `goals:read` |
| POST | `/api/v1/sites/:siteId/goals` | `goals:write` |
| GET | `/api/v1/sites/:siteId/goals/:goalId` | `goals:read` |
| PATCH | `/api/v1/sites/:siteId/goals/:goalId` | `goals:write` |
| DELETE | `/api/v1/sites/:siteId/goals/:goalId` | `goals:write` |
| GET | `/api/v1/sites/:siteId/goals/:goalId/analytics` | `goals:read` |
| GET | `/api/v1/sites/:siteId/funnels` | `funnels:read` |
| POST | `/api/v1/sites/:siteId/funnels` | `funnels:write` |
| GET | `/api/v1/sites/:siteId/funnels/:funnelId` | `funnels:read` |
| PATCH | `/api/v1/sites/:siteId/funnels/:funnelId` | `funnels:write` |
| DELETE | `/api/v1/sites/:siteId/funnels/:funnelId` | `funnels:write` |
| GET | `/api/v1/sites/:siteId/funnels/:funnelId/analytics` | `funnels:read` |

`POST /api/v1/sites` body: `{ "name": "Example", "domain": "https://www.example.com" }`.

The domain is stored as `example.com`. A second create for that domain returns the existing site with `created: false`.

The snippet response includes the site id, name, domain, public tracking key, the HTML snippet from the same template used in the dashboard, the script URL, and installation notes. Collection stays on the existing `/api/collect` endpoint.

`GET /api/v1/sites/:siteId/events?limit=20` returns the latest events for a site in the key's workspace. Each event includes `id`, `type`, `path`, and `timestamp`. Property payloads are omitted. A request that also sends `range`, `filter`, or `cursor` returns one page: `filter` is `all`, `pageviews`, or `custom`; `limit` is 1–50 and defaults to 20; `nextCursor` is null on the last page. `range` is `today`, `7d`, or `30d`, and the window stays inside the workspace plan's retention. `?limit=` alone keeps the original short list.

`GET /api/v1/sites/:siteId/overview?range=today|7d|30d` returns page views, unique sessions, top pages, referrers, and the traffic trend for a site in the key's workspace. `trend[].count` is the number of events in each hour for today, or each day for 7d and 30d. It is not a pageview or visitor series. Pageviews and unique sessions are the separate `pageviews` and `uniqueSessions` fields. The default range is `7d`. Counts come from the same overview query as the CauseWise site dashboard, including that plan's retention window. A free workspace asking for `30d` receives `7d`. The response `range`, `start`, and `end` fields are the window that was queried. This route is not an MCP tool. `get_site_analytics_summary` is the compact MCP overview and uses the same date window.

## 5. Tracking status

Status is based only on collected events:

- `no_events` — nothing has arrived. Creating a site does not count as installed.
- `events_previously` — events exist, but none in the last 24 hours.
- `events_recently` — at least one event arrived in the last 24 hours. This is the operational state.

`recentEventCount` and `recentPageviewCount` use that 24-hour window. `lastEventAt` is the newest event of any age.

## 6. Goals

A goal matches collected events. It does not create those events.

`POST /api/v1/sites/:siteId/goals`

Custom event:

```json
{
  "name": "Successful registrations",
  "trackingMethod": "custom_event",
  "eventName": "signup_completed",
  "propertyConditions": [{ "key": "method", "value": "email" }]
}
```

Page visit:

```json
{
  "name": "Thank-you page",
  "trackingMethod": "pageview",
  "pathPattern": "/thank-you"
}
```

Rules, shared with the dashboard:

- `custom_event` requires `eventName` using letters, numbers, and underscores.
- `pageview` always matches the `pageview` event and requires a path. Paths may be exact, a substring, or a `*` wildcard such as `/checkout/*`.
- `propertyConditions` are AND equality checks. They are not allowed on page visits. At most 8 conditions. Keys start with a letter and use letters, numbers, and underscores.
- `isActive` defaults to `true`.

`GET /api/v1/sites/:siteId/goals/:goalId/analytics?range=today|7d|30d` returns the goal, the reporting period, matching events, unique converting visitors, the latest matching event, and tracking status (`receiving_events`, `no_matching_events`, `tracking_not_installed`, or `inactive`).

`conversionRate` on this route is the per-goal unique-visitor rate: unique converting visitors divided by unique pageview sessions. The response field `conversionRateDefinition` states that. It is `null` when there are no pageview sessions, when any matching event has no visitor id, when converting visitors exceed pageview sessions, or when the goal is inactive. This route does not substitute pageviews.

That rate is separate from the site dashboard. The dashboard, and `goals.conversionRate` on the analytics summary, still divide matching events on active goals by unique pageview sessions, or by pageviews when there are no sessions, and stay `null` when conversions are 0. The summary labels that rollup with `conversionRateDefinition`.

`eventCoverage` says whether that event name was collected during the plan's retention window, and lists event names that were. If it was not collected, the website has to send it.

MCP tools: `list_goals`, `create_goal`, `get_goal`, `update_goal`, `delete_goal`, `get_goal_analytics`.

## 7. Funnels

A funnel is an ordered list of 2 to 8 steps and a conversion window of 1 hour, 24 hours, 7 days, or 30 days. The window may be those labels or `3600`, `86400`, `604800`, or `2592000` seconds. The default is 7 days. Later steps count only for visitors who already completed the earlier steps, using the same visitor identity and matching rules as the dashboard.

`POST /api/v1/sites/:siteId/funnels`

```json
{
  "name": "Homepage to registration",
  "conversionWindow": "7 days",
  "steps": [
    { "name": "Homepage", "eventName": "pageview", "pathPattern": "/" },
    { "name": "Registration", "eventName": "signup_completed" }
  ]
}
```

`GET /api/v1/sites/:siteId/funnels/:funnelId/analytics?range=7d` returns entry visitors, visitors at each step, step-to-step conversion, drop-off count and percentage, overall conversion, median conversion time, the conversion window, and `largestDropOff` when any step lost visitors. Those numbers come from the existing funnel calculation.

Each step includes `eventCoverage`. A step with no matching event is not treated as if the event exists.

MCP tools: `list_funnels`, `create_funnel`, `get_funnel`, `update_funnel`, `delete_funnel`, `get_funnel_analytics`.

## 8. Site analytics summary

`GET /api/v1/sites/:siteId/analytics/summary?range=today|7d|30d` and `get_site_analytics_summary` return a short overview: pageviews, unique pageview sessions, top pages, and recent events without property payloads.

`visitors` is the distinct visitor id count only when every event in the period has a visitor id. Otherwise it is `null`.

Goal and funnel summaries are included only when the key also has `goals:read` or `funnels:read`. A key with only `analytics:read` still receives traffic, plus `available: false` and the missing scope.

`goals.conversionRate` is the existing dashboard rollup, described by `goals.conversionRateDefinition`. `funnels[].overallConversionRate` is the existing funnel completion rate, described by `overallConversionRateDefinition`. Neither field is the per-goal unique-visitor rate from goal analytics.

## 9. Ask Cursor to configure conversion tracking

Example prompt:

```text
Using CauseWise MCP, inspect the events for this website, create a signup conversion goal, and build a funnel from homepage visit to registration.
```

Cursor should:

1. Call `list_sites` and use the site for this domain.
2. Call `list_recent_events` and `get_tracking_status`. Use event names that appear there.
3. Call `create_goal` with `trackingMethod` `custom_event` and `eventName` `signup_completed` only if that name was collected, or if you are about to add that call in this repository.
4. Call `create_funnel` with a pageview step for `/` and a second step for `signup_completed`.
5. Call `get_goal_analytics` and `get_funnel_analytics`.
6. Read `eventCoverage.guidance` and `largestDropOff` before explaining the result.

If `signup_completed` is not in the collected events, do not pretend it is tracked. Add `window.causewise.track("signup_completed")` in the website's registration success path, then reload and check analytics again. The CauseWise MCP server does not modify this repository.

The same pattern works for a purchase goal (`purchase_completed`) or a longer funnel such as landing page, signup, then purchase.

## 10. Paid API access

Management REST routes under `/api/v1` and `POST /api/mcp` require an eligible paid subscription in addition to a valid key and scope.

Eligible means the stored plan is Pro, Team, or Enterprise and the subscription status is `active`, `trialing`, or unset. The free plan is not eligible. A paid plan with status `canceled`, `past_due`, or `unpaid` is not eligible. CauseWise uses the same status window as dashboard entitlements. There is no separate grace period.

A workspace that fails this check receives HTTP 402:

```json
{ "error": { "code": "subscription_required", "message": "The management API requires an active Pro, Team, or Enterprise subscription. Tracking collection and the CauseWise dashboard are unchanged." } }
```

Existing keys stay valid. They work again when the subscription is eligible. Public tracking (`/api/collect` and `cw_` site keys), the signed-in dashboard, and the WordPress snippet are not part of this check.

The server SDK `@razorsoft/causewise` calls these same routes. Install and usage are documented in the package README and at `/causewise-sdk.md`.

## 11. Troubleshooting

- **401 Invalid API key** — the bearer token is missing, malformed, or not a management key.
- **401 revoked or expired** — create a new key in Settings.
- **402 subscription_required** — the workspace needs an active Pro, Team, or Enterprise subscription.
- **403 scope** — the key is missing the scope named in the error, such as `goals:read` or `funnels:write`.
- **400 invalid_goal or invalid_funnel** — the name, event, path, property conditions, step count, or conversion window does not match the rules above.
- **403 site limit** — the workspace plan cannot add another site.
- **404 Site, goal, or funnel not found** — the id is unknown on this site in this workspace. A delete cannot affect another workspace.
- **429** — the key exceeded 120 requests in a minute.
- **Status stays `no_events`** — the snippet may be missing, blocked, or the page has not been loaded since install. CauseWise does not create a fake pageview for you.

## 12. Security

- Only the SHA-256 hash of a management key is stored.
- The full key is returned once, at creation.
- Revoked and expired keys stop working.
- Tracking snippets contain the public site key only.
- Recent events omit property payloads.
- Management routes are separate from `/api/collect`, which is unchanged.
