URL: /plays/usage-upsell · Id: `play:usage-upsell`

# Usage upsell

> Offers the next Offer when a Customer’s use paces past its Limit, with today’s prorated charge; the Customer upgrades in one press.

[Offers](https://startupbuilder.dev/business/nouns/offer) the next Offer when a [Customer](https://startupbuilder.dev/business/nouns/customer)’s use paces past its [Limit](https://startupbuilder.dev/business/nouns/limit), with today’s prorated charge; the Customer upgrades in one press.

```text title="Set it up · Ask"
Be our CSM: when a Customer is on pace to pass their plan’s limit, show them the next plan with today’s charge, or tell them staying is cheaper when it is. Over 150 units, bring in our AE. Show me what’s missing; don’t send.
```

```text title="Run one · Ask"
Hand our latest Customer near a limit to the CSM agent and show me what it chose and why. Don’t send.
```

```ts title="Run one · SDK"
import { $ } from 'startup-builder'

const { events: [last] } = await $.Event.list({ where: 'name in ("Entitlement.checked", "Offer.subscribed", "Invoice.billed", "Message.received", "Renewal.opened", "Subscription.switched", "Page.viewed")', limit: 1 })   // { play } compiled to this Play’s Trigger Events
const { subject } = await $.Trigger.fire({ like: last.$id }, { mode: 'try' })   // the same Event again, in try mode · Not live yet, gaps #7
const { decisions: [decision] } = await $.Decision.list({ where: `on = "${subject}"` })   // what it chose, and why · Not live yet, gaps #7
const { actions } = await $.Action.list({ where: `on = "${subject}"` })   // what it did, with its checks · Not live yet, gaps #7
return { decision, actions }
```

```json title="Run one · Response · sample"
{
  "decision": {
    "$id": "https://api.sb/leaseline.app/decisions/decision_340",
    "$type": "Decision",
    "choice": "offer-next-plan",
    "reason": "118 units on Pro: $68 of overage this period is more than the $50 step to Team",
    "stakes": "low",
    "options": [
      "offer-next-plan",
      "show-the-saving",
      "at-renewal",
      "to-the-ae",
      "not-now"
    ],
    "by": "worker_5",
    "on": "customer_12",
    "expected": "Subscription.switched to Team within 30d",
    "reopensWhen": "no Subscription.switched for customer_12 within 30d"
  },
  "actions": [
    {
      "$id": "https://api.sb/leaseline.app/actions/action_1",
      "$type": "Action",
      "status": "done",
      "mode": "try",
      "by": "worker_5",
      "verb": "Entitlement.check",
      "checks": []
    },
    {
      "$id": "https://api.sb/leaseline.app/actions/action_2",
      "$type": "Action",
      "status": "pending",
      "reason": "awaitingTime",
      "mode": "try",
      "by": "worker_5",
      "verb": "Message.schedule",
      "checks": [
        {
          "check": "owned-by-a-person",
          "status": "pass"
        },
        {
          "check": "keep-first",
          "status": "pass"
        },
        {
          "check": "customer-ask-cap",
          "status": "pass"
        },
        {
          "check": "reconcile",
          "status": "pass"
        }
      ]
    }
  ]
}
```

`action_2` is the note to Kestrel’s billing owner, held 2 minutes; the in-product notice shows at their next sign-in.

`$2,392 limit upgrade ARR against $598 before · before-after, not proof · n 9 · path → arr`

| Try another: the Customer | Choice | Reason |
| --- | --- | --- |
| Kestrel Rentals: 118 units on Pro, $68 of overage this period | `offer-next-plan` | overage passes the $50 step to Team |
| Maple Court: 56 units on Pro, $6 of overage | `show-the-saving` | staying on Pro costs less than Team |
| Harbor Row: 140 units, at risk since Oct 2 | `not-now` | `keep-first`: their CSM sees them first |

| Worth | Your time | Spend | First signal | Use it when | Instead |
| --- | --- | --- | --- | --- | --- |
| ≈ +$650 ARR a month at 6 | 20 min week 1, then 5 a week | $0.04 per note, ~$0.25 a month at 3 | Day 4: first upgrade paid | 10+ Customers on an Offer with a Limit, 3+ a month pacing past it | Under 10 Customers: `instant-response` · no Offer has a Limit: add one (<Op verb="Offer.publish" />) |

**Where it sits:** Stranger → Lead → Trialing → **Activated** → At risk → Churned → Won back · Limit upgrade ARR → Upgrades ↑ → ARR added each week → ARR

Worth’s formula: about +18 upgrades per 100 pacing Customers (Other Startups’ results; no lift of your own yet) × 6 a month × $598 per Pro-to-Team upgrade ≈ +$646. Under First signal: At 6 a month, 90 days can tell only +36 per 100 or more, so your file starts at `holdout: 0` and reads before-after.

`sample · api.sb/leaseline.app · 41 Customers at 2026-10-19 · on Entitlement.checked · first note 3m 10s · 8 keys Not live yet`

## Start it this week
```bash
npx startup-builder add play usage-upsell   # 1 · writes plays/usage-upsell.mdx (Not live yet, gaps #5)

# 2 · dry run; below, its 422 on 2026-08-24
curl -X POST "https://api.sb/leaseline.app/campaigns?mode=try" \
  -H "Authorization: Bearer $STARTUP_BUILDER_API_KEY" \
  -H "Content-Type: application/json" \
  -d '{"play":"usage-upsell"}'

# 3 · keep it, at You approve each one
curl -X POST https://api.sb/leaseline.app/campaigns \
  -H "Authorization: Bearer $STARTUP_BUILDER_API_KEY" \
  -H "Content-Type: application/json" \
  -d '{"play":"usage-upsell"}'

# on
curl -X PATCH https://api.sb/leaseline.app/campaigns/usage-upsell \
  -H "Authorization: Bearer $STARTUP_BUILDER_API_KEY" \
  -H "Content-Type: application/json" \
  -d '{"endsAt":null}'

# off: no ask sends; the bound answer keeps showing
curl -X PATCH https://api.sb/leaseline.app/campaigns/usage-upsell \
  -H "Authorization: Bearer $STARTUP_BUILDER_API_KEY" \
  -H "Content-Type: application/json" \
  -d '{"endsAt":"now"}'
```


```json title="The dry run’s 422 · 2026-08-24 · sample"
{
  "type": "https://startupbuilder.dev/errors/refused",
  "title": "Refused",
  "status": 422,
  "detail": "usage-upsell can’t start yet: 1 check fails and 2 warn.",
  "checks": [
    {
      "check": "use-when",
      "status": "pass",
      "observed": "41 Customers, 6 a month pacing past a Limit",
      "doc": "/plays/usage-upsell#check-use-when",
      "then": "Metric.query"
    },
    {
      "check": "limit-on-offer",
      "status": "pass",
      "observed": "Leaseline Pro: 50 units",
      "doc": "/plays/usage-upsell#check-limit-on-offer",
      "then": "Offer.publish"
    },
    {
      "check": "billing-connected",
      "status": "fail",
      "doc": "/plays/usage-upsell#check-billing-connected",
      "fix": "Connect Stripe so every charge and Meter reading is live.",
      "then": "Integration.connect"
    },
    {
      "check": "baseline",
      "status": "warn",
      "observed": "1 upgrade of 9 pacing Customers in 90 days",
      "doc": "/plays/usage-upsell#check-baseline",
      "fix": "n 9: it reads before-after, not proof, until volume allows a held-back half.",
      "then": "Metric.query"
    },
    {
      "check": "role-unstaffed",
      "status": "warn",
      "roles": [
        "success-manager",
        "billing-specialist"
      ],
      "owned": 4,
      "doc": "/plays/usage-upsell#check-role-unstaffed",
      "fix": "Staff the CSM, or leave it: 4 Customers Priya owns get drafts in her queue.",
      "then": "Role.staff"
    }
  ]
}
```

| Check | Fix (one call) | Your time | Wait |
| --- | --- | --- | --- |
| <a id="check-use-when"></a>`use-when` · <a id="check-limit-on-offer"></a>`limit-on-offer` · pass: 41 Customers, 6 a month pacing; Pro’s 50 units (gaps #8) | <Op verb="Metric.query" /> on `limit-upgrade-arr`; <Op verb="Offer.publish" /> adds a Limit | 1 min | none |
| <a id="check-billing-connected"></a>`billing-connected` · fail | <Op verb="Integration.connect" /> with `system` recurring-billing (Stripe) | 5 min | the first Meter sync, about 10 min |
| <a id="check-baseline"></a>`baseline` · warn: 1 of 9 in 90 days | <Op verb="Metric.query" />; it fills Before in Your number | 0 min | none |
| <a id="check-role-unstaffed"></a>`role-unstaffed` · warn: 4 of your Customers have a person owner, Priya | <Op verb="Role.staff" />, or leave it: CSM routes come to you, and Priya’s 4 get drafts in her queue | 0 min | none |

`Your number · Before $598 · Now $2,392 limit upgrade ARR · Target $3,000 by Dec 31 · before-after, not proof · n 9 matured`

Each note costs $0.04 from your Domain (Worker cost), and Meter reads and <Op verb="Entitlement.check" /> cost nothing. That comes to about $0.25 a month at 3 pacing Customers and $0.50 at 6.

## When it starts
| When | Trigger id | Starts on | Window | How it gets in |
| --- | --- | --- | --- | --- |
| A Customer’s use is on pace to pass a Limit before it resets | `limit-pace` | `threshold` on `Entitlement.checked` where used / bound at period pace > 1 | 3d; once per Entitlement per period | your app calls <Op verb="Entitlement.check" />, or <Op verb="Event.capture" /> |
| An invoice bills overage past the step, two periods running | `overage-past-step` | `threshold` on `Invoice.billed` where overage > step, 2 periods running | 7d | <Op verb="Integration.connect" /> (Stripe) |
| An annual plan at 80 %+ of its Limit nears renewal | `usage-renewal-window` | `at` on `Renewal.opened` where 60 days before renewsAt, at 80 % or more of its Limit | 30d | <Op verb="Integration.connect" /> (Stripe) |
| + 6 more for limit approach and reach, the first month, asks for more, reversals and pricing visits (`?lanes=all`) | | | | |

```json title="One signed delivery · StartupBuilder-Signature"
{
  "$id": "https://api.sb/leaseline.app/events/event_1204",
  "$type": "Event",
  "name": "Entitlement.checked",
  "at": "2026-10-19T09:41:12Z",
  "seq": 1204,
  "data": {
    "customer": "customer_12",
    "limit": "units",
    "used": 118,
    "bound": 50,
    "atBound": "overage",
    "resets": "2026-11-01"
  }
}
```

```bash title="Reproduce it in test"
npx startup-builder trigger Entitlement.checked --like event_1204   # replays the delivery
npx startup-builder clock advance --by 3d   # then runs the day-3 note step
npx startup-builder listen Entitlement.checked --forward-to http://localhost:3000/hook
```

## Who runs it
**CSM · your agent** (`role:success-manager`, `worker_5`) holds the Key Result. It hands to each Customer’s owner: **AE · Priya Shah (person)** for the 4 she owns and for every Quote over 150 units, and **you** (Dana Okafor, person) for the rest, because the CSM and Billing Specialist Roles have no person. Your number links to Did it work.

| Option | Rung today | Evidence (passed of n) | Moves up at | Drops back when | You can |
| --- | --- | --- | --- | --- | --- |
| Offer the next plan · Show the saving | It does it, you can stop it | 6 of 6 · 3 of 3 | met on day 12 (the first 5 notes) | any note fails a Check, or the `misquote` Stop fires → You approve each one | <Op verb="Action.cancel" /> inside the hold |
| Offer it at renewal | You approve each one | 1 of 1 | at 10 passed | — | <Op verb="Approval.approve" /> · <Op verb="Approval.refuse" /> |
| To the AE · Not now | It does it | 1 of 1 · 2 of 2 | — | a held Customer is asked anyway → You approve each one | <Op verb="Action.redirect" /> |
| Change the upgrade rule | You approve each one | 0 merged | never | — | <Op verb="Change.merge" /> · <Op verb="Change.revert" /> |
| Never: a discount, a deadline that isn’t real, a switch down hidden | To a person, every time | 1 asked, 1 handed over | never | — | <Op verb="Approval.approve" /> on the AE’s Quote |

**What it reads · What it may call.** It reads the Meter, the Entitlement and the live Price, and never a pricing-Page visit or a health score in what it writes. <Op verb="Subscription.switch" /> is the Customer’s own press, never the agent’s call. Its tools are <Op verb="Entitlement.check" />, <Op verb="Message.schedule" />, <Op verb="Conversation.answer" />, <Op verb="Conversation.escalate" />, <Op verb="Quote.send" />, <Op verb="Task.assign" />, and any other call becomes an Approval under the Role’s Mandate.

```md title="The brief (folded): compiled Agent-register Markdown · play:usage-upsell@1 · each line names its record key"
You hold the CSM Key Result: $3,000 limit upgrade ARR by 2026-12-31.             ← worker, metric
On each Trigger, choose one option and say why; a held Customer gets Not now.    ← triggers, chooses, keep-first
Every number comes from the headroom card, with its as-of time.                  ← copyRules 1
Suggest the upgrade only when it costs less than the overage it replaces.        ← copyRules 3, your file line 4
The switch down sits on the same billing Page, one press away.                   ← copyRules 8
```

**The slow loop.** The weekly Decisions kept the $50 step as the line, and no Change was merged in the window, so there is no replay or read-back to print.

## What it can do next
| Option | Picked when | Decided by | Runs | Hands to | If it’s just you |
| --- | --- | --- | --- | --- | --- |
| Offer the next plan `offer-next-plan` | overage passes the step to the next Offer | Your rule (line 4) | `usage-upgrade-note` → on press `change-plan` → `usage-upgrade-confirm` | the Customer’s press | the same |
| Show the saving `show-the-saving` | staying costs less than the next Offer | Your rule (line 4) | `overage-savings-note` | nobody | the same |
| Offer it at renewal `at-renewal` | an annual term renews within 60 days | Agent’s call, with its reason | `usage-upgrade-note` at renewal | you approve it · Take this one | you |
| To the AE `to-the-ae` | over 150 units, or asks for terms | Agent’s call, with its reason | <Op verb="Conversation.escalate" /> → <Op verb="Quote.send" /> | <Op verb="Task.assign" /> → AE · Priya Shah, due 1 business day · Take this one | you |
| Not now `not-now` | another Play holds them, or `keep-first` applies | Your rule (rule j) | nothing; names the holder | their CSM, so you · Take this one | you |

| When they write back | Fixture reply | What happens | Who answers | By when | Call |
| --- | --- | --- | --- | --- | --- |
| Interested | “Move us to Team.” | the note’s one-press link; nothing is charged until they press | the Customer | — | <Op verb="Subscription.switch" /> (theirs) |
| Send me pricing | “What would Team cost us?” | the headroom card again, with the live Price and today’s charge | CSM · your agent | 5 min | <Op verb="Conversation.answer" /> |
| Not now | “Not this month.” | the ask closes, and `customer-ask-cap` frees the Customer | nobody | at once | <Op verb="Decision.list" /> |
| Wrong person | “Billing is Lee’s call.” | asks them to forward it; it never writes to an address from their text | CSM · your agent | 1 business day | <Op verb="Conversation.answer" /> |
| Out of office | an auto-reply until Oct 28 | the note waits for the return date | nobody | the return date | <Op verb="Message.schedule" /> |
| Question it can’t answer | “Can we keep Pro’s price on Team?” | To a person: a price is never the agent’s call | AE · Priya Shah, or you | 1 business day | <Op verb="Conversation.escalate" /> |
| Stop | “Stop emailing us about plans.” | notes stop; the in-product notice keeps running | nobody | at once | <Op verb="Contact.suppress" /> |

## When it hands to you
On this Play, hand-offs go to the Customer’s owner, the CSM’s seat. One Brief per Role, as captured (`brief` is Not live yet, gaps #7).

```json title="What lands in each queue · sample"
[
  {
    "$id": "https://api.sb/leaseline.app/tasks/task_402",
    "$type": "Task",
    "assignee": "worker_3",
    "due": "2026-10-08T11:40:00Z",
    "on": "customer_7",
    "brief": {
      "who": "Birchway Properties, on the $2,400 plan",
      "said": "We’re adding 2 owners: about 420 units by January.",
      "chose": "to-the-ae",
      "why": "over 150 units; a Quote is the AE’s",
      "never": [
        "a discount",
        "a deadline that isn’t real"
      ]
    }
  },
  {
    "$id": "https://api.sb/leaseline.app/tasks/task_388",
    "$type": "Task",
    "assignee": "worker_1",
    "due": "2026-10-03T17:00:00Z",
    "on": "customer_19",
    "brief": {
      "who": "Harbor Row",
      "chose": "not-now",
      "why": "at risk since Oct 2: 3 support threads on late notices",
      "held": "keep-first"
    }
  }
]
```

The owner answers with <Op verb="Task.claim" /> (Take this one), <Op verb="Task.return" /> with a reason, or <Op verb="Action.redirect" />, each a one-call window. Once a person claims, the agent’s pending asks to that Customer cancel with `claimedByPerson` (Not live yet, gaps #7), and every reply goes to the owner. A Task nobody claims by its due time goes up a Role through <Op verb="Task.escalate" />, and `person-promise` fires.

**On time:** 3 of 3 claimed within their due time, median 40 min, n 3, ■□□□ Too early to tell beside Other Startups’ 94 % on time. **Your queue:** in week 1, 6 approvals and 1 hand-off; after the promotion, about 1 hand-off a week.

**The fixed line:** <Op verb="Deal.assign" /> sets `owner` to the receiving person, the agent is never a Deal’s owner, and Meetings land on the owner’s calendar under the owner’s name.

## When it won’t send
| Check (refuses one send) | Refuses | Fix | Then |
| --- | --- | --- | --- |
| <a id="check-owned-by-a-person"></a>`owned-by-a-person` (rule j, gaps #8) | a note to a Customer whose open Deal a person owns, or whose Conversation a person claimed | Leave it to the person who owns it: the draft waits in their queue. | <Op verb="Task.assign" /> |
| <a id="check-keep-first"></a>`keep-first` (rule j, gaps #8; the record’s At-risk hold) | an upgrade ask to a Customer who is at risk, past due or has an unsubscribe pending | Let their CSM reach them first, then ask again once they are healthy. | <Op verb="Task.next" /> |
| <a id="check-customer-ask-cap"></a>`customer-ask-cap` (gaps #3) | a second open ask to one Customer in a window, from any Play | `annual-prepay` holds them until Nov 3. | <Op verb="Decision.list" /> |
| <a id="check-reconcile"></a>`reconcile` (the record’s Reconcile stop; Not live yet, gaps #8) | a Customer whose Subscription and Stripe disagree | Clear the Stripe mismatch first. | <Op verb="Task.next" /> |

| Stop (pauses the Play) | Pauses when | Then |
| --- | --- | --- |
| <a id="stop-misquote"></a>`misquote` | a Price, charge, Limit or reset time differs from the live Price or the Entitlement check | You approve each one |
| <a id="stop-meter-gap"></a>`meter-gap` | headroom differs from the Meter or the Stripe usage by over 1 % | every note waits for the Billing Specialist, so you |
| <a id="stop-downgrade"></a>`downgrade` | over 10 % of upgrades switch down or end within 90 days | the pace lane pauses; the upgrade map is flagged to you |
| <a id="stop-logo-churn"></a>`logo-churn` | treated Customers’ 90-day logo churn passes the comparison’s by 2 points | the pace lane pauses until the next readout |
| <a id="stop-surprise-charge"></a>`surprise-charge` | 2 refund requests or disputes on upgrade charges in 30 days | <Op verb="Incident.open" /> |
| <a id="stop-hostile-reply"></a>`hostile-reply` | 3 hostile replies about upgrades in 30 days | note lanes pause |
| <a id="stop-complaint"></a>`complaint` | complaints near 0.1 % of notes | notes pause on every Mailbox |
| <a id="stop-unsubscribe-floor"></a>`unsubscribe-floor` | unsubscribes over 1 % of notes in 30 days | note lanes pause; the in-product notice keeps running |
| <a id="stop-holdout-integrity"></a>`holdout-integrity` | a notice reaches a held-back Customer; over 5 % voids the cohort’s readout | <Op verb="Task.assign" /> |
| <a id="stop-model-spend"></a>`model-spend` | month-to-date model spend reaches its Limit | every ask about more use goes to a person |
| <a id="stop-kill-switch"></a>`kill-switch` | you press it | every ask stops; the bound answer keeps showing |

The captured refusal, then the Stop that fired as a `Campaign.paused` reason (Not live yet, gaps #2); the rewrite that sent is the Maple Court thread in Example.

```json title="The captured refusal · sample"
{
  "type": "https://startupbuilder.dev/errors/refused",
  "title": "Refused",
  "status": 422,
  "detail": "The note to Harbor Row did not send: they are at risk.",
  "checks": [
    {
      "check": "keep-first",
      "status": "fail",
      "doc": "/plays/usage-upsell#check-keep-first",
      "fix": "Let their CSM reach them first, then ask again once they are healthy.",
      "then": "Task.next"
    }
  ]
}
```


```json title="The Stops that fired, as Campaign.paused reasons · sample"
[
  {
    "reason": {
      "limit": "meter-gap",
      "observed": "3.4 %",
      "bound": "1 %",
      "window": "1 sync"
    },
    "doc": "/plays/usage-upsell#stop-meter-gap",
    "task": "task_351"
  }
]
```

## Did it work
At 6 pacing Customers a month, no held-back half reads within 90 days, so the basis is before-after (rule o): the columns read Now and Before, and nothing prints as Added. Each figure’s call is <Op verb="Metric.query" /> by node, and every node above the leaf is Not live yet (gaps #4).

`$2,392 limit upgrade ARR against $598 before · before-after, not proof · n 9 · path → arr`

| Node | Now (56 days) | Before (the 90 days, per 56) | Added | Confidence | Basis |
| --- | --- | --- | --- | --- | --- |
| `limit-upgrade-arr` | $2,392: 4 of 9 matured upgraded (44 per 100) · 2 still maturing · Booked $2,990: credited, not all caused by this Play | $598: 1 of 9 (11 per 100) | — · Worth about +$650 a month | — | before-after, not proof · Other Startups’ results +18 per 100, ■■■□ Likely, n 2,240 |
| `expansion-arr` ↑ | $3,588 | $1,196 | — | — | before-after; includes 1 seat resize this Play never asked for |
| `net-new-arr` · `arr` | carried from `expansion-arr` | carried | — | — | carried |

Before against after reads one window against the 90 days before it, scaled to 56 days; it is not proof, and Other Startups’ results print beside it.

**How it moves it:** each note names Team’s live Price, today’s prorated charge ($20.00 for Kestrel) and the overage it replaces.

**Guardrails:** before against after: `logo-churn`, 0 of 5 upgraded Customers gone against a trailing 2.1 % a month, and `contraction-arr`, $0 from switches down against $347 before; 3 upgrades are still inside their 90 days, still deciding.

```json title="The Campaign card · sample"
{
  "$id": "https://api.sb/leaseline.app/campaigns/usage-upsell",
  "$type": "Campaign",
  "play": "play:usage-upsell@1",
  "path": [
    "limit-upgrade-arr",
    "expansion-arr",
    "net-new-arr",
    "arr"
  ],
  "experiment": {
    "metric": "limit-upgrade-arr",
    "unit": "usd",
    "heldBack": 0,
    "decision": null
  },
  "readout": {
    "value": null,
    "n": 9,
    "maturing": 2,
    "interval": null,
    "readability": "inadmissible",
    "basis": "before-after",
    "pooled": true
  },
  "ledger": {
    "spend": 1,
    "workerCost": 1,
    "hours": 0.9,
    "pipelineArr": 2400,
    "wonArr": 2990,
    "projectedArr": 646,
    "paybackDays": 1
  }
}
```

Outside benchmark: 0.75 % of Subscriptions move to a higher Offer in their first month (10.5M purchases at 4,029 companies, a subscription-analytics firm’s billing data, 2026-09-30).

**Before it · After it.** Before it: `instant-response` and `product-led` feed it the trials that convert (`Offer.subscribed`). After it: `annual-prepay` hears `steady-usage` after 3 periods on Team, and `customer-referrals` hears the Customers it upgraded; `keep-first` holds both while a Customer is at risk.

## Example
Leaseline ran it from 2026-08-24 to 2026-10-19 (56 days): 13 Decisions on 11 Customers, 9 of them matured past their 30-day window. Read every captured thread at `?threads=all`; three follow the tables.

| What happened, by option | Decisions | Upgraded | Booked (credited, not all caused by this Play) |
| --- | --- | --- | --- |
| Offer the next plan | 6 | 5 to Team, 2 still maturing | $2,990 |
| Show the saving · Offer it at renewal · To the AE · Not now | 3 · 1 · 1 · 2 | 0 · 0 (still deciding) · 1 Quote open · 0 | $2,400 pipeline |
| At 3 a month (the Use it when volume) | about 6 in 56 days | before-after, not proof | Other Startups’ results: +18 per 100, ■■■□ Likely, n 2,240 |

| Day | Event | What happened | Your time |
| --- | --- | --- | --- |
| 0 | `event_119` Campaign.created | the three steps; dry run fails `billing-connected` | 12 min |
| 0 | `event_120` Integration.connected | Stripe connected; the first Meter sync took 9 min | 5 min |
| 4 | `event_207` Subscription.switched | the first upgrade paid: Kestrel, Pro to Team | — |
| 12 | `event_388` Role.updated | promoted: notes send after a 2-minute hold | — |
| 17 | `event_455` Campaign.paused | `meter-gap`, cleared in 3 h by you | 10 min |
| 39 | `event_761` Action.refused | `keep-first` held Harbor Row; you took it with <Op verb="Task.claim" /> | 15 min |
| 1–56 | `event_142` Approval.created, first of 6 | you approved 6 notes; weekly reviews, from the <Op verb="Task.next" /> log | 12 min · **total 54 min measured, 1 h asked** |

> **Leaseline → Kestrel’s billing owner, Oct 19, 09:44** · You’re at 118 units on Pro, 68 past its 50-unit Limit, so this period’s overage is $68.¹ Team is $79 a month for up to 150 units: $20.00 today, then $79 a month, and your overage stops.² Switch with one press; switching back sits on the same page.³ *Signed with the Role’s disclosure line.*⁴
>
> **Kestrel, Oct 19, 10:02** · pressed Switch to Team. `Subscription.switched`, +$598 ARR.

```diff title="Maple Court, 2026-09-21 · the draft claim refused, then the rewrite that sent"
- Upgrade this week and we’ll lock Team at $59 a month.
+ At 56 units, staying on Pro costs $6 in overage this period, less than the $50 step to Team, so staying is cheaper.
```

> **Birchway Properties, 2026-10-08 10:40** · “We’re adding 2 owners: about 420 units by January.” · **Leaseline, 10:42** · *Thanks. Priya, your account lead, will reply by 11:40 with a Quote.*⁵ The Brief went to Priya (`task_402`).

¹ Every number comes from the headroom card, as of 09:41. ² The live Price and today’s prorated charge. ³ The switch down, one press away. ⁴ The fixed signature line, compiled from the Role’s disclosure Setting. ⁵ The routed line, compiled from the Role’s Settings.

## Your settings
```mdx title="plays/usage-upsell.mdx"
---
holdout: 0        # 6 a month is too few to hold half back; it reads before-after
---
Offer Team when overage passes the $50 step; under that, show them staying is cheaper. Over 150 units, bring in the AE.
```

```text
$ npx startup-builder show plays/usage-upsell
holdout 0 · step $50 · AE over 150 units     your file  plays/usage-upsell.mdx:2,4 (Ask 2026-08-24)
family expansion · worker role:success-manager, owns never · chooses 5 (3 rule, 2 read) · sequences usage-upgrade-note, overage-savings-note, usage-upgrade-confirm · checks owned-by-a-person, keep-first, customer-ask-cap, reconcile · stops 11 · placements gmail, outlook + in-product notice    catalog  play:usage-upsell@1 (Not live yet, gaps #5)
```

### Every key from the catalog
| Key | Id | What it is |
| --- | --- | --- |
| Tactic | <a id="tactic-usage-headroom-watch"></a>`usage-headroom-watch` | Usage headroom watch builds one headroom card per Customer, Entitlement and period, with pace, the projected crossing and today’s charge. Starts at Sends nothing. |
| Tactic | <a id="tactic-next-offer-suggest"></a>`next-offer-suggest` | Next offer suggestion shows the upgrade Offer, its live Price and today’s prorated charge before the bound, in product and in one note. Starts at You approve each one. |
| Tactic | <a id="tactic-limit-bound-answer"></a>`limit-bound-answer` | Limit bound answer follows the Offer’s own behavior at the bound and shows the upgrade Offer with today’s charge, one press away. Starts at You approve the first wave. |
| Tactic | <a id="tactic-usage-offer-switch"></a>`usage-offer-switch` | Usage offer switch moves a Subscription to the upgrade Offer through Stripe Billing with proration, only on the Customer’s own Action. Starts at Sends nothing. |
| Tactic | <a id="tactic-usage-upsell-readout"></a>`usage-upsell-readout` | Usage upsell readout counts upgrades per 100 crossing cards over 30 days, reads the holdout at 90 days, and reports the savings lane apart. Starts at You approve the first wave. |
| Tactic | <a id="tactic-in-response-lifecycle"></a>`in-response-lifecycle` | In-response lifecycle carries Limit headroom, fixes, honest over-Limit Offers, signed receipts and deprecations inside every reply to an agent Customer. Starts at Sends nothing. |
| Tactic | <a id="tactic-churn-alert"></a>`churn-alert` | Churn alert: Turns a paying Customer’s visit to pricing, billing or cancel Pages into a Success Manager Task with a health brief, and sends no Message. Starts at Sends nothing. |
| Tactic | <a id="tactic-send-gate-check"></a>`send-gate-check` | Send gate check: Checks Suppression, a live reason, the Market, the basis stamp, the Person-month grant, the facts and the labels before any cold Message exists. Starts at Sends nothing. |
| Tactic | <a id="tactic-reply-matrix"></a>`reply-matrix` | Reply matrix: Labels every outreach reply with one of 11 labels, maps it to one of seven reply kinds, and runs that kind’s move by its deadline. Starts at You approve each one. |
| Trigger | <a id="trigger-limit-pace"></a>`limit-pace` | Limit pace: `threshold` on `Entitlement.checked`, window 3d. |
| Trigger | <a id="trigger-limit-approach"></a>`limit-approach` | Limit approach: `threshold` on `Entitlement.checked`, window 7d. |
| Trigger | <a id="trigger-limit-reached"></a>`limit-reached` | Limit reached: `threshold` on `Entitlement.checked`, window 1d. |
| Trigger | <a id="trigger-first-month-climb"></a>`first-month-climb` | First-month climb: `event` on `Offer.subscribed`, window 30d. |
| Trigger | <a id="trigger-overage-past-step"></a>`overage-past-step` | Overage past the step: `threshold` on `Invoice.billed`, window 7d. |
| Trigger | <a id="trigger-more-use-ask"></a>`more-use-ask` | More use ask: `event` on `Message.received`, window 3d. |
| Trigger | <a id="trigger-usage-renewal-window"></a>`usage-renewal-window` | Usage renewal window: `at` on `Renewal.opened`, window 30d. |
| Trigger | <a id="trigger-usage-upgrade-reversed"></a>`usage-upgrade-reversed` | Upgrade reversed: `event` on `Subscription.switched`, window 90d. |
| Trigger | <a id="trigger-customer-pricing-visit"></a>`customer-pricing-visit` | Customer pricing visit: `event` on `Page.viewed`, window 7d. |
| Sequence | <a id="sequence-usage-upgrade-note"></a>`usage-upgrade-note` | Usage upgrade note: 4 steps; it ends on An upgrade to the upgrade Offer; The Limit resets; A reply; A not-now; An opt-out; An at-risk hold; 14 days from the note. |
| Sequence | <a id="sequence-overage-savings-note"></a>`overage-savings-note` | Overage savings note: 1 steps; it ends on An upgrade to the upgrade Offer; A reply; An opt-out; The next Invoice. |
| Sequence | <a id="sequence-usage-upgrade-confirm"></a>`usage-upgrade-confirm` | Usage upgrade confirmation: 2 steps; it ends on The use summary sent; A switch down; An unsubscribe. |
| Benchmark | <a id="benchmark-usage-upsell-month-one-plan-upgrade"></a>`usage-upsell-month-one-plan-upgrade` | Share of Subscriptions moving to a higher Offer in their first month: 0.75 % (A subscription-analytics firm’s billing data, published in a pricing newsletter, 2026-09-30). |
| Benchmark | <a id="benchmark-usage-upsell-month-four-plan-upgrade"></a>`usage-upsell-month-four-plan-upgrade` | Share of Subscriptions moving to a higher Offer in their fourth month: 0.16 % (A subscription-analytics firm’s billing data, published in a pricing newsletter, 2026-09-30). |
| Benchmark | <a id="benchmark-usage-upsell-renewal-upgrade-share"></a>`usage-upsell-renewal-upgrade-share` | Share of first-year Offer upgrades that land at the month-12 renewal: 40 % (A subscription-analytics firm’s billing data, published in a pricing newsletter, 2026-09-30). |
| Benchmark | <a id="benchmark-usage-upsell-month-one-seat-upgrade"></a>`usage-upsell-month-one-seat-upgrade` | Share of Subscriptions adding seats in their first month: 0.83 % (A subscription-analytics firm’s billing data, published in a pricing newsletter, 2026-09-30). |