Who might cancel: health, silence and who to call first
Who might cancel: health by Customer, 14 days of silence, and the first call your CSM makes.
Customer health reads every paying Customer each morning, and the CSM’s queue opens on the one most likely to cancel.
You, to your agent
Which customers might cancel, and who do I call 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":"customer-health","shape":"trend","breakdown":"customer"}'
curl -X POST "https://api.sb/leaseline.app/processes/retention/enable?mode=try" \
-H "Authorization: Bearer $STARTUP_BUILDER_API_KEY"
curl -X POST https://api.sb/leaseline.app/tasks/next \
-H "Authorization: Bearer $STARTUP_BUILDER_API_KEY"
curl -X POST "https://api.sb/leaseline.app/messages/message_77/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":"churned-arr","shape":"trend","window":"7d"}'import { $ } from 'startup-builder'
const { rows } = await $.Metric.query({ metric: 'customer-health', shape: 'trend', breakdown: 'customer' }) // 4 Customers at risk; Harbor Row lowest since Oct 2
await $.Process.enable({ process: 'retention' }, { mode: 'try' }) // silence-watch: 14 days with no sign-in opens a Task
const { task } = await $.Task.next() // your CSM agent: "Call Harbor Row first: 3 support threads on late notices"
await $.Message.schedule({ message: 'message_77', at: '2026-10-22T15: 00: 00Z' }, { mode: 'try' }) // the upgrade note usage-upsell drafted for Harbor Row
// 422 · The upgrade 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: 'churned-arr', shape: 'trend', window: '7d' }) // Not live yet, gaps #4
npx startup-builder do Metric.query --json '{"metric":"customer-health","shape":"trend","breakdown":"customer"}'
npx startup-builder do Process.enable --json '{"process":"retention"}' --try
npx startup-builder do Task.next
npx startup-builder do Message.schedule --json '{"message":"message_77","at":"2026-10-22T15:00:00Z"}' --try
npx startup-builder do Metric.query --json '{"metric":"churned-arr","shape":"trend","window":"7d"}' # Not live yet (gaps #4){
"name": "do",
"arguments": {
"code": "const { rows } = await $.Metric.query({ metric: 'customer-health', shape: 'trend', breakdown: 'customer' }) // 4 Customers at risk; Harbor Row lowest since Oct 2\nawait $.Process.enable({ process: 'retention' }, { mode: 'try' }) // silence-watch: 14 days with no sign-in opens a Task\nconst { task } = await $.Task.next() // your CSM agent: \"Call Harbor Row first: 3 support threads on late notices\"\nawait $.Message.schedule({ message: 'message_77', at: '2026-10-22T15:00:00Z' }, { mode: 'try' }) // the upgrade note usage-upsell drafted for Harbor Row\n// 422 · The upgrade 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: 'churned-arr', shape: 'trend', window: '7d' }) // Not live yet, gaps #4",
"mode": "try"
}
}Response200
application/json{
"metric": {
"$id": "https: //api.sb/ leaseline.app/ metrics/ churned-arr",
"$type": "Metric",
"name": "Churned ARR"
},
"rows": [
{
"week": "2026-10-19",
"value": -1041,
"customers": 3,
"atRiskWeekBefore": 2
}
]
}
sample · 1 Not live yet−$1,
Metric.query({"metric": · sample · leaseline.app at 2026-10-19
What it does
| What it does | Price per unit | Moves | Who does it | Today |
|---|---|---|---|---|
POST /{namespace}/metrics/query reads customer-health by Customer each morning; the silence-watch Workflow opens a Task after 14 days with no sign-in. | $0 per read; $0.04 per check-in note (Worker cost) | Customer health, Lost Customers | CSM | 4 of 5 calls live |
Set it up
| Check (dry run on a 0-Customer Startup, gaps #8) | Fix | Then | Your time | Wait |
|---|---|---|---|---|
billing-connected (Not live yet, gaps #8) · fail | Connect Stripe so every charge and Meter reading is live. | POST /{namespace}/integrations/connect | 5 min | the first sync, about 10 min |
role-unstaffed (Not live yet, gaps #8) · warn: the CSM has no person: at-risk Tasks come to you | Staff the CSM, or leave it: at-risk Tasks come to you. | POST /{namespace}/roles/{role}/staff | 0 min | none |
The records
| Customer | Health | At risk since | Why | Who calls | By |
|---|---|---|---|---|---|
| Harbor Row | 31 | Oct 2 | 3 support threads on late notices | CSM · your agent drafts, you call | Oct 20 |
| Cedar Lane | 38 | Oct 9 | no sign-in for 19 days; asked us to stop emailing on Oct 12 | you, by phone | Oct 22 |
| Juniper Homes | 44 | Oct 12 | use down 40 %, renews Nov 3 | CSM · POST /{namespace}/renewals/open | Oct 19 |
| Pinecrest Lofts | 47 | Oct 14 | their admin left | CSM · your agent | Oct 21 |
{
"$id": "https://api.sb/leaseline.app/metrics/customer-health" ,
"$type": "Metric",
"name": "Customer health",
"rows": [
{
"customer": "customer_19",
"value": 31,
"atRiskSince": "2026-10-02"
}
]
}Calls
| Call | Here | Emits | Status |
|---|---|---|---|
POST /{namespace}/metrics/query | 4 Customers at risk; Harbor Row lowest since Oct 2 | Metric.queried | live |
POST /{namespace}/processes/{process}/enable | silence-watch: 14 days with no sign-in opens a Task | Process.enabled | live |
POST /{namespace}/tasks/next | your CSM agent: "Call Harbor Row first: 3 support threads on late notices" | Task.offered | live |
POST /{namespace}/messages/{message}/schedule | the upgrade note usage-upsell drafted for Harbor Row | Message.scheduled | live |
POST /{namespace}/metrics/query | Metric.queried | Not live yet, gaps #4 |
Events
| Event | When it fires | What hears it |
|---|---|---|
Task.offered | a Customer turns at risk, or 14 days pass with no sign-in | the CSM’s POST /{namespace}/tasks/next |
Renewal.opened | a term Subscription nears its end | the CSM’s renewal Task |
Refuses when
| Check | Fix | Then |
|---|---|---|
keep-first fail · Not live yet, gaps #8 | Let 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 upgrade 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.
| Check | Refuses | Fix | Then |
|---|---|---|---|
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. | POST /{namespace}/tasks/next |
owned-by-a-person (Not live yet, gaps #8) | a send to a Contact whose Lead, open Deal or Conversation a person owns | Leave it to the person who owns it: the draft waits in their queue. | POST /{namespace}/tasks/{task}/assign |
Plays that use it
| Play | What it does | Use it when |
|---|---|---|
quiet-account-rescue (Not live yet, gaps #4) | The Play that calls a paying Customer who went quiet, before they cancel. | today: The hub Workflow silence-watch, which opens a Task in POST /{namespace}/tasks/next. |
| Annual prepay | Offers a steady monthly Customer a year up front, at a discount their own churn pays for, and answers an annual cancel with one save. | 10 or more monthly Customers past their third renewal, losing 2 % or more a month |
| Objection watch | Records why each Customer or Deal said no, holds them from other outreach, and sends one Message, with proof, once the reason stops being true. | 20 or more nos a month recorded with a reason: lost Deals, cancels, downgrades and stalled trials |
Hub guides it deepens
| On startupbuilder.dev | Id |
|---|---|
| Retention | process: |
| Silence watch | workflow: |
Example
Oct 19 · 4 at risk · Harbor Row first (31) · the upgrade note usage-upsell drafted for them is refused (keep-first) · the CSM calls Oct 20 · 2 of the 3 Customers lost this week were at risk a week earlier · sampleCompiled, 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.