Skip to content

Product-led growth

Gives free Customers enough to reach the result twice, then shows the upgrade inside the task at the Limit, with the real Price.

Channel
Signup flows
Run oneMode try
You, to your agent

Hand our latest free account at a limit to the CSM agent and show me what it chose and why. Don’t send.

curl "https://api.sb/leaseline.app/events?where=name%20in%20(%22Entitlement.checked%22%2C%20%22Event.captured%22%2C%20%22Customer.activated%22%2C%20%22Offer.subscribed%22%2C%20%22Subscription.ended%22%2C%20%22Version.deployed%22%2C%20%22Metric.watched%22)&limit=1" \
  -H "Authorization: Bearer $STARTUP_BUILDER_API_KEY"

# Not live yet (gaps #7)
curl -X POST "https://api.sb/leaseline.app/triggers/fire?mode=try" \
  -H "Authorization: Bearer $STARTUP_BUILDER_API_KEY" \
  -H "Content-Type: application/json" \
  -d '{"like":"event_1310"}'

# Not live yet (gaps #7)
curl "https://api.sb/leaseline.app/decisions?where=on%20%3D%20%22customer_44%22" \
  -H "Authorization: Bearer $STARTUP_BUILDER_API_KEY"

# Not live yet (gaps #7)
curl "https://api.sb/leaseline.app/actions?where=on%20%3D%20%22customer_44%22" \
  -H "Authorization: Bearer $STARTUP_BUILDER_API_KEY"
import { $ } from 'startup-builder'

