+1
Revenue

Price change and legacy pricing impact

We raised prices, so what actually happened? Measures the effect of a price increase or new pricing page on cancellations, plan mix, conversion and revenue per account, using the never-migrated legacy cohort as a control, and sizes the revenue still sitting on old grandfathered prices and what those accounts would be worth at current list price.

  • +2
    What happened to churn and revenue after we raised prices?
  • +2
    How much MRR is still on our old pricing?
  • +2
    Did the price increase push people to downgrade or to cancel?

The playbook

Almost every price change is reported as "we lost X% of customers and revenue went up Y%", which is two numbers with no control group and no way to tell the price change apart from the churn that would have happened anyway. This playbook is price-cohort-centric: it dates the change from the price objects themselves, uses the never-migrated legacy accounts as the control, and keeps downgrades, cancellations and proration artefacts in separate columns.

Steps

  1. Find the price objects and date the change from the data. Stripe prices are immutable, so a price change always appears as a new price ID plus active: false on the superseded one. Call stripe.list_prices with type: "recurring" and active: true, then again with active: false, paging both fully, and sort by created to locate the change event. Join to stripe.list_products for names, falling back to product or price metadata when nickname is null, and report price IDs when no human name resolves. On Polar call polar.list_products and polar.get_metrics to see which product IDs and metric slugs exist; on Dodo call dodo_payments.list_products. If no clean price boundary exists, group by distinct unit_amount within a single currency and state that assumption out loud.

  2. Build the three populations. For each price of interest, stripe.list_subscriptions with price: <price_id> and status: "all", paged on starting_after until exhausted. This per-price filter is natively supported and is the backbone of the analysis. Classify each account as on-new-price, still-on-legacy-price, or migrated, and record the migration date from the subscription item change or from the first invoice carrying the new amount. On Polar use polar.list_subscriptions sorted by amount and by started_at; on Dodo use dodo_payments.list_subscriptions with product_id and read recurring_pre_tax_amount.

  3. Normalise every amount to monthly MRR per currency. Compute items[].price.unit_amount multiplied by quantity, divided by 100, divided by the number of months in the interval. Handle recurring.interval_count explicitly: a quarterly plan has interval_count: 3 and a six-monthly plan has 6. Keep currencies apart, because the same tier normally has one price ID per currency and grouping on unit_amount alone merges 29 USD with 29 EUR.

  4. Pull the cancelled population and compare against the legacy control. stripe.list_subscriptions has no canceled_at filter. Its description suggests combining status: "canceled" with a canceled_at range, but the schema accepts only created_gte and created_lte, so that parameter does not exist and passing it filters nothing. Use stripe.search with resource: "subscriptions" and a query such as status:"canceled" AND created>..., page the synthetic _next_page row, then filter on canceled_at and ended_at client side and read cancellation_details.feedback. Compute the migrated cohort's churn rate in the N months after migration minus its own pre-migration baseline, then subtract the same difference computed for the never-migrated legacy cohort over the identical calendar months. On Polar, polar.get_metrics with canceled_subscriptions_too_expensive, canceled_subscriptions_switched_service, churn_rate and average_revenue_per_user at interval: "month" gives the cleanest reason attribution available on any of the three. On Dodo, dodo_payments.list_subscriptions with cancel_at_next_billing_date: true is an early-warning signal the other two lack.

  5. Separate downgrades from cancellations and size the legacy exposure. Build the account and MRR distribution across tiers before and after the change, so a move from the old top tier to a cheaper new tier is counted as contraction rather than churn. Then sum monthly-normalised MRR on inactive or superseded prices as a share of total MRR, and recompute that same population at current list price: the difference is the uplift a migration project is worth. Pull stripe.list_invoices for migrated subscriptions around the migration date to identify proration lines, and strip them before attributing any revenue change. Optionally call hubspot.search_deals or hubspot.search_contacts to check whether sales-touched accounts were migrated on different terms from self-serve ones, and narrow the filter window until the result fits one page because the HubSpot tools do not return a usable next cursor. Where billing history is mirrored to a warehouse, postgres.get_schema then postgres.query makes the before-and-after comparison a single query and should be preferred.

  6. Report. One row per cohort with columns: cohort (new price, legacy, migrated), accounts at start, accounts at end, monthly-normalised MRR at start, MRR at end, ARPU at start, ARPU at end, churned accounts, churn rate, downgraded accounts, expanded accounts, "too expensive" share of cancellations, currency. Add a one-line legacy exposure summary: stranded legacy MRR, its share of total MRR, and its value at current list price. Then deliver one judgement, stated as the difference-in-differences result rather than raw churn: how many points of the migrated cohort's churn sit above both its own baseline and the legacy control, where in the price ladder they concentrate, and what happened to revenue per retained account.

