URL: /revenue/upgrades-and-seats · Id: `page:/revenue/upgrades-and-seats`

# More seats and upgrades: when they are ready

> More seats and upgrades: who is ready, the switch they press, and a downgrade that never waits.

More seats and a bigger plan are offered when use passes the [Limit](https://startupbuilder.dev/business/nouns/limit), and a downgrade runs at once.

```text title="Ask"
Which customers are ready for more seats, and what has to be set up first?
```

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

const { rows } = await $.Metric.query({ metric: 'expansion-revenue', shape: 'trend', breakdown: 'customer', window: '28d' })   // 2 ready: Kestrel Rentals at 118 of 50 units, Elm Street at 180 of 150
await $.Subscription.switch({ subscription: 'subscription_21', offer: 'offer_2' }, { mode: 'try' })   // Kestrel to Team, prorated: the Customer’s own press
await $.Subscription.resize({ subscription: 'subscription_12', quantity: 6 }, { mode: 'try' })   // Birchway’s 10 to 6 runs at once; one save offer sits beside it
await $.Message.schedule({ message: 'message_79', at: '2026-10-22T15:00:00Z' }, { mode: 'try' })   // the seat note to Harbor Row, at 140 units
// 422 · The seat note to Harbor Row did not send: they are at risk.
// keep-first: Let their CSM reach them first, then ask again once they are healthy. · then Task.next
return await $.Metric.query({ metric: 'expansion-arr', shape: 'trend', window: '28d' })   // Not live yet, gaps #4
```

```json title="Response · sample"
{
  "metric": {
    "$id": "https://api.sb/leaseline.app/metrics/expansion-arr",
    "$type": "Metric",
    "name": "Expansion ARR"
  },
  "rows": [
    {
      "window": "28d",
      "value": 2392,
      "customers": 4,
      "contraction": -347
    }
  ]
}
```

`+$2,392 ARR from upgrades in 28 days · −$347 from downgrades · before-after, not proof · n 9`

`Metric.query({"metric":"expansion-arr","shape":"trend","window":"28d"})` · sample · leaseline.app at 2026-10-19

## What it does
| What it does | Price per unit | Moves | Who does it | Today |
| --- | --- | --- | --- | --- |
| <Op verb="Subscription.switch" /> moves a Subscription to another Offer and <Op verb="Subscription.resize" /> changes its seats; both prorate, and a downgrade runs at once. | $0.04 per note (Worker cost) | [Upgrades](/revenue/expansion-arr), [Downgrades](/revenue/contraction-arr) | [CSM](/capabilities/how-your-agents-act#csm) | 4 of 5 calls live |

## Set it up
| Check (dry run on a 0-Customer Startup, gaps #8) | Fix | Then | Your time | Wait |
| --- | --- | --- | --- | --- |
| [`limit-on-offer`](/code/refusals#check-limit-on-offer) (Not live yet, gaps #8) · pass: Leaseline Pro: 50 units | Add a Limit to the Offer, then publish it. | <Op verb="Offer.publish" /> | 1 min | none |
| [`billing-connected`](/code/refusals#check-billing-connected) (Not live yet, gaps #8) · fail | Connect Stripe so every charge and Meter reading is live. | <Op verb="Integration.connect" /> | 5 min | the first sync, about 10 min |
| [`role-unstaffed`](/code/refusals#check-role-unstaffed) (Not live yet, gaps #8) · warn: 4 of your Customers have a person owner, Priya | Staff the CSM, or leave it: Priya’s 4 get drafts in her queue. | <Op verb="Role.staff" /> | 0 min | none |

## The records
```json title="Kestrel Rentals, past its Limit"
{
  "$id": "https://api.sb/leaseline.app/subscriptions/subscription_21",
  "$type": "Subscription",
  "status": "active",
  "quantity": 1,
  "renewsAt": "2026-11-01"
}
```

## Calls
| Call | Here | Emits | Status |
| --- | --- | --- | --- |
| <Op verb="Metric.query" /> | 2 ready: Kestrel Rentals at 118 of 50 units, Elm Street at 180 of 150 | `Metric.queried` | live |
| <Op verb="Subscription.switch" /> | Kestrel to Team, prorated: the Customer’s own press | `Subscription.switched` | live |
| <Op verb="Subscription.resize" /> | Birchway’s 10 to 6 runs at once; one save offer sits beside it | `Subscription.resized` | live |
| <Op verb="Message.schedule" /> | the seat note to Harbor Row, at 140 units | `Message.scheduled` | live |
| <Op verb="Metric.query" /> |  | `Metric.queried` | Not live yet, gaps #4 |

## Events
| Event | When it fires | What hears it |
| --- | --- | --- |
| `Subscription.switched` | the Customer presses the upgrade | books Upgrades |
| `Subscription.resized` | seats change, up or down | books Upgrades or Downgrades |

## Refuses when

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

| Check | Refuses | Fix | Then |
| --- | --- | --- | --- |
| [`keep-first`](/code/refusals#check-keep-first) (Not live yet, gaps #8) | an upgrade, referral, review or reference ask to a Customer who is at risk, past due or leaving | Let their CSM reach them first, then ask again once they are healthy. | <Op verb="Task.next" /> |
| [`limit-on-offer`](/code/refusals#check-limit-on-offer) (Not live yet, gaps #8) | an upgrade Play on Offers that carry no Limit | Add a Limit to the Offer, then publish it. | <Op verb="Offer.publish" /> |
| [`exit-never-waits`](/code/refusals#check-exit-never-waits) (Not live yet, gaps #3) | any agent Action that holds, delays or conditions a Customer’s cancel or downgrade | Leave the cancel standing; the Customer takes it back with one press while access lasts. | <Op verb="Subscription.keep" /> |

## Plays that use it
| Play | What it does | Use it when |
| --- | --- | --- |
| [Usage upsell](/plays/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. | 10 or more Customers on an Offer with a Limit, 3 or more a month pacing past it |
| [Signal expansion](/plays/signal-expansion) | Shows a growing Customer the seats they already use at the live Price, and names a successor within 5 business days when their champion leaves. | 20 or more paying Customers on a seat-priced Offer, 2 or more a month adding people past their seats |
| [Single sign-on upgrade](/plays/sso-rung) | Answers each single sign-on ask within one business hour with the plan that includes it, and the admin tests sign-in before paying. | 2 or more single sign-on asks a month, and an Offer that includes it |
| [Next order due](/plays/next-outcome) | Tells a Customer the day their last order makes the next one due, with the live price and a one-click order. | 10 or more Customers with a delivered Service Order in your outcome map, 3 or more next outcomes due a month |
| `downgrade-save` (Not live yet, gaps #4) | The Play that offers one save before a seat or plan downgrade runs. | today: The hub Workflow change-plan: <Op verb="Subscription.resize" /> runs at once; one <Op verb="Subscription.keep" /> offer sits beside it. |
| `outcome-review` (Not live yet, gaps #4) | The Play that reviews a delivered outcome with the Customer and offers the next one. | today: The hub Workflow fan-out-outcome. |

## Hub guides it deepens
| On startupbuilder.dev | Id |
| --- | --- |
| [Change a plan](https://startupbuilder.dev/workflows/processes/plan-changes/change-plan) | `workflow:change-plan` |
| [Trials and plan changes](https://startupbuilder.dev/workflows/processes/plan-changes) | `process:plan-changes` |

## Example
```text title="Leaseline, as the fixture ran it"
Kestrel Rentals · 118 of 50 units on Pro · Team offered, theirs to press · Birchway 10 to 6 seats runs at once · the seat note to Harbor Row refused (keep-first) · +$2,392 from upgrades, −$347 from downgrades in 28 days · sample
```