const { events: [last] } = await $.Event.list({ where: 'name in ("Entitlement.checked", "Event.captured", "Customer.activated", "Offer.subscribed", "Subscription.ended", "Version.deployed", "Metric.watched")', 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 }
npx startup-builder do Event.list --json '{"where":"name in (\"Entitlement.checked\", \"Event.captured\", \"Customer.activated\", \"Offer.subscribed\", \"Subscription.ended\", \"Version.deployed\", \"Metric.watched\")","limit":1}'
npx startup-builder do Trigger.fire --json '{"like":"event_1310"}' --try   # Not live yet (gaps #7)
npx startup-builder do Decision.list --json '{"where":"on = \"customer_44\""}'   # Not live yet (gaps #7)
npx startup-builder do Action.list --json '{"where":"on = \"customer_44\""}'   # Not live yet (gaps #7)
{
  "name": "do",
  "arguments": {
    "code": "const { events: [last] } = await $.Event.list({ where: 'name in (\"Entitlement.checked\", \"Event.captured\", \"Customer.activated\", \"Offer.subscribed\", \"Subscription.ended\", \"Version.deployed\", \"Metric.watched\")', limit: 1 })   // { play } compiled to this Play’s Trigger Events\nconst { subject } = await $.Trigger.fire({ like: last.$id }, { mode: 'try' })   // the same Event again, in try mode · Not live yet, gaps #7\nconst { decisions: [decision] } = await $.Decision.list({ where: `on = \"${subject}\"` })   // what it chose, and why · Not live yet, gaps #7\nconst { actions } = await $.Action.list({ where: `on = \"${subject}\"` })   // what it did, with its checks · Not live yet, gaps #7\nreturn { decision, actions }",
    "mode": "try"
  }
}
Response200application/json
{
  "decision": {
    "$id": "https://api.sb/leaseline.app/decisions/decision_361",
    "$type": "Decision",
    "choice": "prompt-at-bound",
    "reason": "the 10th lease read this month was refused, inside the renewal notice they were drafting, and Pro grants 200",
    "stakes": "low",
    "options": [
      "prompt-at-bound",
      "offer-paid-trial",
      "upsell-month-one",
      "win-back",
      "to-the-ae",
      "not-now"
  …
sample · 4 Not live yetPreview

On PATCH /{namespace}/campaigns/{id}Off PATCH /{namespace}/campaigns/{id}Take this one POST /{namespace}/tasks/{task}/claim

action_2 puts Oakfield in the agent half; the prompt shows inside the notice they were drafting. action_3 is the bound follow-up’s day-1 email, which ends when they upgrade or the grant resets.

+18.3 activated per 100 · ■■□□ Early sign · n 164 · path → arr

Try another: the free accountChoiceReason
Oakfield Lettings: 10 of 10 free lease reads used, the 10th refused mid-noticeprompt-at-boundthe bound met inside the task
Pinecrest Rentals: 14 notices sent in 6 days on free, no bound metoffer-paid-trialthe pattern that pays, before any limit
Juniper Homes: paid 12 days ago, a second member refused the owner reportupsell-month-oneTeam grants the owner report
Cedar Lane: ended Sep 30; the reminder fix they asked for shipped Oct 14win-backthe reason they gave is fixed
Northgate Residential: 220 units on a free account, asked about a contractto-the-aeover 150 units, and terms are the AE’s

Where it sits: Stranger → Lead → Trialing → Activated → At risk → Churned → Won back · Activation rate → New paying Customers → New Customers ↑ → ARR added each week → ARR

Worth’s formula: about +18.3 activated per 100 × 99 free signups a month × 0.24 activated to paid (your last 90 days) × $347 Pro ARR ≈ +$1,509. Under First signal: About 6 weeks to tell a +14 lift at 99 a month (4 to read, 2 to mature); at 20 a month, 90 days can tell only +31 or more, so your file starts at holdout: 0.

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

Start it this week

Terminal
npx startup-builder add play product-led   # 1 · writes plays/product-led.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":"product-led"}'

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

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

# off: no prompt or email sends; free grants and the cancel button stay
curl -X PATCH https://api.sb/leaseline.app/campaigns/product-led \
  -H "Authorization: Bearer $STARTUP_BUILDER_API_KEY" \
  -H "Content-Type: application/json" \
  -d '{"endsAt":"now"}'
422Refusedproduct-led can’t start yet: 1 check fails and 1 warns.
CheckFixThen
use-when pass · Not live yet, gaps #8Keep 20 or more free signups a month, or run signup-interviews.POST /{namespace}/metrics/query
limit-on-offer fail · Not live yet, gaps #8Add a 10-read monthly Limit to Leaseline Free, then publish it.POST /{namespace}/offers/{offer}/publish
billing-connected pass · Not live yet, gaps #8Connect Stripe so every grant, charge and Meter reading is live.POST /{namespace}/integrations/connect
baseline pass · Not live yet, gaps #8Read it once; it fills Before in Your number.POST /{namespace}/metrics/query
role-unstaffed warn · Not live yet, gaps #8Staff the CSM, or leave it: CSM routes come to you, and Priya’s 2 get drafts.POST /{namespace}/roles/{role}/staff
JSON
{
  "type": "https://startupbuilder.dev/errors/refused",
  "title": "Refused",
  "status": 422,
  "detail": "product-led can’t start yet: 1 check fails and 1 warns.",
  "checks": [
    {
      "check": "use-when",
      "status": "pass",
      "observed": "99 free signups in 30 days",
      "doc": "/plays/product-led#check-use-when",
      "fix": "Keep 20 or more free signups a month, or run signup-interviews.",
      "then": "Metric.query"
    },
    {
      "check": "limit-on-offer",
      "status": "fail",
      "observed": "Leaseline Free: 5 units, lease reads unmetered",
      "doc": "/plays/product-led#check-limit-on-offer",
      "fix": "Add a 10-read monthly Limit to Leaseline Free, then publish it.",
      "then": "Offer.publish"
    },
    {
      "check": "billing-connected",
      "status": "pass",
      "observed": "Stripe, connected 2026-08-24",
      "doc": "/plays/product-led#check-billing-connected",
      "fix": "Connect Stripe so every grant, charge and Meter reading is live.",
      "then": "Integration.connect"
    },
    {
      "check": "baseline",
      "status": "pass",
      "observed": "31.7 activated per 100 free signups, n 212 in 90 days",
      "doc": "/plays/product-led#check-baseline",
      "fix": "Read it once; it fills Before in Your number.",
      "then": "Metric.query"
    },
    {
      "check": "role-unstaffed",
      "status": "warn",
      "roles": [
        "success-manager"
      ],
      "owned": 2,
      "doc": "/plays/product-led#check-role-unstaffed",
      "fix": "Staff the CSM, or leave it: CSM routes come to you, and Priya’s 2 get drafts.",
      "then": "Role.staff"
    }
  ]
}

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

CheckFix (one call)Your timeWait
use-when · baseline · pass: 99 free signups a month; 31.7 activated per 100, n 212 (gaps #8)POST /{namespace}/metrics/query on activation-rate, which fills Before in Your number1 minnone
limit-on-offer · fail (gaps #8): lease reads on Leaseline Free have no LimitPOST /{namespace}/offers/{offer}/publish with a 10-read monthly Limit, 2 a day10 minnone; new signups get it, existing free accounts after 30 days’ notice
billing-connected · pass: StripePOST /{namespace}/integrations/connect with system recurring-billing (Stripe)0 minnone
role-unstaffed · warn (gaps #8): 2 free accounts have a person owner, PriyaPOST /{namespace}/roles/{role}/staff, or leave it: CSM routes come to you, and Priya’s 2 get drafts in her queue0 minnone

Your number · Before 31.7 · Now 50.0 activated per 100 · Target 55 by Dec 31 · ■■□□ Early sign · n 164 matured

Each lease read on a free grant costs $0.03 from the free Offer pool, booked as marketing spend; each prompt email costs $0.04 from your Domain (Worker cost); POST /{namespace}/entitlements/check costs nothing. That comes to about $21 a month at 99 free signups, inside the $50 pool, and $4 at 20.

When it starts

WhenTrigger idStarts onWindowHow it gets in
A free account meets a metered limit, or has 20 % or less leftusage-thresholdthreshold on Entitlement.checked where allowed = false, or 20 % or less of the grant leftprompt inside the task; follow-up within 2dyour app calls POST /{namespace}/entitlements/check before each lease read
A free account does what paying accounts did in their first weekpaid-predictorthreshold on Event.captured where the fitted usage Event reaches its fitted count within 7 days of signup7d from signupyour app calls POST /{namespace}/events/capture on each notice sent
A new paying account nears a Limit, or a member is refusedlimit-pressurethreshold on Entitlement.checked where used / bound >= 0.8 inside the first 30 paid days, or a member refused a Verb the next Offer grants3d, inside the first 30 paid daysthe same POST /{namespace}/entitlements/check
+ 6 more for activation, first paid, cancels, fixes, cost shifts and the pool (?lanes=all)
One signed delivery · StartupBuilder-Signature
{
  "$id": "https://api.sb/leaseline.app/events/event_1310",
  "$type": "Event",
  "name": "Entitlement.checked",
  "at": "2026-10-19T10:12:40Z",
  "seq": 1310,
  "data": {
    "customer": "customer_44",
    "offer": "leaseline-free",
    "limit": "lease-reads",
    "used": 10,
    "bound": 10,
    "allowed": false,
    "resets": "2026-11-01"
  }
}
Reproduce it in test
npx startup-builder trigger Entitlement.checked --like event_1310   # replays the delivery
npx startup-builder clock advance --by 1d   # runs the bound follow-up’s day-1 email (action_3)
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 AE · Priya Shah (person) (role:sales-lead, worker_3) for free accounts over 150 units and any ask for terms, and to you (Dana Okafor, person, worker_1) for the rest, because no person holds the CSM seat. Your number links to Did it work.

OptionRung todayEvidence (passed of n)Moves up atDrops back whenYou can
Prompt at the limit · Upsell in month oneIt does it, you can stop it38 of 38 · 6 of 6met on day 9 (the first wave of 20)any prompt fails a Check → You approve the first wavePOST /{namespace}/actions/{action}/cancel inside the hold
Offer the paid trial · Win backYou approve the first wave19 of 20 (1 refused) · 4 of 4at 20 passed in a row—POST /{namespace}/approvals/{approval}/approve · POST /{namespace}/approvals/{approval}/refuse
To the AE · Not nowIt does it1 of 1 · 3 of 3—a held account is asked anyway → You approve each onePOST /{namespace}/actions/{action}/redirect
Change the free grantYou approve each one1 merged of 1never—POST /{namespace}/changes/{change}/merge · POST /{namespace}/changes/{change}/revert
Never: a discount, a countdown that isn’t real, a cancel that waitsTo a person, every time2 asked, 2 handed overnever—POST /{namespace}/approvals/{approval}/approve on the AE’s Quote

What it reads · What it may call. It reads the account’s own Events, the Entitlement and the live Price; a predictor score or a matched company never appears in what it writes. The cancel is one press and never waits on the agent (exit-never-waits). Its tools are POST /{namespace}/entitlements/check, POST /{namespace}/offers/{offer}/trial, POST /{namespace}/offers/{offer}/subscribe, POST /{namespace}/conversations/{conversation}/escalate, POST /{namespace}/tasks/{task}/assign, and any other call becomes an Approval under the Role’s Mandate.

The brief (folded): compiled Agent-register Markdown · play:product-led@1 · each line names its record key
You hold the CSM Key Result: 55 activated per 100 free signups by 2026-12-31.    ← worker, metric
On each Trigger, choose one option and say why; a held account gets Not now.     ← triggers, chooses, keep-first
Free grant: 10 lease reads a month, twice what activation takes.                 ← your file, line 4
A prompt states the units left, the reset time and Pro’s price from the Pricing Page. ← copyRules 1
Never a countdown, a discount or a win-back offer below list.                    ← copyRules 4, 8
Never write to an account a person owns: draft it into their queue.              ← worker.owns, owned-by-a-person

The slow loop, as a Change. decision_142 (2026-09-07, weekly) read that activation took a median of 5 lease reads and that 31 of 40 free accounts hit the old 6-read cap before their second notice, so change_19 raised the grant to 10 (your file, line 4) and Dana merged it with POST /{namespace}/changes/{change}/merge. Replayed on past signups, POST /{namespace}/changes/{change}/review put the agent half at 48.1 per 100; read back at +30d it is 50.0, n 82, ■■□□ Early sign (Example, day 44).

Bring your own agent (folded) starts at It does it, you can stop it when it matches or beats the seated agent on the eval suite, and at You approve the first wave when it does not; POST /{namespace}/roles/{role}/staff seats it.

Terminal
npx startup-builder eval --scenario product-led-184 --agent "claude -p" --seat success-manager   # seated: 50.0 per 100, 0 Checks failed sent, $0.21 a signup · yours: 47.6, 1 failed, $0.26 → You approve the first wave

What it can do next

OptionPicked whenDecided byRunsHands toIf it’s just you
Prompt at the limit prompt-at-bounda metered limit met, or 20 % or less leftYour rule (line 4)POST /{namespace}/entitlements/check → the prompt inside the task → bound-follow-upthe Customer’s pressthe same
Offer the paid trial offer-paid-trialthe pattern that pays, before any limitAgent’s call, with its reasontrial-to-paid → POST /{namespace}/offers/{offer}/trial → convert-trialthe Customer’s pressthe same
Upsell in month one upsell-month-onepaid under 30 days, at 80 % of a Limit or a member refusedYour rule (line 5)first-30-upsell → on press change-planthe Customer’s pressthe same
Win back win-backa canceled plan ended, or their reason got fixedAgent’s call, with its reasonwin-back-1-30-60 → on press POST /{namespace}/offers/{offer}/subscribenobody; restore is one pressthe same
To the AE to-the-aeover 150 units, or an ask for termsAgent’s call, with its reasonPOST /{namespace}/conversations/{conversation}/escalate → POST /{namespace}/tasks/{task}/assignPOST /{namespace}/tasks/{task}/assign → AE · Priya Shah, due 1 business day · Take this oneyou, within 1 business day
Not now not-nowanother Play holds them, or keep-first appliesYour rule (rule j)nothing; names the holdertheir CSM, so you · Take this oneyou
When they write backFixture replyWhat happensWho answersBy whenCall
Interested“OK, move us to Pro.”the one-press link in the prompt; nothing is charged until they pressthe Customer—POST /{namespace}/subscriptions/{subscription}/switch (theirs)
Send me pricing“What does Pro cost for 30 units?”Pro’s live Price and what it grants, from the Pricing PageCSM · your agent5 minPOST /{namespace}/conversations/{conversation}/answer
Not now“We’ll stay on free for now.”the follow-up stops; the grant resets on the 1st as statednobodyat onceGET /{namespace}/decisions
Wrong person“Our office manager pays for software.”asks them to forward it; it never writes to an address from their textCSM · your agent1 business dayPOST /{namespace}/conversations/{conversation}/answer
Out of officean auto-reply until Oct 27the follow-up waits for the return datenobodythe return datePOST /{namespace}/messages/{message}/schedule
Question it can’t answer“Can you import our leases from the old system?”To a person, the question in the Briefyou1 business dayPOST /{namespace}/conversations/{conversation}/escalate
Stop“Stop emailing me about plans.”emails stop; the in-task prompt keeps showing at the limitnobodyat oncePOST /{namespace}/contacts/{contact}/suppress

When it hands to you

To the AE goes to AE · Priya Shah (person); Not now and every approval come to you, the CSM seat. One Brief per Role, as captured (brief is Not live yet, gaps #7).

What lands in each queue · sample
[
  {
    "$id": "https://api.sb/leaseline.app/tasks/task_441",
    "$type": "Task",
    "assignee": "worker_3",
    "due": "2026-10-07T16:20:00Z",
    "on": "customer_58",
    "brief": {
      "who": "Northgate Residential, free account",
      "said": "We have 220 units. Is there an annual contract?",
      "chose": "to-the-ae",
      "why": "over 150 units; terms are the AE’s",
      "never": [
        "a discount",
        "a deadline that isn’t real"
      ]
    }
  },
  {
    "$id": "https://api.sb/leaseline.app/tasks/task_418",
    "$type": "Task",
    "assignee": "worker_1",
    "due": "2026-09-30T17:00:00Z",
    "on": "customer_51",
    "brief": {
      "who": "Willow Park, Pro since Sep 21",
      "chose": "not-now",
      "why": "2 support threads on failed imports this week",
      "held": "keep-first"
    }
  },
  {
    "$id": "https://api.sb/leaseline.app/approvals/approval_52",
    "$type": "Approval",
    "verdict": null,
    "deadline": "2026-08-28T15:00:00Z",
    "draft": "You’ve sent 14 notices this week. Pro gives you 200 lease reads a month for $29…",
    "route": "offer-paid-trial",
    "checks": [
      {
        "check": "price",
        "status": "pass"
      },
      {
        "check": "keep-first",
        "status": "pass"
      }
    ]
  }
]

The receiver answers with POST /{namespace}/tasks/{task}/claim (Take this one), POST /{namespace}/tasks/{task}/return with a reason, POST /{namespace}/actions/{action}/redirect, POST /{namespace}/approvals/{approval}/approve or POST /{namespace}/approvals/{approval}/refuse, each a one-call window. Once a person claims, the agent’s pending asks to that account cancel with claimedByPerson (Not live yet, gaps #7), and every reply goes to the claimer. A Task nobody claims by its due time goes up a Role through POST /{namespace}/tasks/{task}/escalate, and person-promise fires.

On time: AE · Priya Shah, 1 of 1 within 1 business day, n 1; you, 3 of 3, median 2 h, n 3, ■□□□ Too early to tell beside Other Startups’ 92 % on time. Your queue: in week 1, 20 approvals and 1 weekly review; after the promotion on day 9, 1 weekly review and about 1 hand-off a week.

The fixed line: POST /{namespace}/deals/{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)RefusesFixThen
owned-by-a-person (rule j; gaps #8)a prompt email to an account whose open Deal a person owns, or whose Conversation a person claimedLeave it to the person who owns it: the draft waits in their queue.POST /{namespace}/tasks/{task}/assign
keep-first (rule j; gaps #8)an upgrade or win-back ask to a Customer at risk, past due or with an unsubscribe pendingLet their CSM reach them first, then ask again once they are healthy.POST /{namespace}/tasks/next
customer-ask-cap (gaps #3)a second open ask to one Customer in a window, from any Playusage-upsell holds them until Nov 2.GET /{namespace}/decisions
price (gaps #1)a price, plan or date that differs from the Pricing PageQuote the Pricing Page’s live Price, or send the ask to the AE.POST /{namespace}/approvals/{approval}/approve
exit-never-waits (the record’s Cancel never waits; gaps #3)any screen, hold or Approval in front of a cancel, pause or downgradeRemove the step: the cancel completes in one press.POST /{namespace}/subscriptions/{subscription}/unsubscribe
abuse (the record’s Abuse stop)the costly grant to a cluster of more than 3 free accounts in 7 days, or a disposable mailboxVerify the mailbox or add a card; cheap Verbs keep working.POST /{namespace}/payment-sources/add
Stop (pauses the Play)Pauses whenThen
poolmonth-to-date free-grant cost reaches 100 % of the $50 poolnew visitors see the trial Offer until the 1st; accepted grants stand
activation-floora grant change drops activation 3 points or more below the prior 30 days, at n 100the change reverts for new signups (POST /{namespace}/changes/{change}/revert)
downgradean upsell arm’s 60-day downgrade or refund rate passes your half’sthat arm ends
complaintcomplaints near 0.1 % of a week’s prompt emailsemails pause on every Mailbox; in-task prompts keep showing
kill-switchyou press itevery prompt and email stops; new visitors see the control Offer, accepted Offers stand
+ 3 more for the checkout lane: refund, day-0 cancel and first charge (?lanes=all)

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

422RefusedThe upsell to Juniper Homes did not send: it quotes Team at $59, and the Pricing Page says $79.
CheckFixThen
price fail · Not live yet, gaps #1Quote the Pricing Page’s live Price, or send the ask to the AE.POST /{namespace}/approvals/{approval}/approve
keep-first pass · Not live yet, gaps #8—POST /{namespace}/tasks/next
JSON
{
  "type": "https://startupbuilder.dev/errors/refused",
  "title": "Refused",
  "status": 422,
  "detail": "The upsell to Juniper Homes did not send: it quotes Team at $59, and the Pricing Page says $79.",
  "checks": [
    {
      "check": "price",
      "status": "fail",
      "doc": "/plays/product-led#check-price",
      "fix": "Quote the Pricing Page’s live Price, or send the ask to the AE.",
      "then": "Approval.approve"
    },
    {
      "check": "keep-first",
      "status": "pass"
    }
  ]
}

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

The Stops that fired, as Campaign.paused reasons · sample
[
  {
    "reason": {
      "limit": "pool",
      "observed": "$50.00",
      "bound": "$50",
      "window": "2026-09"
    },
    "doc": "/plays/product-led#stop-pool",
    "task": "task_430"
  }
]

Did it work

Both halves are read from the same random assignment at signup (rules m–o), and each figure’s call is POST /{namespace}/metrics/query by node and by arm; every node above the leaf is Not live yet (gaps #4).

+18.3 activated per 100 · ■■□□ Early sign · n 164 · path → arr

Chose
prompt-at-bound
Why
the 10th lease read this month was refused, inside the renewal notice they were drafting, and Pro grants 200
From
prompt-at-bound · offer-paid-trial · upsell-month-one · win-back · to-the-ae · not-now
Expects
Offer.subscribed on Pro within 14d
Reopens when
no Offer.subscribed for customer_44 within 14d
By
worker_5
JSON
{
  "$id": "decisions/decision_361",
  "$type": "Decision",
  "choice": "prompt-at-bound",
  "reason": "the 10th lease read this month was refused, inside the renewal notice they were drafting, and Pro grants 200",
  "stakes": "low",
  "options": [
    "prompt-at-bound",
    "offer-paid-trial",
    "upsell-month-one",
    "win-back",
    "to-the-ae",
    "not-now"
  ],
  "by": "worker_5",
  "on": "customer_44",
  "expected": "Offer.subscribed on Pro within 14d",
  "reopensWhen": "no Offer.subscribed for customer_44 within 14d"
}
NodeAgent halfYour halfAddedConfidenceBasis
activation-rate50.0 per 100 (41 of 82)31.7 (26 of 82)+18.3 [3.5, 33.1]■■□□ Early signheld back at random · n 164 · 20 still maturing
new-paying-customers17.2 per 100 (11 of 64)7.8 (5 of 64)+9.4 [−2.0, 20.7] · Not carrying up yet■□□□ Too early to tellthe same assignment, paid by day 30 · n 128 · 36 still maturing
new-arr ↑$3,817, 11 Customers on Pro · Booked: credited, not all caused by this Play$1,735, 5 Customers+$2,082 [−$437, +$4,601] · Worth about +$1,500 a month■□□□ Too early to tell (11 vs 5 Customers)the same · 3 trials still deciding
net-new-arr · arr+$3,817+$1,735+$2,082, carried: it moves no other bridge term■□□□ Too early to tellcarried from new-arr

The leaf counts free signups who sent 2 notices within 14 days per 100 assigned once quarantine has run (rule m), to 150 matured in each half; at 82 and 82 so far the Experiment runs on.

How it moves it: the agent half got a metered grant sized to activation (6 reads, then 10 from day 14) and met the upgrade inside the task at a median of day 6; your half kept today’s free plan and its plain upgrade page.

Guardrails: treated against held back: paid per activated account, 0.27 against 0.19 (new-paying-customers over activation-rate), and refunds in 30 days, 1 of 11 against 0 of 5.

The Campaign card · sample
{
  "$id": "https://api.sb/leaseline.app/campaigns/product-led",
  "$type": "Campaign",
  "play": "play:product-led@1",
  "path": [
    "activation-rate",
    "new-paying-customers",
    "new-arr",
    "net-new-arr",
    "arr"
  ],
  "experiment": {
    "metric": "activation-rate",
    "unit": "per-100",
    "heldBack": 92,
    "decision": null
  },
  "readout": {
    "value": 18.3,
    "n": 164,
    "maturing": 20,
    "interval": [
      3.5,
      33.1
    ],
    "readability": "C",
    "basis": "held-back",
    "pooled": false
  },
  "ledger": {
    "spend": 38,
    "workerCost": 6,
    "hours": 1.6,
    "pipelineArr": 2400,
    "wonArr": 3817,
    "projectedArr": 1509,
    "paybackDays": 4
  }
}

Outside benchmark: 0.75 % to 0.83 % of Subscriptions upgrade in their first month (a subscription-analytics vendor’s purchase data, 2026-09-30).

Before it · After it. Before it: programmatic-seo, editorial-seo and free-tools bring the free signups. After it: usage-upsell hears Pro accounts that pace past 50 units, and run-beside holds a new paying Customer’s first 30 days; keep-first holds an account at risk.

Example

Leaseline ran it from 2026-08-24 to 2026-10-19 (56 days). 196 free accounts signed up, 12 were quarantined by the abuse Check, and 184 were assigned at random, 92 to each half; 164 have matured for activation and 128 for paid. Read every captured thread at ?threads=all; three follow the tables.

What happened, agent half, by optionDecisionsPaidWon (credited, not all caused by this Play)
Prompt at the limit387 to Pro$2,429 ARR
Offer the paid trial194 to Pro, 3 still deciding$1,388 ARR
Upsell in month one · Win back · To the AE · Not now6 · 4 · 1 · 31 to Team · 1 restored · 1 Quote open · 0on expansion-arr and reactivated-arr, not here · $2,400 open
Agent half · your half92 · 92 assigned11 of 64 matured · 5 of 64$3,817 · $1,735
At 20 a month (holdout: 0)about 37 in 56 daysbasis before-after: Before · Now, not proofOther Startups’ results: +14.0 per 100, ■■■□ Likely, n 3,410
DayEventWhat happenedYour time
0event_1121 Campaign.createdthe three steps; the dry run fails limit-on-offer12 min
0event_1124 Offer.publishedLeaseline Free gains a 6-read monthly Limit10 min
3event_1170 Offer.subscribedthe first upgrade paid: Oakfield, free to Pro—
9event_1233 Role.updatedpromoted: prompts send after a 2-minute hold—
14 · 44event_1262 Change.merged · event_1488 Readout.createdthe grant from 6 to 10 reads: replayed at 48.1 per 100, read back at +30d at 50.0, n 825 min
36event_1402 Campaign.pausedpool hit $50 on Sep 29; new visitors saw the trial until Oct 14 min
1–56event_1140 Approval.created, first of 20you approved the first wave; price refused 1 draft, rewritten once and sent; weekly reviews, from the POST /{namespace}/tasks/next log22 min · total 1.6 h measured, 1.7 h asked

Oakfield Lettings, in Leaseline, Oct 19 10:12 · the 10th lease read refused, inside the renewal notice they were drafting.

The prompt, in the task · You’ve used 10 of 10 lease reads this month; they reset Nov 1.¹ Pro is $29 a month for 50 units and 200 reads.² Upgrade and this notice finishes now; switching back sits on your billing page.³

Oakfield, 10:14 · pressed Upgrade to Pro. Offer.subscribed, +$347 ARR.

Juniper Homes, 2026-09-18 · the draft price refused, then the rewrite that sent
- Upgrade to Team before Friday and it’s $59 a month.
+ Team is $79 a month and adds the owner report; today’s charge is $41.20, and switching back sits on the same page.

Northgate Residential, 2026-10-06 16:20 · “We have 220 units. Is there an annual contract?” · Leaseline, 16:21 · *Thanks. Priya, your account lead, replies by tomorrow 16:20 with the annual plan.*⁴ The Brief went to Priya (task_441).

¹ The true units left and the reset time, from the Entitlement. ² The next Offer’s Price, from the Pricing Page. ³ The switch down, one press away. ⁴ The routed line, compiled from the Role’s Settings.

Your settings

plays/product-led.mdx
---
holdout: 0.5      # half the free signups get today’s free plan and upgrade page
---
Give free accounts 10 lease reads a month, 2 a day: twice what activation takes.
In the first 30 paid days, offer Team when a second member is refused the owner report.
Over 150 units, bring in our AE.
Text
$ npx startup-builder show plays/product-led
holdout 0.5 · grant 10 reads · Team on a refused member · AE over 150 units     your file  plays/product-led.mdx:2,4,5,6 (Ask 2026-08-24; line 4 by change_19, 2026-09-07)
worker role:success-manager, owns never · chooses 6 (3 rule, 3 read) · sequences bound-follow-up, trial-to-paid, first-30-upsell, win-back-1-30-60 · approval first-send · checks owned-by-a-person, keep-first, customer-ask-cap, price, exit-never-waits, abuse · stops 8 (5 on in-product and email) · placements gmail, outlook, yahoo-mail, app-store, google-play, chrome-web-store, github, google-search    catalog  play:product-led@1 (Not live yet, gaps #5)

Every key from the catalog

Key Id What it is
Tactic meter-line Meter line frees cheap Verbs, meters the value Verb through a daily Credit Grant, and caps it at twice the activation path. Starts at You approve each one.
Tactic free-cost-pool Free Offer cost pool books the serving cost of free and trialing Customers to a marketing Account under a monthly pool, and stops free-use abuse. Starts at Sends nothing.
Tactic entry-variants Entry Variants run entry terms as parallel Offers at one Price, split by a Flag, and decide each on day-60 revenue per 1,000 Visitors. Starts at You approve the first wave.
Tactic usage-prompt Usage prompt offers the upgrade inside the task that meets a usage bound or the paid-predictor pattern, with the true limit and Price. Starts at You approve the first wave.
Tactic first-30-upsell First 30-day upsell spends its touches in a new paying Customer’s first 30 days, from their own usage, scored at day 90 against a holdout. Starts at You approve the first wave.
Tactic win-back-1-30-60 Win-back at days 1, 30 and 60 makes cancel one step, then sends a restore, the matched fixes and a fitting Offer, never a discount. Starts at You approve the first wave.
Tactic 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.
Trigger usage-threshold Usage threshold: threshold on Entitlement.checked, window 2d.
Trigger paid-predictor Paid predictor: threshold on Event.captured, window 7d.
Trigger activation-reached Activation reached: event on Customer.activated, window 72h.
Trigger first-paid First paid: event on Offer.subscribed, window 30d.
Trigger limit-pressure Limit pressure: threshold on Entitlement.checked, window 3d.
Trigger subscription-ended Subscription ended: state on Subscription.ended, window 60d.
Trigger reason-fixed Reason fixed: event on Version.deployed, window 30d.
Trigger verb-cost-shift Verb cost shift: threshold on Metric.watched, window 7d.
Trigger pool-threshold Pool threshold: threshold on Metric.watched, window 1d.
Sequence bound-follow-up Bound follow-up: 1 steps; it ends on Upgrade; Single-task purchase; Unsubscribe; The grant resets.
Sequence trial-to-paid Trial to paid: 3 steps; it ends on Paid; Cancel; Unsubscribe.
Sequence first-30-upsell First 30-day upsell: 2 steps; it ends on Switch; Downgrade; Refund request; Failed charge; Negative support Issue; Unsubscribe; Day 30.
Sequence win-back-1-30-60 Win-back at days 1, 30 and 60: 4 steps; it ends on Restore; Unsubscribe; A no-contact reason code; A fix Message sent after day 30, which replaces the day-60 touch; Day 60.
Benchmark product-led-month-1-upgrade Monthly upgrade rate in a Subscription's first month, upgrades to a higher Offer (low) and to more units (high): 0.75–0.83 % (A subscription-analytics vendor's purchase data, published by its affiliated analyst, 2026-09-30).
Benchmark product-led-month-1-multiple Month-1 upgrade rate as a multiple of the month-4 rate, to a higher Offer and to more units: 4.4–4.7 ratio (A subscription-analytics vendor's purchase data, published by its affiliated analyst, 2026-09-30).
Benchmark product-led-renewal-upgrade-share Share of first-year upgrades to a higher Offer made at the month-12 renewal: 40 % (A subscription-analytics vendor's purchase data, published by its affiliated analyst, 2026-09-30).
Benchmark product-led-churn-return Share of churned subscribers who return within 3 months, all products (low) and AI-native products (high): 5–10 % (A subscription-analytics vendor's purchase data, published by its affiliated analyst, 2026-09-30).
Benchmark product-led-card-trial Free-to-paid conversion within six months of free trials that require a card: 30 % (A growth analyst's free-to-paid survey with two subscription-analytics vendors; correlational, 2026-02-04).
Benchmark product-led-hard-paywall-d35 Median day-35 conversion (trial-to-paid in the text, download-to-paid in the chart), freemium apps (low) against hard onboarding paywall apps (high): 2.1–10.7 % (A mobile subscription-billing vendor's annual app report; correlational, 2026-03-19).
Benchmark product-led-rpi-d60 Median revenue per install at day 60, freemium apps (low) against hard onboarding paywall apps (high): $0.38–$3.09 (A mobile subscription-billing vendor's annual app report; correlational, 2026-03-19).
Benchmark product-led-trial-length Median trial-to-paid, trials under 4 days (low) against 17 to 32 days (high): 25.5–42.5 % (A mobile subscription-billing vendor's annual app report; correlational, 2026-03-19).
Benchmark product-led-day-0-cancel Share of 3-day-trial cancellations made on day 0: 55.4 % (A mobile subscription-billing vendor's annual app report, 2026-03-19).

Compiled, not written: edit the Play record product-led and its Leaseline run, 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.