Gotchas

  • Stripe prices are immutable, and grandfathered accounts hide behind active: false. Never expect an amount to change on an existing price. Equally, if you pull only active: true prices you exclude precisely the legacy population the analysis is about, so pull both states and join them.
  • Proration makes the change month unreadable. A mid-cycle price change produces proration credits and debits on the next invoice, so summing invoice totals in the change month shows a spike or dip that is pure accounting rather than demand. Strip proration lines before attributing revenue, and compute MRR from subscription items instead of from invoice totals.
  • canceled_at is not the churn date, and the canceled_at filter does not exist. Stripe states that for a period-end cancellation canceled_at reflects the time of the most recent update request, not the end of the subscription period, so use ended_at for when revenue actually stopped. And cancel_at_period_end means the account is still paying and still in current MRR: after a price increase this bucket swells first, so reporting it as churn double counts next month while ignoring it understates the damage.
  • Annual plans delay the entire signal by up to twelve months. An annual cohort does not feel a price increase until renewal, so a blended view makes the increase look harmless for a year. Report monthly and annual cohorts separately, and say how much of the base has not yet renewed.
  • Discounts mean the list price is not the realised price. An account on a new $79 price with a 40% coupon pays $47. Realised ARPU has to net out discounts read from the discounts array on subscriptions and invoices, or the measured increase is fiction. Note that Stripe exposes no coupon list tool here, so coupon identity must be read off subscriptions and invoices.
  • Your control group is not random. Grandfathered accounts are typically the oldest and most committed, so they would have churned less regardless. Present the difference-in-differences as the best available estimate and state the selection bias rather than claiming causality.
  • Pagination, test mode and the timezone of the cut. stripe.list_subscriptions still caps at 100 rows per page even with a price filter, so a popular tier runs to many pages and a partial pull truncates the biggest cohort first. Filter on livemode and drop non-null test_clock, since pricing is the area people test most. Put the before-and-after boundary in the same timezone as the deploy: Stripe filters are UTC epoch seconds, Polar accepts an IANA timezone, and a UTC cut against a US-evening launch misassigns a day of signups.

Sequel CLI

Install Sequel skills into your agent

One command connects your agent to Sequel and installs the Sequel skill, so it knows this playbook exists and reads it when a question matches. The CLI signs you in, provisions a scoped API key and writes the config for you.

Already have an MCP client?

https://api.sequel.sh/mcp

Point it at this URL and sign in when prompted, or send an API key from Settings as a Bearer token. Skills come with it; nothing else to install. Manual setup per client

  1. 1

    Install the Sequel CLI

    One line installs the latest CLI with whatever package manager you have.

    curl -fsSL https://sequel.sh/install | sh
  2. 2

    Sign in

    Authenticate in your browser and pick an organization.

    sequel login
  3. 3

    Install into your agent

    Writes the MCP config and installs the Sequel skill file for agents that support skills. Pick an agent from the list, or target one directly by its slug.

    sequel install
    • Claude Code
      sequel install claude-code
    • Claude
      sequel install claude
    • Cursor
      sequel install cursor
    • VS Code
      sequel install vscode
    • Windsurf
      sequel install windsurf
    • Zed
      sequel install zed
    • Codex
      sequel install codex
    • OpenClaw
      sequel install openclaw
    • Hermes
      sequel install hermes

FAQ

Frequently asked questions

How do I measure the impact of a SaaS price increase?
Build three populations: accounts on the new price, accounts still on the old price, and accounts migrated between them with a migration date. Then compare the migrated cohort's churn in the months after migration against its own pre-migration baseline, and subtract the same before-and-after difference for the never-migrated legacy cohort over the identical calendar months. Without that control the number is seasonality and the price change added together.
How much MRR is stranded on legacy pricing?
Sum the monthly-normalised MRR of every subscription whose price is inactive or superseded, and express it as a share of total MRR. Then recompute that population at current list price: the difference is the uplift a migration project would be worth. On Stripe this population frequently sits on prices with active set to false, so pulling only active prices misses exactly the accounts you are looking for.
Should I grandfather existing customers when raising prices?
The data can answer it for your own base rather than in the abstract. Grandfathered accounts are usually your oldest and most loyal, so they would have churned less anyway, which means a naive comparison overstates the benefit of grandfathering. Report the migrated-versus-legacy churn difference and state that selection bias explicitly rather than claiming causality.
Is a downgrade after a price increase the same as churn?
No. A tier downgrade is contraction revenue and is frequently the healthy outcome of a price increase, since the account is retained at a lower amount. Counting downgrades as churn overstates the damage and hides the plan-mix shift, which is usually the real result. Report accounts moved down a tier, accounts cancelled and accounts expanded as three separate lines.
Can an AI agent analyse a price change and legacy pricing exposure?
Yes. With Stripe, Polar or Dodo Payments connected the agent dates the change from price creation timestamps, builds the migrated, legacy and new-price cohorts, runs the difference against the legacy control, separates downgrades from cancellations, and sizes stranded legacy MRR at current list price. Sequel provides the Stripe, Polar, Dodo Payments and HubSpot connections over MCP and the price-change-impact playbook.

Put this playbook to work

Connect a source, ask the question, and the agent follows these steps. Free to start.