# Find Why Customers Churned in Stripe

Yes. An agent reads your cancelled Stripe subscriptions and writes churn.md, grouping churn by reason, plan and tenure and splitting failed payments from choices.

[Run this on Rush](https://cloud.getrush.ai/run/churn-analysis/stripe/).

## What the agent does

This job needs no repository. The agent works through your Stripe connection, Stripe's own remote MCP server at `mcp.stripe.com`, which Rush lists among its connectors for looking up customers, charges, subscriptions and balances. You connect it once with a sign-in, and the request names it in `connections`, so the run gets that connection and no other. The prompt says read only, and the agent uses only lookups; nothing in the account is created, updated or cancelled.

It starts by listing subscriptions whose status is `canceled` and whose `canceled_at` falls in the last 90 days. For each one it records the fields that explain a cancellation: `cancellation_details.reason` (Stripe sets `cancellation_requested`, `payment_failed` or `payment_disputed`), `cancellation_details.feedback` when the customer picked an answer in the customer portal (`too_expensive`, `missing_features`, `switched_service`, `unused` and the others), and the free-text `comment`. It adds the price and product on the subscription's items, the `start_date`, and the status of the latest invoice and its payment, so tenure can be counted in whole months from start to cancellation.

The split between voluntary and involuntary churn is the part a dashboard filter does not give you. A subscription Stripe ended after its retries ran out is involuntary even when no reason was entered, so the agent checks the last invoice's payment attempts as well as the `reason` field. A customer who cancelled while their last invoice was unpaid is listed in its own group, because that is often a card problem described as a choice. Monthly recurring revenue for each lost subscription is computed from its price, quantity and billing interval, so an annual plan counts as one twelfth per month, and a subscription with a coupon is noted rather than counted at list price.

Then it writes `churn.md`. Every number in it comes from a lookup the agent made in this run, and the transcript keeps each call with what Stripe returned. When a field is missing (no feedback collected, a deleted price), the report says how many cancellations lack it instead of guessing.

## Start it with one API call

`POST https://api.prix.dev/o/{org}/sessions`

```json
{
  "agent": "claude",
  "input": "Use the Stripe connection to list subscriptions cancelled in the last 90 days with their cancellation_details (reason, feedback, comment), price and product, start date, canceled_at and the status of their last invoice and payment. Write churn.md that groups cancellations by stated reason, by plan and by months of tenure, separates voluntary cancellation from failed payment (treat a subscription ended after failed payment retries as involuntary even if no reason is set), computes lost MRR per subscription from price, quantity and interval, and lists the 5 largest lost accounts by MRR. Say how many cancellations have no feedback. Read only: do not create, update or cancel anything in Stripe.",
  "connections": [
    "stripe"
  ]
}
```

1. **Get an API key.** In the console, open Settings, then API keys, and create a key for your org. Send it in the Authorization header as a Bearer token.
2. **Send the request.** POST the body in request.json to https://api.prix.dev/o/{org}/sessions, with your repository as the resources URL. The run starts in its own cloud sandbox.
3. **Read what came back.** Follow the run on its live stream or wait for the run.completed webhook, then read the result and its trajectory.

## What comes back

What comes back is `churn.md`, an artifact on the session with a download link that works for an hour; reading the session again returns a fresh one. It opens with the 90-day totals: cancellations, lost MRR, and how much of each was voluntary and how much was failed payment. Then come three tables: churn by stated reason and feedback, by plan, and by tenure band, each with a count and lost MRR. The last section lists the five largest lost accounts with their customer id, plan, tenure, reason and last payment status, so someone can decide who is worth a call.

The agent's closing summary reads back as the session's `result`, and the transcript beside it holds every Stripe lookup the agent made with the response it got. A reviewer who doubts a figure opens the step behind it rather than re-running the analysis.

Before acting on it, check two things. The voluntary and involuntary split depends on how your account handles failed payments (Smart Retries, the number of attempts, whether a subscription is cancelled or left unpaid at the end), so confirm the agent's rule matches your settings. And feedback is only as good as what the cancellation flow collects: if most rows have none, the useful next step is to turn on the reasons in the customer portal, not to read more into the comments.

## Run it on every first of the month

Churn is read by month, so have a scheduler on your server send this request on the first of each month, with an `Idempotency-Key` built from the month so a retried call does not start a second run. Each run reads the account as it is that day, so the reports stack into a month-by-month record.

Set a webhook URL on the API key and Rush POSTs `run.completed` when the run finishes, with `session_url`. Your server reads the session, takes the `churn.md` link from its artifacts and posts it to the channel where the team reviews revenue.

## Why run it on Rush?

A churn analysis on a laptop means a restricted API key in a script, an export in a spreadsheet and a copy of customer data that outlives the question. On Rush the agent reaches Stripe through a connection you approved once, the run gets only the connections it names, and the data stays in the run's own sandbox rather than in a local file someone forgets to delete.

The result is a report with its proof: every lookup and response is in the run's record, so finance and product can check a number back to Stripe without trusting the person who ran it. The same request can run for several Stripe accounts at once, one run each, up to the key's concurrent run limit.

## Frequently asked questions

### Can the agent change anything in my Stripe account?

The prompt makes the run read only and the agent calls only list and retrieve lookups, which the transcript lets you confirm. The connection itself can do what you approved at sign-in, so for a hard limit connect Stripe's server with `"auth": {"mode": "bearer"}` and a restricted key that has read permissions only.

### How does the run get access to Stripe?

Connect Stripe's MCP server once, with `POST /me/connections` and `"auth": {"mode": "oauth"}`, and open the `authorize_url` it returns while signed in to the console. Then name it in the request's `connections` by the name you gave it, `stripe` here. A run only gets the connections it names.

### What if we never collected cancellation reasons?

Then `cancellation_details.feedback` is empty and the report says so with a count. The voluntary and failed-payment split still works, because it comes from Stripe's own `reason` and the invoice's payment attempts, and the plan and tenure tables need no feedback at all.

### Does it work with Stripe test mode?

Yes. The run reads whichever account and mode the connection was signed in to, so you can try the request on test data before connecting the live account.
