Skip to content

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, and a downgrade runs at once.

Who is ready for moreMode try
You, to your agent

Which customers are ready for more seats, and what has to be set up first?

curl -X POST https://api.sb/leaseline.app/metrics/query \
  -H "Authorization: Bearer $STARTUP_BUILDER_API_KEY" \
  -H "Content-Type: application/json" \
  -d '{"metric":"expansion-revenue","shape":"trend","breakdown":"customer","window":"28d"}'

curl -X POST "https://api.sb/leaseline.app/subscriptions/subscription_21/switch?mode=try" \
  -H "Authorization: Bearer $STARTUP_BUILDER_API_KEY" \
  -H "Content-Type: application/json" \
  -d '{"offer":"offer_2"}'

curl -X POST "https://api.sb/leaseline.app/subscriptions/subscription_12/resize?mode=try" \
  -H "Authorization: Bearer $STARTUP_BUILDER_API_KEY" \
  -H "Content-Type: application/json" \
  -d '{"quantity":6}'

curl -X POST "https://api.sb/leaseline.app/messages/message_79/schedule?mode=try" \
  -H "Authorization: Bearer $STARTUP_BUILDER_API_KEY" \
  -H "Content-Type: application/json" \
  -d '{"at":"2026-10-22T15:00:00Z"}'

# Not live yet (gaps #4)
curl -X POST https://api.sb/leaseline.app/metrics/query \
  -H "Authorization: Bearer $STARTUP_BUILDER_API_KEY" \
  -H "Content-Type: application/json" \
  -d '{"metric":"expansion-arr","shape":"trend","window":"28d"}'
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
npx startup-builder do Metric.query --json '{"metric":"expansion-revenue","shape":"trend","breakdown":"customer","window":"28d"}'
npx startup-builder do Subscription.switch --json '{"subscription":"subscription_21","offer":"offer_2"}' --try
npx startup-builder do Subscription.resize --json '{"subscription":"subscription_12","quantity":6}' --try
npx startup-builder do Message.schedule --json '{"message":"message_79","at":"2026-10-22T15:00:00Z"}' --try
npx startup-builder do Metric.query --json '{"metric":"expansion-arr","shape":"trend","window":"28d"}'   # Not live yet (gaps #4)
{
  "name": "do",
  "arguments": {
    "code": "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\nawait $.Subscription.switch({ subscription: 'subscription_21', offer: 'offer_2' }, { mode: 'try' })   // Kestrel to Team, prorated: the Customer’s own press\nawait $.Subscription.resize({ subscription: 'subscription_12', quantity: 6 }, { mode: 'try' })   // Birchway’s 10 to 6 runs at once; one save offer sits beside it\nawait $.Message.schedule({ message: 'message_79', at: '2026-10-22T15:00:00Z' }, { mode: 'try' })   // the seat note to Harbor Row, at 140 units\n// 422 · The seat note to Harbor Row did not send: they are at risk.\n// keep-first: Let their CSM reach them first, then ask again once they are healthy. · then Task.next\nreturn await $.Metric.query({ metric: 'expansion-arr', shape: 'trend', window: '28d' })   // Not live yet, gaps #4",
    "mode": "try"
  }
}
Response200application/json
{
  "metric": {
    "$id": "https://api.sb/leaseline.app/metrics/expansion-arr",
    "$type": "Metric",
    "name": "Expansion ARR"
  },
  "rows": [
    {
      "window": "28d",
      "value": 2392,
      "customers": 4,
      "contraction": -347
    }
  ]
}
sample · 1 Not live yet

+$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 doesPrice per unitMovesWho does itToday
POST /{namespace}/subscriptions/{subscription}/switch moves a Subscription to another Offer and POST /{namespace}/subscriptions/{subscription}/resize changes its seats; both prorate, and a downgrade runs at once.$0.04 per note (Worker cost)Upgrades, DowngradesCSM4 of 5 calls live

Set it up

Check (dry run on a 0-Customer Startup, gaps #8)FixThenYour timeWait
limit-on-offer (Not live yet, gaps #8) · pass: Leaseline Pro: 50 unitsAdd a Limit to the Offer, then publish it.POST /{namespace}/offers/{offer}/publish1 minnone
billing-connected (Not live yet, gaps #8) · failConnect Stripe so every charge and Meter reading is live.POST /{namespace}/integrations/connect5 minthe first sync, about 10 min
role-unstaffed (Not live yet, gaps #8) · warn: 4 of your Customers have a person owner, PriyaStaff the CSM, or leave it: Priya’s 4 get drafts in her queue.POST /{namespace}/roles/{role}/staff0 minnone

The records

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

CallHereEmitsStatus
POST /{namespace}/metrics/query2 ready: Kestrel Rentals at 118 of 50 units, Elm Street at 180 of 150Metric.queriedlive
POST /{namespace}/subscriptions/{subscription}/switchKestrel to Team, prorated: the Customer’s own pressSubscription.switchedlive
POST /{namespace}/subscriptions/{subscription}/resizeBirchway’s 10 to 6 runs at once; one save offer sits beside itSubscription.resizedlive
POST /{namespace}/messages/{message}/schedulethe seat note to Harbor Row, at 140 unitsMessage.scheduledlive
POST /{namespace}/metrics/queryMetric.queriedNot live yet, gaps #4

Events

EventWhen it firesWhat hears it
Subscription.switchedthe Customer presses the upgradebooks Upgrades
Subscription.resizedseats change, up or downbooks Upgrades or Downgrades

Refuses when

422RefusedThe seat note to Harbor Row did not send: they are at risk.
CheckFixThen
keep-first fail · Not live yet, gaps #8Let their CSM reach them first, then ask again once they are healthy.POST /{namespace}/tasks/next
JSON
{
  "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"
    }
  ]
}

It says no, says why, and names the fix.

CheckRefusesFixThen
keep-first (Not live yet, gaps #8)an upgrade, referral, review or reference ask to a Customer who is at risk, past due or leavingLet their CSM reach them first, then ask again once they are healthy.POST /{namespace}/tasks/next
limit-on-offer (Not live yet, gaps #8)an upgrade Play on Offers that carry no LimitAdd a Limit to the Offer, then publish it.POST /{namespace}/offers/{offer}/publish
exit-never-waits (Not live yet, gaps #3)any agent Action that holds, delays or conditions a Customer’s cancel or downgradeLeave the cancel standing; the Customer takes it back with one press while access lasts.POST /{namespace}/subscriptions/{subscription}/keep

Plays that use it

PlayWhat it doesUse it when
Usage upsellOffers 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 expansionShows 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 upgradeAnswers 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 dueTells 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: POST /{namespace}/subscriptions/{subscription}/resize runs at once; one POST /{namespace}/subscriptions/{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.devId
Change a planworkflow:change-plan
Trials and plan changesprocess:plan-changes

Example

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

Compiled, not written: edit its page, then regenerate. Every step runs from a sentence or a line of code. In your words: the CRM words you know, and where each lives here.

Contents